通过对象列表中的多个参数过滤查询

Filter query by multiple parameters from the list of objects

有一个 class 看起来像这样:

class CustomerPurchase {
    int CustomerId;
    int PurchaseId;
    int Cost;
}

我使用代码优先方法和 Entity Framework 核心迁移来创建数据库。

还有一个看起来像这样的过滤器:

class Filter {
    int CustomerId;
    int PurchaseId;
}

现在,我正在尝试按 ID 对过滤数据。

这是我的代码:

var filters = new List<Filter>();

ctx.CustomerPurchases
    .Where(p => filters.Any(f => 
        f.CustomerId == p.CustomerId &&
        f.PurchaseId == p.PurchaseId))
    .ToList();

显然,这是行不通的。 filters 是对象列表,它不会被翻译成 SQL 查询。 我遇到了一个例外:The LINQ expression blah could not be translated.

那么,我该如何让它发挥作用呢?这个特定的 table 包含几百万条记录,所以我无法在客户端过滤它。

我与 entity framework 无关:请随时提出不同的方法,以防万一。 出于显而易见的原因,我不会生成原始查询,例如 WHERE (CustomerId = {1} AND PurchaseId = {2}) OR ...。除此之外的任何东西都是受欢迎的。

我之前遇到过这个问题,这里是我如何解决它的 在这种情况下你有两个选择

1- 根据 ids 过滤

var res1 = dbcontext.CustomerPurchases
    .Where(p => filters.Select(c=>c.PurchaseId).Contains(p.PurchaseId))
    .Where(p => filters.Select(c => c.CustomerId).Contains(p.CustomerId));

2- 使用包含

var resq = await dbcontext.CustomerPurchases
    .Where(p=> filters.Contains(new Filter { CustomerId = p.CustomerId,PurchaseId = p.PurchaseId }))
    .ToListAsync();

但是如果你 运行 这个你不会得到任何结果,除非你实施 IEquatable

所以你的 Filter class 看起来像这样

public class Filter : IEquatable<Filter>
{
    public int CustomerId;
    public int PurchaseId;

    public bool Equals(Filter? other)
    {
        return this.PurchaseId == other.PurchaseId &&
            this.CustomerId == other.CustomerId;
    }
}

两种方式的完整代码如下

var options = new DbContextOptionsBuilder<ApplicationDBContext>()
                  .UseInMemoryDatabase("test")
                  .Options;
var dbcontext = new ApplicationDBContext(options);
await dbcontext.CustomerPurchases.AddAsync(new CustomerPurchase { CustomerId = 1,PurchaseId = 1,Cost = 10 });
await dbcontext.CustomerPurchases.AddAsync(new CustomerPurchase { CustomerId = 1, PurchaseId = 2, Cost = 10 });
await dbcontext.CustomerPurchases.AddAsync(new CustomerPurchase { CustomerId = 1, PurchaseId = 3, Cost = 10 });
await dbcontext.CustomerPurchases.AddAsync(new CustomerPurchase { CustomerId = 2, PurchaseId = 2, Cost = 10 });
await dbcontext.SaveChangesAsync();

var filters = new List<Filter>();
filters.Add(new Filter { CustomerId = 1, PurchaseId = 2 });
filters.Add(new Filter { CustomerId = 2, PurchaseId = 2 });

var resq = await dbcontext.CustomerPurchases
    .Where(p=> filters.Contains(new Filter { CustomerId = p.CustomerId,PurchaseId = p.PurchaseId }))
    .ToListAsync();
foreach (var item in resq)
{
    Console.WriteLine($" CustomerId : {item.CustomerId} , PurchaseId : {item.PurchaseId} Cost : {item.Cost}");
}

var res1 = dbcontext.CustomerPurchases
    .Where(p => filters.Select(c=>c.PurchaseId).Contains(p.PurchaseId))
    .Where(p => filters.Select(c => c.CustomerId).Contains(p.CustomerId));

var res = await res1.ToListAsync();
Console.WriteLine("===========================================================");
foreach (var item in res)
{
    Console.WriteLine($" CustomerId : {item.CustomerId} , PurchaseId : {item.PurchaseId} Cost : {item.Cost}");
}

和运行ning代码

更新 更改为 SQL 服务器后,我仍然遇到错误,因此当 运行 在 SQL 服务器

上运行时,选项 2 不是一个选项

但我找到了另一个解决方案,我可以根据我拥有的过滤器列表构建 where 子句 我找到这个 PredicateBuilder 这是使用 predicate builder

的代码
var whereclause = PredicateBuilder.False<CustomerPurchase>();
foreach (var filterrow in filters)
{

    whereclause = whereclause.Or(c => c.CustomerId == filterrow.CustomerId && c.PurchaseId == filterrow.PurchaseId);
    
}
var resqq = dbcontext.CustomerPurchases.Where(whereclause);

var resq = await resqq.ToListAsync();
foreach (var item in resq)
{
    Console.WriteLine($" CustomerId : {item.CustomerId} , PurchaseId : {item.PurchaseId} Cost : {item.Cost}");
}

这将构建查询,该查询将由 sql 转换为以下语句

DECLARE @__filterrow_CustomerId_0 int = 1;
DECLARE @__filterrow_PurchaseId_1 int = 2;
DECLARE @__filterrow_CustomerId_2 int = 2;
DECLARE @__filterrow_PurchaseId_3 int = 2;

SELECT [c].[PurchaseId], [c].[CustomerId]
FROM [dbo].[CustomerPurchase] AS [c]
WHERE (([c].[CustomerId] = @__filterrow_CustomerId_0) AND ([c].[PurchaseId] = @__filterrow_PurchaseId_1)) 
    OR 
    (([c].[CustomerId] = @__filterrow_CustomerId_2) AND ([c].[PurchaseId] = @__filterrow_PurchaseId_3))

这是 PredicateBuyilder

的完整 class
public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                        Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                         Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }
}

希望能回答你的问题!!