linq to SQL データコンテキストの一意性をチェックするための一般的な検証属性

StackOverflow https://stackoverflow.com/questions/2031547

質問

ここ数日間、asp.net のプログラミングを行ってきました。これは私自身でも理解することができない質問です。

私が何を達成したいのか、そして実際に達成したいのかがコードから明らかであればいいのですが、それはきれいではありません。さらに、どのテーブルでも、どのフィールドでもそれを使用したいと考えています。指定したテーブルとフィールドに対して値の一意性をチェックし、すべてを属性コンストラクターに渡します。

public class UniqueEmailAttribute : ValidationAttribute
{
    public UniqueEmailAttribute()
    {
    }

    public override Boolean IsValid(Object value)
    {
        //not pretty. todo: do away with this.
        var db = new CoinDataContext();
        int c = db.Emails.Count(e => e.Email1 == value.ToString());
        return (Boolean) (c == 0);
    }
}
役に立ちましたか?

解決

ただでブラッド・ウィルソンによってに asp.netのフォーラムから

この。それにそう喜んで。ノーエラーハンドリング!

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public class UniqueAttribute : ValidationAttribute {
    public UniqueAttribute(Type dataContextType, Type entityType, string propertyName) {
        DataContextType = dataContextType;
        EntityType = entityType;
        PropertyName = propertyName;
    }

    public Type DataContextType { get; private set; }

    public Type EntityType { get; private set; }

    public string PropertyName { get; private set; }

    public override bool IsValid(object value) {
        // Construct the data context
        ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
        DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);

        // Get the table
        ITable table = dataContext.GetTable(EntityType);

        // Get the property
        PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);

        // Our ultimate goal is an expression of:
        //   "entity => entity.PropertyName == value"

        // Expression: "value"
        object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
        ConstantExpression rhs = Expression.Constant(convertedValue);

        // Expression: "entity"
        ParameterExpression parameter = Expression.Parameter(EntityType, "entity");

        // Expression: "entity.PropertyName"
        MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo);

        // Expression: "entity.PropertyName == value"
        BinaryExpression equal = Expression.Equal(property, rhs);

        // Expression: "entity => entity.PropertyName == value"
        LambdaExpression lambda = Expression.Lambda(equal, parameter);

        // Instantiate the count method with the right TSource (our entity type)
        MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);

        // Execute Count() and say "you're valid if you have none matching"
        int count = (int)countMethod.Invoke(null, new object[] { table, lambda });
        return count == 0;
    }

    // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
    private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
}

他のヒント

まず、属性の書き換えを見てみましょう...

public override bool IsValid(object value)
{
    var db = new CoinDataContext();

    //Return whether none of the email contains the specified value
    return db.Emails.Count(e => e.Email1 == value.ToString()) == 0;
}

また、キャストする必要はありませんでした (c == 0) その操作の結果がすでにブール値であるため、ブール値として。そしてタイプは bool です エイリアス のために Boolean それと同じように int のエイリアスです Int32. 。どちらでも構いません。私自身は小文字バージョンの方が好きです。

アレックスがすでに彼の中で示唆したように、 答え, 、これは、電子メール アドレスがデータベースに登録されるときに一意であるかどうかを判断する確実な方法ではありません。確認した時点では一意であるというだけです。

最後に、少し話が逸れましたが…私は持っている 書かれた 次のクラスなどのいくつかの linq 拡張機能。これを使用すると、属性の戻り値を次のように書き換えることができます。 db.Emails.None(e => e.Email1 == value.ToString());. 。これにより、 若干 より読みやすくなります。

アップデートデータベースにアクセスして行を書き込まれた値と比較することなく、データベース内の値の一意性を判断する方法はありません。データベースにインスタンスを作成する必要があります。でも私がやるべきことは、見てみることです これらの懸念を分離する サービス層やデータ層などの領域に分割します (MVC Web サイト プロジェクトとは別のプロジェクト)。データ層は、データベースに関するあらゆるものを排他的に処理します。ご希望であれば、CoinDataContext を属性自体から分離する方法の例をいくつか書いていただけますか?

別の懸念事項に対処するために、ここでは属性内のクエリの必要性を削除しますが、それでもデータベースを呼び出し、使用するテーブルを指定する必要があります。

ただし、これは属性であるため、その属性で linq ラムダ式を使用できるかどうかは 100% わかりません。そのため、属性はこの方法で一般化されたままにする必要があります。

データ層プロジェクト

このレイヤーには、さまざまなテーブルに関連するさまざまなクラスが含まれます。以下のクラスは電子メール テーブル専用です。

電子メール マッパー クラス

public static class EmailMapper
{
  public static void IsValid(Func<string, bool> query)
  {
    var db = new CoinDataContext();
    return db.Emails.Count(query) == 0;
  }
}

サービス層プロジェクト

この層はオブジェクトの一般的な検証を担当しますが、外部 API などの他の層にアクセスするためにも使用されます。

EmailService クラス

public static class EmailService
{
  public static IsValid(string address)
  {
    bool isValid = false;

    //...Check email is valid first with regex. Not done.
    isValid = RegexHelper.IsEmailAddressValid(address);

    //Go to the database and determine it's valid ONLY if the regex passes.
    return isValid ? EmailMapper.IsValid(x=> x.Email == address) : false;
  }
}

Web プロジェクトの属性クラス

public override Boolean IsValid(Object value)
{
    return EmailService.IsValid(value.ToString());
}

私は、LINQにないんだけど、あなたが一意のクライアント側を強制しようとしているようです。それはちょうどことはできません。一意性制約は、のデータベースに適用されなければなりません。あなたは、同時トランザクションがそのチェックが行われた直後のメールアドレスをコミットするとどうなるかと思いますか?

あなたは「申し訳ありませんが、そのアドレスがすでに使用されている」-messageを提供するだけでチェックしている場合であっても、そこにあるのまだの別のトランザクションが同じアドレスを挿入している可能性ます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top