申请"其中"表达式到泛型dbset以过滤某些数据

时间:2018-01-26 08:20:00

标签: c# entity-framework generics

我正在编写一个类似于角色的系统;用户只能看到他们有权访问的数据。这适用于用于表单填充,搜索,列表,报告等的数据。

我计划实施此方法,通过添加" WHERE"来添加过滤器来获取请求。执行前的EF查询子句。

如果不是因为我们使用泛型,这很简单。

Get函数曾经是这个

public class EntityFactory<TEntity, TDto> : IEntityFactory<TEntity, TDto> where TEntity : class
{
    private readonly DBContext _context;
    private readonly IMapper _mapper;
    private DbSet<TEntity> _dbset;

    public EntityFactory(DBContext context, IMapper mapper)
    {
        //...
    }

    public async Task<List<TDto>> GetAsync()
    {
        List<TEntity> d = await _dbset.AsNoTracking().ToListAsync();
        return _mapper.Map<List<TDto>>(d);
    }
}

我想做什么:

public async Task<List<TDto>> GetAsync()
{
    //If the object implements a special interface
    if (i.GetInterfaces().Contains(typeof(IFoo)))
    {
        //expression to filter the data on a linked table containing the user's Id.
        Expression<Func<Bar, bool>> exp = x => x.Foos.Any(a => a.UserId == _user.UserId);

        //add the expression to the dbSet
        _dbSet = _dbSet.Where(exp);
    }

    //Execute the get
    List<TEntity> d = await q.AsNoTracking().ToListAsync();

    //return the converted objects
    return _mapper.Map<List<TDto>>(d);
}

但这不起作用!我得到这个编译器错误:

Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<Bar, bool>>' to 'System.Linq.Expressions.Expression<System.Func<TEntity, int, bool>>'

有没有办法:

  • 创建一个&#34;动态&#34;未经检查的查询或
  • 将dbset更改为所需类型,应用表达式并将其返回到泛型类型?

2 个答案:

答案 0 :(得分:0)

您可以使用Linq动态查询来应用where子句。

var query = _dbSet.AsQueryable();
//If the object implements a special interface
if (typeof(IFoo).IsAssignableFrom(typeof(TEntity)))
{
    query = query.Where("UserId = @0", _userId);
}
List<TEntity> d = await query.AsNoTracking().ToListAsync();

答案 1 :(得分:0)

通过寻找不同的方式来查看此问题,您可能会得到更好的服务。由于这是一个安全过滤器,您可能会发现意外地没有实现IFoo,并且您没有按照自己的想法进行过滤。

话虽如此,你可以做到这一点,但它需要几个步骤。您可以手动构建表达式,但我更喜欢在代码中构建表达式,然后使用ReplaceVisitor修改当前类型的表达式。

首先,我创建一个类型为Expression<Func<IFoo,bool>>的过滤表达式。这不是最终的过滤表达式,但它是兼容的,因为我们正在检查泛型类型是否实现了IFoo。

接下来,我使用Expression Visitor将每个对IFoo参数表达式的引用替换为TEntity参数表达式。结果表达式将是Expresssion<Func<TEntity,bool>>,因此我将其转换为该表达式,因此编译器会很高兴。

最后,我将结果表达式传递给Where子句。

注意:这是一个非异步版本,但它演示了这些原则。

public class EntityFactory<TEntity, TDto> : IEntityFactory<TEntity, TDto> where TEntity : class
{
    private readonly DbContext _context;
    private readonly IMapper _mapper = new Mapper(new MapperConfiguration(v => { }));
    private DbSet<TEntity> _dbSet;
    private Type i = typeof(TEntity);

    private Expression<Func<IFoo, bool>> getFilterExpression(int userId)
    {
        return (x => x.Foos.Any(a => a.UserId == userId));
    }

    private class ReplaceVisitor : ExpressionVisitor
    {
        readonly Expression _originalExpression;
        readonly Expression _replacementExpression;

        public ReplaceVisitor(Expression originalExpression, Expression replacementExpression)
        {
            _originalExpression = originalExpression;
            _replacementExpression = replacementExpression;
        }

        public override Expression Visit(Expression node)
        {
            return _originalExpression == node ? _replacementExpression : base.Visit(node);
        }

        public static Expression VisitExpression(Expression node, Expression originalExpression, Expression replacementExpression)
        {
            return new ReplaceVisitor(originalExpression, replacementExpression).Visit(node);
        }
    }

    public List<TDto> Get()
    {
        IQueryable<TEntity> query = _dbSet;

        //If the object implements a special interface
        if (i.GetInterfaces().Contains(typeof(IFoo)))
        {
            var userId = 7;

            var baseFilterExpression = getFilterExpression(userId);
            var filterExpression = (Expression<Func<TEntity, bool>>)ReplaceVisitor.VisitExpression(
                baseFilterExpression,
                baseFilterExpression.Parameters.First(),
                Expression.Parameter(typeof(TEntity)));

            //add the expression to the dbSet
            query = query.Where(filterExpression);
        }

        List<TEntity> d = query.AsNoTracking().ToList();
        return _mapper.Map<List<TDto>>(d);
    }
}
public interface IEntityFactory<TEntity, TDTO> { }
public interface IFoo
{
    List<FooItem> Foos { get; set; }
}
public class FooItem
{
    public int UserId { get; set; }
}