Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/325.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 具有动态实体的泛型ValidationAttribute。属性唯一性检查(在运行时设置)_C#_Asp.net_Linq_Entity Framework - Fatal编程技术网

C# 具有动态实体的泛型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

我目前有一个自定义ValidationAttribute,可以确保属性是唯一的。看起来是这样的:

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