使用LINQ和Lambda进行Join/Where查询

561

我在使用LINQ和Lambda编写查询时遇到了问题。目前为止,我的代码出了很多错误:

int id = 1;
var query = database.Posts.Join(database.Post_Metas,
                                post => database.Posts.Where(x => x.ID == id),
                                meta => database.Post_Metas.Where(x => x.Post_ID == id),
                                (post, meta) => new { Post = post, Meta = meta });

我不确定这个查询是否正确。


14
你想要达成什么目标? - Germán Rodríguez
4
你希望查询做什么?以一句话表达。 - hunter
6
你的关键选择器太过复杂。如果你想通过ID进行选择,只需使用x=>x.ID即可。 - Eric Lippert
1
我想从数据库中获取一篇文章以及该文章的元数据。 - David
你在2010年就开始使用Meta了?!? - KornMuffin
10个回答

1284

我发现如果你熟悉SQL语法,使用LINQ查询语法会更加清晰、自然,并且容易发现错误:

var id = 1;
var query =
   from post in database.Posts
   join meta in database.Post_Metas on post.ID equals meta.Post_ID
   where post.ID == id
   select new { Post = post, Meta = meta };

如果您真的坚持使用 Lambda 表达式,那么您的语法有些错误。以下是使用 LINQ 扩展方法相同的查询:

var id = 1;
var query = database.Posts    // your starting point - table in the "from" statement
   .Join(database.Post_Metas, // the source table of the inner join
      post => post.ID,        // Select the primary key (the first part of the "on" clause in an sql "join" statement)
      meta => meta.Post_ID,   // Select the foreign key (the second part of the "on" clause)
      (post, meta) => new { Post = post, Meta = meta }) // selection
   .Where(postAndMeta => postAndMeta.Post.ID == id);    // where statement

12
关于您的编辑,"在JOIN条件中已经设定了ID字段相等;您不需要使用WHERE子句!":WHERE子句并不是测试ID字段之间的相等性,而是测试帖子ID列与声明在查询之外的id参数之间的相等性。 - Daniel Schaffer
10
很棒的 lambda 代码块,易于使用和理解。 - Piotr Kula
1
有时候 lambda 的解释是用 lambda 表示的,讲解得很清楚。 - Pinch
我一直试图使用.includes方法,但它只能让我走到这一步。"from post in database.Posts" 这个方法是我必须要记住的。我在控制器中还注入了DapperDb以进行复杂查询,但对于简单的联接,我会使用from post in database.Posts 的示例。这真的很方便。如果遇到任何性能问题,我会将查询转换为Dapper。 - Andy
meta.Post_ID不是限制连接行数的内部键吗?这是否意味着,如果目标是查找具有元数据的某个帖子,即使该帖子在Posts表中,如果它没有元数据,则它将不会成为结果集的一部分? - Henrik Erlandsson
@HenrikErlandsson 没错,这个例子会生成内连接的 SQL。也可以进行左外连接,请参见:https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-left-outer-joins - Daniel Schaffer

96

你可以有两种方法来解决这个问题。使用LINQPad(如果你是LINQ的新手,这个工具非常有用)和一个虚拟数据库,我编写了以下查询:

Posts.Join(
    Post_metas,
    post => post.Post_id,
    meta => meta.Post_id,
    (post, meta) => new { Post = post, Meta = meta }
)

或者

from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }
在这种情况下,我认为LINQ语法更加简洁(我会根据易读性来选择使用哪种语法)。但是我想指出的是,如果您的数据库中有适当的外键(在post和post_meta之间),则除非您尝试加载大量记录,否则您可能不需要显式连接。根据您的示例,您似乎正在尝试加载单个帖子及其元数据。假设每个帖子都有许多post_meta记录,那么您可以按照以下方式操作:
var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();

如果你想避免n+1问题,那么你可以明确告诉LINQ to SQL一次性加载所有相关项(尽管这可能是一个高级主题,当你更熟悉L2S时再使用)。下面的示例表示:“当你加载一个Post时,也通过由'Post_metas'属性表示的外键加载它的所有记录”:

var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);

var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;

var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically

对于同一类型或者多个不同类型,可以对同一组 DataLoadOptions 进行多次 LoadWith 调用。但是如果你经常这样做,你可能需要考虑缓存。


1
LinqPad和CRM 2016? - Kiquenet

75

Daniel对语法关系有很好的解释,但我为我的团队整理了这份文档,以使他们更容易理解。希望这能帮助某人。输入图像描述


当你只是处理像我们这里一样的值列表时,那是行不通的。对象上没有id属性。 - Talspaugh27
我确实发现这非常有用,但是我遇到了一个错误,需要我添加连接列。此外,查看@Mark Byers发布的答案,连接列在第二个别名meta => meta.Post_ID中具有Post_ID字段。在此示例中,原始选择语句JOIN gStatus g on g.id中的g.id部分未在最终Lambda表达式中复制。 - SausageFingers
3
我并不是试图将这个内容作为 OP 提出的实际 LINQ 答案的参考文章发布,而更多地是提供了一个如何将 SQL 转换为 LINQ 格式的参考,因此我的输入与原始问题有些不同。如果我为 gStatus 值创建了一个类,我会在其中放置一个 id 属性,然后通过 g => g.id 进行连接。我使用值列表来尝试保持代码尽可能简单。 - Talspaugh27
@Talspaugh27 那么为什么在 SQL 查询中它要连接到 gStatus 上的 g.id?这是错误还是有意为之? - Drammy
@Drammy 在 SQL 表中,每个列都必须有一个名称,因此由于这是一个仅用于保存这些 ID 的 1 列表格,我只使用了一个名为 id 的列,而 List<int> 没有这个问题。如果我将其设置为以下内容:
public class IdHolder{ int id }
然后在 gStatus 中使用该对象
List<IdHolder> gStatus = new List<IdHolder>(); gStatus.add(new IdHolder(){id = 7}); gStatus.add(new IdHolder(){id = 8});
那么它将更改 Linq 为
t =>t.value.TaskStatusId, g=>g.id
这种更改有意义吗?
- Talspaugh27
@Talspaugh27 当然可以,我只是在问一下图片中的语法是否能够编译(但我也可以自己测试一下!)。谢谢。 - Drammy

41

你的关键选择器不正确。它们应该接受一个与所讨论的表格类型相同的对象,并返回在联接中使用的键。我认为你的意思是这样的:

var query = database.Posts.Join(database.Post_Metas,
                                post => post.ID,
                                meta => meta.Post_ID,
                                (post, meta) => new { Post = post, Meta = meta });

您可以在键选择器之后应用where子句,而不是作为其一部分。


9

我发布这篇帖子是因为当我开始使用LINQ和EntityFramework时,我花了一整天时间来看这些示例。

如果你正在使用EntityFramework,并且你的Post模型对象集合上设置了一个名为Meta的导航属性,那么这很容易实现。如果你使用实体而没有该导航属性,那你还在等什么?

database
  .Posts
  .Where(post => post.ID == id)
  .Select(post => new { post, post.Meta });

如果你正在使用代码优先的方式,你可以这样设置属性:
class Post {
  [Key]
  public int ID {get; set}
  public int MetaID { get; set; }
  public virtual Meta Meta {get; set;}
}

8
我之前做过类似的事情;
var certificationClass = _db.INDIVIDUALLICENSEs
    .Join(_db.INDLICENSECLAsses,
        IL => IL.LICENSE_CLASS,
        ILC => ILC.NAME,
        (IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
    .Where(o => 
        o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
        o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
    .Select(t => new
        {
            value = t.PSP_INDLICENSECLAsse.ID,
            name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,                
        })
    .OrderBy(x => x.name);

8

LINQ Join 的查询语法

var productOrderQuery = from product in Product.Setup()//outer sequence
                        join order in OrderDetails.Setup()//inner sequence
                        on product.Id equals order.ProductId //key selector
                        select new//result selector
                        {
                            OrderId = order.Id,
                            ProductId = product.Id,
                            PurchaseDate = order.PurchaseDate,
                            ProductName = product.Name,
                            ProductPrice = product.Price
                        };

LINQ Join的方法语法
var productOrderMethod = Product.Setup().//outer sequence
    Join(OrderDetails.Setup(), //inner sequence
    product => product.Id//key selector
    ,order=> order.ProductId //key selector
    ,(product,order)=> //projection result
        new
        {
            OrderId = order.Id,
            ProductId = product.Id,
            PurchaseDate = order.PurchaseDate,
            ProductName = product.Name,
            ProductPrice = product.Price
        }
    );

请参考Product.cs文件

class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    public decimal Price { get; set; }
    public static IEnumerable<Product> Setup()
    {
        return new List<Product>()
        {
            new Product(){Id=1, Name="Bike", Price=30.33M },
            new Product(){Id=2, Name="Car", Price=50.33M },
            new Product(){Id=3, Name="Bus", Price=60.33M }
        };
    }
}

以下是 OrderDetails.cs 类的参考内容

class OrderDetails
{
    public int Id { get; set; }
    public virtual int ProductId { get; set; }

    public DateTime PurchaseDate { get; set; }
    public static IEnumerable<OrderDetails> Setup()
    {
        return new List<OrderDetails>()
        {
            new OrderDetails(){Id=1, ProductId=1, PurchaseDate= DateTime.Now },
            new OrderDetails(){Id=2, ProductId=1, PurchaseDate=DateTime.Now.AddDays(-1) },
            new OrderDetails(){Id=3, ProductId=2, PurchaseDate=DateTime.Now.AddDays(-2) }
        };
    }

}

你能否提供一个带有 where 条件的 LINQ Join 方法语法的答案? - raw_hitt

7

这可能类似于:

var myvar = from a in context.MyEntity
            join b in context.MyEntity2 on a.key equals b.key
            select new { prop1 = a.prop1, prop2= b.prop1};

6
这个LINQ查询应该对您有用。它将获取所有具有文章元数据的帖子。
var query = database.Posts.Join(database.Post_Metas,
                                post => post.postId, // Primary Key
                                meta => meat.postId, // Foreign Key
                                (post, meta) => new { Post = post, Meta = meta });

相等的SQL查询

Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId

1
你在第三个参数后面关闭了括号..."Join没有接受三个参数的重载"。 - I Stand With Russia
5
这与被接受的答案完全相同,而且是在7年后发表的。-1 - reggaeguitar

1

1等于1,两个不同的表连接

var query = from post in database.Posts
            join meta in database.Post_Metas on 1 equals 1
            where post.ID == id
            select new { Post = post, Meta = meta };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接