Lambda 表达式 LINQ 等效于 SQL 在同一表/变量上存在查询

Posted

技术标签:

【中文标题】Lambda 表达式 LINQ 等效于 SQL 在同一表/变量上存在查询【英文标题】:Lambda Expression LINQ Equivalent to SQL Exists Query on Same Table/Variable 【发布时间】:2018-11-25 03:51:16 【问题描述】:

我正在尝试使用 lambda 表达式格式在 LINQ 中复制以下 SQL 查询(以使其与迄今为止开发的代码保持一致):

SELECT *
FROM Product p
WHERE p.DateObsolete IS NULL
    OR p.DateObsolete > GETDATE()
    OR EXISTS (
        SELECT NULL
        FROM dbo.Product p1
        WHERE p1.Ref01 = p.Ref01
            AND p1.Ref02 = p.Ref02
            AND p1.Ref03 = p.Ref03
            AND p1.Version = p.Version + 1
            AND p1.DateApproved IS NULL
        )

查看了其他问题(Linq subquery same table using lambda 是我能找到的最接近的问题,但没有显示如何“或”条件)在 SO 和其他地方我认为以下方法可行,但它只会导致堆栈溢出(严重) 异常和一条关于未加载 pdb 文件的消息,我认为这有点牵强。

products = products
        .Where(p => !p.DateObsolete.HasValue 
              || p.DateObsolete > DateTime.Now 
              || products.Any(p1 => p1.Ref01 == p.Ref01 
                         && p1.Ref02 == p.Ref02 
                         && p1.Ref03 == p.Ref03 
                         && p1.Version == p.Version + 1 
                         && p1.DateApproved == null));

products 是一个 IQueryable 变量。

产品在此上下文中定义为 DbSet:

public class Product

    public int ProductID  get; set; 

    [MaxLength(200)]
    public string Name  get; set; 

    [MaxLength(3)]
    public string Ref01  get; set; 

    [MaxLength(3)]
    public string Ref02  get; set; 

    public int Ref03  get; set; 

    public int Version  get; set; 
    public DateTime? DateReceivedByGraphics  get; set; 
    public DateTime? DateApproved  get; set; 
    public DateTime? DateObsolete  get; set; 

    public bool Deleted  get; set; 
    public bool Discontinued  get; set; 


如您所知,我是 LINQ 的新手(以及在 SO 上发布问题),因此我们将不胜感激地收到任何帮助。

【问题讨论】:

我们能看到 Product 实体吗?我的意思是这个查询中隐含的所有属性。 缺少信息:EF 版本 + products 的类型和来源。我猜数据库是 Sql Server。 尝试将您的表达式分配给一个新变量,例如var newProducts = products.Where(..... 很可能与products 变量闭包有关。尝试var query = products.Where(...),而不是将结果查询分配给products 变量。 对不起,@AleksAndreev 和 Ivan,我再次尝试重新分配(我试图追踪失败的确切点)并且它起作用了:我不确定我做了什么不同的事情。 【参考方案1】:

您可以通过加入来完成此操作。

DateTime date = DateTime.Today; // or .Now
var products = context.Products.Join(context.Products,
    p1 => new  p1.Ref01, p1.Ref02, p1.Ref03 ,
    p2 => new  p2.Ref01, p2.Ref02, p2.Ref03 ,
    (p1, p2) => new  Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved  )
    .Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
    .Select(x=>x.Product)
    .ToList();

这会在 Ref 1-3 上将 Product 与自身连接,然后选择“左侧”项目及其版本、“右侧”版本和批准日期。 Where 条件隔离了“右”版本比左版本大 1 并且没有批准日期的情况。结果将是具有符合这些条件的对应项的“剩余”产品。

更新: 如果您已经将产品过滤到一组已知的适用产品,那么这将对对象起作用。例如:

// given products is an IQueryable representing the filtered products...
DateTime date = DateTime.Today; // or .Now

var productList = products.ToList(); // Materialize the EF Queryable into list of entities.

    productList = productList.Join(productList,
        p1 => new  p1.Ref01, p1.Ref02, p1.Ref03 ,
        p2 => new  p2.Ref01, p2.Ref02, p2.Ref03 ,
        (p1, p2) => new  Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved  )
        .Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
        .Select(x=>x.Product)
        .ToList();

如果您的目标是尝试将其保留为范围为 EF 的 IQueryable,那么我怀疑如果可能的话,它可能不值得复杂性/时间。最坏的情况是,如果您确实想要保留 IQueryable,请使用上述方法将产品 ID 选择到列表中,然后将该列表用作 IQueryable 的过滤器。

var productList = products.ToList(); // Materialize the EF Queryable into list of entities.

// Fetch a list of applicable product IDs.
var productIds = productList.Join(productList,
    p1 => new  p1.Ref01, p1.Ref02, p1.Ref03 ,
    p2 => new  p2.Ref01, p2.Ref02, p2.Ref03 ,
    (p1, p2) => new  ProductId = p1.ProductId, DateObsolete = p1.DateObsolete, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved  )
    .Where(x=> x.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
    .Select(x=>x.ProductId)
    .ToList();

// Filter the original IQueryable.
products = products.Where(x => productIds.Contains(x.ProductId));

【讨论】:

谢谢,但我不确定我能不能这样做。更多背景知识:此查询是在原始产品集合上运行的几个查询之一,用于识别满足用户指定搜索条件的产品集合,因此我需要针对产品变量中的集合而不是原始数据集运行它。我试过了,似乎无法从变量开始加入。抱歉,这只是暴露了我对 LINQ 缺乏了解!【参考方案2】:

正如 Aleks Andreev 和 Ivan Stoev 所建议的,将表达式分配给一个新变量可以解决问题。我不确定为什么这第一次不起作用,但我的猜测是,在完成查询后,我尝试将结果重新分配回原始变量 - 为了不必完全更改变量名称我更改后的代码。

【讨论】:

以上是关于Lambda 表达式 LINQ 等效于 SQL 在同一表/变量上存在查询的主要内容,如果未能解决你的问题,请参考以下文章

如何在 LINQ 和 Lambda 表达式 LINQ C# 上正确执行 SQL 查询

如何为 sql 编写 lambda (linq) 表达式?

帮助我使用实体框架从 SQL 转换为 linq 嵌套 lambda 表达式

如何将带有内连接的 sql 查询转换为 linq lambda 表达式?

等效于 Java 中的 Python 的 lambda 函数?

LINQ TO SQL