简体   繁体   中英

.Net 5 Entity Framework Generic Any Query

As I am a lazy dog I would like to implement a generic UniqueValidator for .net FluentValidation. The goal is simple: have a validator to which I pass the model, the Expression to get the Property / Field that has to be unique and run a EF Any query. That would avoid to write a dumb class every single time one have to verify the unicity of a value in DB.

I came up after few tweaks to what seems to me a fair solution to avoid passing and invoking a precompiled Lambda to EF query translator which of course would result in a expression could not be translated exception.

Here what I have implemented:

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

Here is how the validator is used:

RuleFor(d => d)
    .MustAsync((driver, cancellationToken) => {
        return uv.IsUnique(driver, d => d.Email, cancellationToken);
    });

Unfortunately this throw a very cumbersome and not helpful exception:

System.InvalidOperationException: The LINQ expression 'DbSet<Driver>() .Any(d => d.Email == "any.valid@email.com")' could not be translated...

Note: in the UniqueValidator implementation I added a line to test this very same query that is described in the exception and it runs perfectly, just to discard any doubt on the query validity.

I imagine the problem is with the translation of expression.Body but can't see any reason why nor how to workaround it. Any help would be appreciated.

You must reuse the original parameter of the expression, or you must use an expression replacer:

var pe = new ParameterExpression[] { Expression.Parameter(typeof(T)) };

change to

var pe = expression.Parameters[0];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM