Generic ValidationAttribute with dynamic unique Entity.Property validation (set at runtime)
I currently have a special ValidationAttribute that makes the property unique. It looks like this:
public class UniqueLoginAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (db.Users.SingleOrDefault(user => user.Login == (string)value) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
}
I would like this check to work with any Entity / Property combination. In other words, set both the Entity (in this case "Users") and the Property (in this case, "Login") at runtime. I've found some examples of DynamicLINQ, but I'd like to get a pure EF solution that I can't seem to find.
source to share
It will work. You need to build the expression tree manually.
Using
Expression<Func<User, string>> userExp = x => x.Login;
UniqueAttribute u = new UniqueAttribute(userExp);`
EDIT: Removed generics to work w / Attribute
. To get the appropriate method SingleOrDefault
, you need to use reflection w / execution types. Note that your expression with this code is not compile-time type checked. You should always declare the expression first (as in the example use case) to avoid type problems.
public class UniqueAttribute
{
private LambdaExpression Selector { get; set; }
private Type EntityType { get; set; }
public UniqueAttribute(LambdaExpression selector) {
this.EntityType = selector.Parameters[0].Type;
this.Selector = selector;
}
private LambdaExpression GeneratePredicate(object value) {
ParameterExpression param = Selector.Parameters[0];
Expression property = Selector.Body;
Expression valueConst = Expression.Constant(value);
Expression eq = Expression.Equal(property, valueConst);
LambdaExpression predicate = Expression.Lambda(eq, new ParameterExpression[]{param});
return predicate;
}
private TEntity SingleOrDefault<TEntity>(IQueryable<TEntity> set, LambdaExpression predicate) {
Type queryableType = typeof(Queryable);
IEnumerable<MethodInfo> allSodAccessors = queryableType.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name=="SingleOrDefault");
MethodInfo twoArgSodAccessor = allSodAccessors.Single(x => x.GetParameters().Length == 2);
MethodInfo withGenArgs = twoArgSodAccessor.MakeGenericMethod(new []{typeof(TEntity)});
return (TEntity) withGenArgs.Invoke(null, new object[]{set, predicate});
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
if (SingleOrDefault(db.Set(EntityType), GeneratePredicate(value)) != null)
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
return null;
}
}
source to share
This might work for you, but has the disadvantage of having to get a complete list of entities and loop through them to find a match. You can specify object and property as arguments for an attribute.
public class UniqueAttribute : ValidationAttribute
{
public UniqueAttribute(Type entityType, string propertyName)
{
_entityType = entityType;
_propertyName = propertyName;
}
private Type _entityType;
private string _propertyName;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Context db = new Context();
foreach (var item in db.Set(_entityType).ToList())
{
if (value.Equals(GetPropertyValue(item, _propertyName))
{
return new ValidationResult(validationContext.DisplayName + " is already taken.");
}
}
return null;
}
private object GetPropertyValue(object item, string propertyName)
{
var type = item.GetType();
var propInfo = type.GetProperty(propertyName);
return (propInfo != null) ? propInfo.GetValue(value, null) : null;
}
}
}
Using:
[Unique(typeof(User), "Login")]
public string Login { get; set; }
source to share