Un attributo di convalida generale per il controllo di unicità in un LINQ to SQL contesto dati

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

Domanda

Sono stato di programmazione asp.net per, oh, un paio di giorni. Ecco una domanda che non posso nemmeno cominciare a capire per me stesso.

Spero che sia evidente dal codice quello che voglio realizzare, e ho, ma non è abbastanza. Inoltre mi piacerebbe utilizzarlo su qualunque tavolo, qualunque campo, vale a dire verificare l'unicità di un valore contro un tavolo e campo a specificare, passando tutto in costruttore di attributo.

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);
    }
}
È stato utile?

Soluzione

Questo solo in da forum ASP.NET da Brad Wilson . Così soddisfatto. No la gestione degli errori!

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

Altri suggerimenti

Per prima cosa, diamo un'occhiata a riscrivere l'attributo ...

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

Inoltre, non c'era bisogno di lanciare (c == 0) come un valore booleano, come risultato di tale operazione è già un bool. E il tipo di bool è un alias per Boolean nello stesso modo in cui int è un alias per Int32. O è accettabile. Io preferisco la versione minuscola me stesso.

Come Alex ha già suggerito nel suo risposta , questo non sarebbe un modo sicuro per determinare se l'indirizzo e-mail è unico quando si va in banca dati. Solo che è unico nel suo genere, al momento della verifica.

Infine, e un po 'fuori-tangente ... Ho scritto alcune estensioni LINQ come ad esempio la seguente classe. Usandolo mi avrebbe permesso di riscrivere il rendimento del attributo per db.Emails.None(e => e.Email1 == value.ToString());. Questo lo rende un po ' più leggibile.

Aggiorna Non c'è un modo per determinare l'unicità di un valore nel database senza andare alla banca dati e confrontando le righe contro i valori scritti. Hai ancora bisogno di creare un'istanza al database. Che cosa farei se è guardare separa queste preoccupazioni in aree come ad esempio un livello di servizio e di una strato di dati (progetti separati dal progetto sito MVC). Il tuo livello di dati avrebbe gestito esclusivamente qualcosa a che fare con il database. Se vuoi posso scrivere alcuni esempi di come si sarebbe separare il CoinDataContext dall'attributo stesso?

Affrontare un'altra delle vostre preoccupazioni, qui togliamo la necessità per la query all'interno dell'attributo, ma è comunque necessario una chiamata al database e specificando quale tabella si desidera utilizzare.

, perché questo è un attributo tuttavia, io non sono sicuro al 100% se è possibile utilizzare le espressioni lambda LINQ nell'attributo, quindi l'attributo deve rimanere generalizzato in questo modo.

progetto strato di dati

Questo strato conterrebbe diverse classi in materia di tavole diverse. La classe inferiore è dedicato alla tabella di posta elettronica.

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

Servizio progetto strato

Questo strato è responsabile per la convalida generale degli oggetti, ma è anche usato per andare ad altri strati quali le API esterne.

class 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 Attribute nel progetto web

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

Io non sono in LINQ, ma sembra che si sta cercando di far rispettare lato client unicità. Questo non è solo possibile. vincoli di unicità deve essere eseguita nel database. Cosa ne pensi succede se una transazione concorrente commette un indirizzo e-mail subito dopo che il check è stato fatto?

Anche se si sta controllando solo per fornire un -message "Mi dispiace, questo indirizzo è già utilizzato", non v'è ancora una possibilità che un'altra transazione inserisce lo stesso indirizzo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top