C# 具有动态实体的泛型ValidationAttribute。属性唯一性检查(在运行时设置)
我目前有一个自定义ValidationAttribute,可以确保属性是唯一的。看起来是这样的:C# 具有动态实体的泛型ValidationAttribute。属性唯一性检查(在运行时设置),c#,asp.net,linq,entity-framework,C#,Asp.net,Linq,Entity Framework,我目前有一个自定义ValidationAttribute,可以确保属性是唯一的。看起来是这样的: public class UniqueLoginAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { Context db = new Conte
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;
}
}
}
我要做的是使此验证适用于任何实体/属性组合。换句话说,在运行时设置实体(在本例中为“用户”)和属性(在本例中为“登录”)。我已经找到了一些DynamicIQ的示例,但我想要一个纯EF解决方案,我似乎找不到。这可能对您有用,但缺点是需要检索实体的完整列表并循环查找匹配项。可以将实体和属性指定为属性的参数
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;
}
}
}
用法:
[Unique(typeof(User), "Login")]
public string Login { get; set; }
这会奏效的。您需要手动构建表达式树 用法
您将如何在运行时指定实体类型和属性名称?属性本质上是声明性的,所以任何参数都是在编译时指定的。。。validationContext.ObjectType.Name将返回实体名称,validationContext.MemberName将返回属性名称。您仍然需要定义要唯一的对象和属性。在运行时?如何?我分担你的痛苦。从Ruby这样的动态解释语言,很难回到c#这样的强类型/编译语言。一些非常简单的元编程技术变得更加复杂…我的DbContext没有可获取的方法。我的坏,可获取的是Linq版本。在EF中,它是Set().-1此解决方案将整个DB表加载到内存中。这是一个巨大的性能瓶颈。我在回答中确实说过。如果有一种方法可以使用属性名作为字符串来构建EF可翻译的“where”谓词,我很想看看。有很多方法,但在这种情况下,只使用泛型并让编译器来完成工作更容易。编译器对lambda表达式的所有“魔力”都可以通过
Expression
类的方法手动复制。请参阅我的答案中的一个示例。它没有生成:泛型类型不能从“ValidationAttribute”派生,因为它是一个属性类。为什么要传递TEntity和TProperty类型?它们可以从validationContext中反映出来。@cschiewek使用泛型可以简化语法。如果没有它们,您就无法在Ctor中轻松地使用选择器
表达式。@just.other.programmer我明白了,但正如我在前面的评论中所说,它无法编译。“泛型类型不能从'ValidationAttribute'派生,因为它是一个属性类”@cschiewek我已更新了答案以删除泛型
Expression<Func<User, string>> userExp = x => x.Login;
UniqueAttribute u = new UniqueAttribute(userExp);`
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;
}
}