【问题标题】:Apply "where" expression to generic dbset to filter certain data将“where”表达式应用于通用数据库集以过滤某些数据
【发布时间】:2018-01-26 08:20:00
【问题描述】:

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

我计划通过在执行前向 EF 查询添加“WHERE”子句来实现此向 Get 请求添加过滤器的方式。

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

以前的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>>'

有没有办法:

  • 创建未选中的“动态”查询或
  • 将 dbset 更改为所需的类型,应用表达式并将其返回为泛型类型?

【问题讨论】:

  • Queryable.Where 已经适用于泛型和表达式。为什么要创造不同的表达方式?为什么要替换 field 而不是创建新查询?如果没有编译错误,你怎么能做到这一点Where 返回一个 IQueryable。还要检查错误的内容 - 它涉及Expression&lt;System.Func&lt;TEntity, int, bool&gt;&gt;。您是否创建了自己的Where?你不需要
  • 您是否创建了可能会影响 LINQ 自己的方法的扩展方法?为什么?代码中也有一些非常奇怪的 cmets - //If the object implements a special interface if (i.GetInterfaces().Contains(typeof(IFoo))) 什么对象,为什么不是通用约束?为什么对象是字段而不是参数?您是否将数据访问代码放在实体本身中,或者将其实现为实体的基类?这与使用 ORM 完全相反
  • 在泛型方面我仍然是个菜鸟,而且我正在编写由另一个开发人员编写的代码。我得看看他是否创建了额外的扩展方法。
  • @PanagiotisKanavos Where 不是自定义扩展,它来自 System.Linq。但是,有 2 个 where 函数,一个是“预期”的 另一个是 一个。是否有理由调用错误的方法?
  • LINQ to EF 没有两个 Where。 LINQ to Objects 可以。 DbSet 没有Where,也不能将 IEnumerable 或 IQueryable 分配给 DbSet 字段。不知何故,您在代码中的某个地方混淆了字段名称和类型。

标签: c# entity-framework generics


【解决方案1】:

您可以使用 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();

【讨论】:

  • 我收到一个错误“'Where' 不接受 2 个参数。”。将参数更改为 string 编译器会抱怨 string 不是有效的参数
  • 您应该添加“System.Linq.Dynamic”命名空间。
  • 无需使用字符串过滤器。 Where 适用于表达式。最初的错误表明问题不在哪里。其实Where的结果怎么能赋值给一个DbSet字段呢?
  • @Panagiotis Kanavos,是的,你是对的。但是动态查询已经为我们执行了。
  • @Stormcloak 因为你没有混淆了 EF 和 enumerables。如果你有,它可能会在运行时抛出,你甚至不会注意到。此外,它是另一个依赖项。这个答案显示了进行查询的正确方法 - 进行 query 并向其添加条件,而不是 DbSet
【解决方案2】:

找到一种不同的方式来看待这个问题可能会更好地为您服务。由于这是一个安全过滤器,您可能会意外地发现 IFoo 没有实现,并且您没有在您认为是过滤器时进行过滤。

话虽如此,您可以执行此操作,但需要几个步骤。您可以手动构建表达式,但我更喜欢在代码中构建表达式,然后使用 ReplaceVisitor 修改当前类型的表达式。

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

接下来,我使用表达式访问器将每个对 IFoo 参数表达式的引用替换为 TEntity 参数表达式。结果表达式将是一个Expresssion&lt;Func&lt;TEntity,bool&gt;&gt;,所以我将它转换为那个,这样编译器会很高兴。

最后,我将结果表达式传递给 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; }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-19
    • 2019-10-01
    • 1970-01-01
    • 2014-06-14
    • 2014-04-12
    相关资源
    最近更新 更多