Un attribut de validation générale pour le contrôle de l'unicité dans un contexte de LINQ à données sql

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

Question

J'ai été la programmation asp.net pour, oh, quelques jours maintenant. Voici une question que je ne peux même pas commencer à comprendre pour moi-même.

J'espère qu'il est évident à partir du code ce que je veux accomplir, et je dois, mais ce n'est pas assez. De plus, je voudrais l'utiliser sur tout le tableau, quel que soit le terrain, à savoir vérifier l'unicité d'une valeur par rapport à une table et sur le terrain je précise, en passant tout dans le constructeur d'attribut.

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);
    }
}
Était-ce utile?

La solution

juste de forums asp.net par Brad Wilson . Alors heureux avec elle. Aucune manipulation d'erreur!

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);
}

Autres conseils

Tout d'abord, regardons la réécriture de l'attribut ...

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;
}

En outre, il n'y avait pas besoin de jeter (c == 0) comme un booléen, à la suite de cette opération est déjà un bool. Et le type bool est un pour Boolean de la même manière alias int est un alias pour Int32. Ou bien est acceptable. Je préfère la version minuscule moi-même.

Comme Alex a déjà suggéré dans son réponse , ce ne serait pas un moyen sûr de déterminer si l'adresse e-mail est unique quand il entre dans la base de données. Seulement qu'il est unique au moment de la vérification.

Enfin, et un peu hors tangente ... J'ai écrit quelques extensions LINQ telles que la classe suivante. Son utilisation me permettrait de réécrire le retour sur l'attribut à db.Emails.None(e => e.Email1 == value.ToString());. Cela en fait un peu plus lisible.

Mise à jour Il n'y a pas moyen de déterminer le caractère unique d'une valeur dans la base de données sans aller à la base de données et en comparant les lignes contre les valeurs écrites. Vous avez encore besoin de créer une instance de la base de données. Ce que je ferais bien est de regarder seperating ces préoccupations dans des domaines tels que une couche de service et un couche de données (projets distincts du projet de site Web MVC). Votre couche de données traiterait exclusivement rien à voir avec la base de données. Si vous souhaitez que je peux écrire quelques exemples de la façon dont vous souhaitez séparer l'CoinDataContext de l'attribut lui-même?

Aborder une autre de vos préoccupations, ici nous enlevons la nécessité de la requête dans l'attribut, mais vous avez encore un appel à la base de données, et préciser quelle table que vous souhaitez utiliser.

Parce que c'est un attribut cependant, je ne suis pas sûr à 100% si vous pouvez utiliser des expressions LINQ lambda dans l'attribut, donc votre attribut doit rester généralisée de cette façon.

Projet de couche de données

Cette couche contiendrait différentes classes relatives aux tables différentes. La classe ci-dessous est dédiée à la table e-mail.

Email classe Mapper

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

Projet de couche de service

Cette couche est responsable de la validation générale des objets, mais aussi est utilisé pour aller à d'autres couches telles que les API externes.

classe 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;
  }
}

classe d'attributs dans le projet web

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

Je ne suis pas dans LINQ, mais il semble que vous essayez d'appliquer côté client unique. C'est tout simplement pas possible. doivent être exécutée dans la base de données. Que pensez-vous faire si une transaction concurrente commet une adresse e-mail juste après que l'enregistrement a été fait?

Même si vous vérifiez juste pour fournir un « Désolé, cette adresse est déjà utilisée » -Message, il est toujours une possibilité qu'une autre transaction insère la même adresse.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top