.Net 5 Entity Framework 通用任意查询
.Net 5 Entity Framework Generic Any Query
因为我是一只懒狗,所以我想为 .net FluentValidation 实现一个通用的 UniqueValidator。目标很简单:有一个我将模型传递给的验证器,用于获取必须唯一的 属性 / 字段的表达式和 运行 一个 EF Any
查询。这样可以避免每次必须验证数据库中值的唯一性时都写一个愚蠢的class。
我对在我看来是一个公平的解决方案进行了一些调整,以避免将预编译的 Lambda 传递和调用到 EF 查询转换器,这当然会导致 expression could not be translated
异常。
这里是我实现的:
public class UniqueValidator : IUniqueValidator
{
private ApplicationContext _dbContext;
public UniqueValidator(ApplicationContext dbContext)
{
_dbContext = dbContext;
}
public async Task<bool> IsUnique<T>(T model, Expression<Func<T, string>> expression, CancellationToken cancellationToken) where T : class
{
// Getting the value to check for unicity:
Func<T, string> method = expression.Compile(true);
var value = method(model);
// For GetDbSet<T>() test purpose, run perfectly:
bool test = await GetDbSet<T>().OfType<BL.Driver>().AnyAsync(d => d.Email == "any.valid@email.com");
// Building Linq expression
var binaryExpression = Expression.Equal(expression.Body, Expression.Constant(value));
var pe = new ParameterExpression[] { Expression.Parameter(typeof(T)) };
var anyExpression = Expression.Lambda<Func<T, bool>>(binaryExpression, pe);
return !(await GetDbSet<T>().AnyAsync(anyExpression));
}
private DbSet<T> GetDbSet<T>() where T : class
{
return (DbSet<T>)typeof(ApplicationContext)
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(DbSet<T>))
.GetValue(_dbContext);
}
}
验证器的使用方法如下:
RuleFor(d => d)
.MustAsync((driver, cancellationToken) => {
return uv.IsUnique(driver, d => d.Email, cancellationToken);
});
不幸的是,这会抛出一个非常麻烦且无用的异常:
System.InvalidOperationException: The LINQ expression 'DbSet<Driver>() .Any(d => d.Email == "any.valid@email.com")' could not be translated...
注意:在 UniqueValidator 实现中,我添加了一行来测试异常中描述的相同查询,它 运行 非常完美,只是为了消除对查询有效性的任何疑问。
我想问题出在 expression.Body
的翻译上,但看不出任何原因或解决方法。
任何帮助将不胜感激。
您必须重新使用表达式的原始参数,或者您必须使用表达式替换器:
var pe = new ParameterExpression[] { Expression.Parameter(typeof(T)) };
改为
var pe = expression.Parameters[0];
因为我是一只懒狗,所以我想为 .net FluentValidation 实现一个通用的 UniqueValidator。目标很简单:有一个我将模型传递给的验证器,用于获取必须唯一的 属性 / 字段的表达式和 运行 一个 EF Any
查询。这样可以避免每次必须验证数据库中值的唯一性时都写一个愚蠢的class。
我对在我看来是一个公平的解决方案进行了一些调整,以避免将预编译的 Lambda 传递和调用到 EF 查询转换器,这当然会导致 expression could not be translated
异常。
这里是我实现的:
public class UniqueValidator : IUniqueValidator
{
private ApplicationContext _dbContext;
public UniqueValidator(ApplicationContext dbContext)
{
_dbContext = dbContext;
}
public async Task<bool> IsUnique<T>(T model, Expression<Func<T, string>> expression, CancellationToken cancellationToken) where T : class
{
// Getting the value to check for unicity:
Func<T, string> method = expression.Compile(true);
var value = method(model);
// For GetDbSet<T>() test purpose, run perfectly:
bool test = await GetDbSet<T>().OfType<BL.Driver>().AnyAsync(d => d.Email == "any.valid@email.com");
// Building Linq expression
var binaryExpression = Expression.Equal(expression.Body, Expression.Constant(value));
var pe = new ParameterExpression[] { Expression.Parameter(typeof(T)) };
var anyExpression = Expression.Lambda<Func<T, bool>>(binaryExpression, pe);
return !(await GetDbSet<T>().AnyAsync(anyExpression));
}
private DbSet<T> GetDbSet<T>() where T : class
{
return (DbSet<T>)typeof(ApplicationContext)
.GetProperties()
.FirstOrDefault(p => p.PropertyType == typeof(DbSet<T>))
.GetValue(_dbContext);
}
}
验证器的使用方法如下:
RuleFor(d => d)
.MustAsync((driver, cancellationToken) => {
return uv.IsUnique(driver, d => d.Email, cancellationToken);
});
不幸的是,这会抛出一个非常麻烦且无用的异常:
System.InvalidOperationException: The LINQ expression 'DbSet<Driver>() .Any(d => d.Email == "any.valid@email.com")' could not be translated...
注意:在 UniqueValidator 实现中,我添加了一行来测试异常中描述的相同查询,它 运行 非常完美,只是为了消除对查询有效性的任何疑问。
我想问题出在 expression.Body
的翻译上,但看不出任何原因或解决方法。
任何帮助将不胜感激。
您必须重新使用表达式的原始参数,或者您必须使用表达式替换器:
var pe = new ParameterExpression[] { Expression.Parameter(typeof(T)) };
改为
var pe = expression.Parameters[0];