Un attributo di convalida generale per il controllo di unicità in un LINQ to SQL contesto dati
-
19-09-2019 - |
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);
}
}
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.