使用LINQ进行查询,其中Context与对象列表中的多个参数匹配

时间:2017-08-07 21:37:57

标签: c# sql-server linq asp.net-core

假设我有一张桌子"文章"多列,例如:

CREATE TABLE dbo.Article (
  Id int NOT NULL,
  ProducerId INT NOT NULL,
  Barcode nvarchar(50) NOT NULL,
  DescriptionText nvarchar(50) NOT NULL,
  ActiveFlag BIT NOT NULL
)

在我的ASP.NET核心应用程序中,我使用LINQ来查询该表,例如:

IQueryable<Article> query = _context.Article
                     .Where( p => p.Active == true );

当然有效。 现在我得到一些参数,这是一个非常简单的对象,带有 List&lt; ArticleQuery&gt; 或者另外一个 IEnumerable&lt; ArticleQuery&gt;

ArticleQuery.cs:

public class ArticleQuery
{
    [Required]
    public IEnumerable<ArticleRequest> Parts { get; set; }
}

public class ArticleRequest
{
    public int ProducerId { get; set; }
    public string Barcode { get; set; }
}

实际上我不知道如何将它集成到LINQ中。我尝试了很多东西,但最后我从来没有想过会得到像这样的东西:

IQueryable<Article> query = _context.Article
    .Join(articleQuery.ArticleRequest,
        x => new { a = x.Barcode, b = x.Barcode},
        y => new { a = y.ProducerId, b = y.ProducerId},
        (x, y) => x);

这个伪代码也没有用(但是Join似乎是更好的尝试):

IQueryable<Article> query = _context.Article.Where( p => 
   p.Active == true &&
   articleQuery.ArticleRequest.ProducerId.Contains(p.ProducerId) && 
   articleQuery.ArticleRequest.Barcode.Contains(p.Barcode)
);

任何想法如何正常工作?

2 个答案:

答案 0 :(得分:3)

不幸的是,这是正常的LINQ to Entities代码无法很好地处理的情况。

想要使用的语法如下所示:

IQueryable<Article> query = _context.Article
    .Where( p => 
       p.Active == true &&
       articleQuery.ArticleRequest.Any(
           r => r.ProducerId == p.ProducerId && 
                r.ArticleRequest.Barcode == p.Barcode
       )
    );

不幸的是,LINQ to Entities无法将内存中的集合混合到像这样的数据库查询中。

理论上,您可以将ArticleRequest表示为数据库中的模型,并执行类似于上述查询的查询,但基于上下文中的IQueryable<ArticleRequest>而不仅仅是参数变量。但那将是一个非常糟糕的解决方法。

有些人诉诸于实现整套Article对象,以便他们可以使用LINQ to Objects提供程序:

IQueryable<Article> query = _context.Article
    .AsEnumerable() // DANGER: materializes *all* the articles
    .Where( p => 
       p.Active == true &&
       articleQuery.ArticleRequest.Any(
           r => r.ProducerId == p.ProducerId && 
                r.ArticleRequest.Barcode == p.Barcode
       )
    );

但是当你收到更多文章时,这种方法不会很好地扩展。

另一种方法是动态构建表达式树,这将导致表达式如下所示:

    p => 
       p.Active == true &&
       ((p.ProducerId == 12 && p.BarCode == "12345") ||
        (p.ProducerId == 13 && p.BarCode == "54321") ||
        ...
       )

但是动态生成这种语句所需的代码非常难看并且很难理解。

更好的解决方案可能是使用一堆联盟。一种简洁的方式来表示:

IQueryable<Article> query = articleQuery.ArticleRequest.Aggregate(
    _context.Article.Where(p => false),
    (q, r) => q.Union(context.Article.Where(p => 
           r.ProducerId == p.ProducerId && 
           r.ArticleRequest.Barcode == p.Barcode))
)
    .Where(p => p.Active == true);

这将生成一个SQL查询,该查询将几个一次性查询的结果相互联系起来。使用更多C#代码,您可以删除多余的&#34; Where False&#34;查询的一部分。

还有其他图书馆,例如Dynamic LINQLINQKit,旨在帮助解决这类问题,但是自从我和他们一起玩以来已经很久了我无法保证他们对现代版本的Entity Framework的效果如何。

或者,您可以完全避免使用LINQ,只为这一个查询构造一些自定义SQL。这可能就是我的所作所为。注意防止条形码上的SQL注入!

答案 1 :(得分:1)

我自己解决了这个问题。不完美,但它正在发挥作用。

我使用表“RequestArticles”为我的webservice创建了一个新数据库:

CREATE TABLE dbo.RequestArticle(
    RequestId UNIQUEIDENTIFIER NOT NULL,
    ProducerId INT NOT NULL,
    Barcode NVARCHAR(30) NOT NULL
)

我将请求写入该表:

Guid requestGuid = Guid.NewGuid();
foreach (var requestArticle in requestedArticles.Articles) requestArticle.RequestId = requestGuid;
_context.RequestArticle.AddRange(requestedArticles.Articles); //IEnumerable list of articles
_context.SaveChanges();

之后我只是手动加入了我的新请求表。

IQueryable<Articles> query = _context.Articles.FromSql<Articles>(
    @"SELECT      a.*, ra.Barcode AS RequestedBarcode
      FROM        dbo.Articles a
      INNER JOIN  Webservice.dbo.RequestArticle ra
              ON  ra.ProducerId = a.ProducerId AND ra.Barcode = a.Barcode
      WHERE       ra.RequestId = {1}",
    requestGuid);

目前我正在使用与新上下文不同的字符串替换SQL-String中的新静态数据库名称,但基础知识像魅力一样快速运行。虽然它不是我希望的解决方案,但我认为LINQ + EF比那更聪明。 : - )