具有动态Entity.Property唯一检查的通用ValidationAttribute(在运行时设置)

时间:2014-10-21 12:39:08

标签: c# asp.net linq entity-framework

我目前有一个自定义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;

        }
    }
}

我想要的是使此验证适用于任何实体/属性组合。换句话说,在运行时设置实体(在本例中为“Users”)和Property(在本例中为“Login”)。我找到了一些DynamicLINQ的例子,但我想要一个纯粹的EF解决方案,我似乎无法找到它。

2 个答案:

答案 0 :(得分:1)

这会奏效。您需要手动构建表达式树。

用法

Expression<Func<User, string>> userExp = x => x.Login;
UniqueAttribute u = new UniqueAttribute(userExp);`

编辑:删除了与Attribute一起工作的泛型。您需要使用带有运行时类型的反射来获取适当的SingleOrDefault方法。请注意,使用此代码不会对表达式进行编译时类型检查。您应该首先声明表达式(我在使用示例中的方式)以避免类型问题。

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

答案 1 :(得分:-1)

这可能对您有用,但是需要检索完整的实体列表并循环查找实体以查找匹配。您可以将实体和属性指定为属性的参数。

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