在EF 4.1中进行与LINQ的许多关系进行比较的最有效方法是什么?

在我的数据库中,我有以下表格:

  • 岗位
  • InterestTag

Person-InterestTag和Post-InterestTag之间存在许多关系

我需要在EF 4.1中执行linq查询以撤回包含至少一个与至少一个与给定用户相关的兴趣标签匹配的兴趣标签的任何post。

一个人有以下兴趣:

  • 汽车
  • 体育
  • 身体素质

我需要退回任何与汽车,运动或健身相关的post。

在性能方面编写此查询的最有效方法是什么?

编辑

根据下面给出的答案遇到错误…

这编译得很好但在运行时抛出错误:

var matchingPosts = posts.Where(post => post.Topics.Any(postTopic => person.Interests.Contains(postTopic))); 

错误是:

 Unable to create a constant value of type 'System.Collections.Generic.ICollection`1'. Only primitive types ('such as Int32, String, and Guid') are supported in this context. 

任何想法如何解决这一问题?

编辑2

所以我的课程结构如下:

 public class Person { public int PersonID {get; set;} public string FirstName {get; set;} public string LastName {get; set;} //other properties of types string, int, DateTime, etc. public ICollection InterestTags {get; set;} } public class Post { public int PostID {get; set;} public string Title{get; set;} public string Content {get; set;} //other properties of types string, int, DateTime, etc. public ICollection InterestTags {get; set;} } public class InterestTag { public int InterestTagID { get; set; } public string InterestDescription { get; set; } public bool Active { get; set; } public ICollection Persons { get; set; } public ICollection Posts { get; set; } } 

在我的Context类中,我重写了OnModelCreating来定义我的数据库表名

 modelBuilder.Entity().HasMany(u => u.InterestTags).WithMany(t => t.Persons) .Map(m => { m.MapLeftKey("PersonID"); m.MapRightKey("InterestTagID"); m.ToTable("PersonInterestTags"); }); modelBuilder.Entity().HasMany(u => u.InterestTags).WithMany(t => t.Posts) .Map(m => { m.MapLeftKey("PostID"); m.MapRightKey("InterestTagID"); m.ToTable("PostInterestTags"); }); 

在我的查询方法中,我带回了一个IQueryable of Post并应用了一些filter,包括我在这个问题中试图完成的条款。

  var person = personRepository.Get(x => x.PersonID = 5); var posts = postRepository.GetQueryable(); //I have tried this and get the error above posts= posts.Where(x => x.InterestTags.Any(tag => person.InterestTags.Contains(tag))); 

如果你只从给定的personId (或userId )开始,你可以在一个往返中执行此查询,如下所示:

 var posts = context.Posts .Intersect(context.People .Where(p => p.Id == givenPersonId) .SelectMany(p => p.InterestTags.SelectMany(t => t.Posts))) .ToList(); 

这转换为SQL中的INTERSECT语句。

您也可以通过两次往返来完成此操作:

 var interestTagsOfPerson = context.People.Where(p => p.Id == givenPersonId) .Select(p => p.InterestTags.Select(t => t.Id)) .SingleOrDefault(); // Result is an IEnumerable which contains the Id of the tags of this person var posts = context.Posts .Where(p => p.InterestTags.Any(t => interestTagsOfPerson.Contains(t.Id))) .ToList(); // Contains translates into an IN clause in SQL 

在第二个查询中使用基元类型列表( interestTagsOfPersonint的集合)也可以修复您在问题的编辑中提到的错误。 对于Contains您无法在LINQ to Entities中使用对象引用,因为EF不知道如何将其转换为SQL。

我不知道这两种方法中的哪一种更快(SQL专家可能有更好的想法),但可能会开始测试第一个选项。 (我已经测试了一下,似乎返回了正确的结果,但这是我第一次使用Intersect 。)

编辑

要了解生成的SQL(从SQL事件探查器捕获):

第一个查询(使用Intersect )创建此SQL查询:

 SELECT [Intersect1].[Id] AS [C1], [Intersect1].[Name] AS [C2], FROM (SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], FROM [dbo].[Posts] AS [Extent1] INTERSECT SELECT [Join1].[Id] AS [Id], [Join1].[Name] AS [Name], FROM [dbo].[PersonInterestTags] AS [Extent2] INNER JOIN (SELECT [Extent3].[TagId] AS [TagId], [Extent4].[Id] AS [Id], [Extent4].[Name] AS [Name] FROM [dbo].[PostInterestTags] AS [Extent3] INNER JOIN [dbo].[Posts] AS [Extent4] ON [Extent3].[PostId] = [Extent4].[Id] ) AS [Join1] ON [Extent2].[TagId] = [Join1].[TagId] WHERE 1 = [Extent2].[PersonId]) AS [Intersect1] 

第二种选择:

Query1(用于人员标签ID的列表):

 SELECT [Project1].[Id] AS [Id], [Project1].[C1] AS [C1], [Project1].[TagId] AS [TagId] FROM ( SELECT [Limit1].[Id] AS [Id], [Extent2].[TagId] AS [TagId], CASE WHEN ([Extent2].[PersonId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] FROM (SELECT TOP (2) [Extent1].[Id] AS [Id] FROM [dbo].[People] AS [Extent1] WHERE 1 = [Extent1].[Id] ) AS [Limit1] LEFT OUTER JOIN [dbo].[PersonInterestTags] AS [Extent2] ON [Limit1].[Id] = [Extent2].[PersonId] ) AS [Project1] ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC 

查询2为最终post:

 SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], FROM [dbo].[Posts] AS [Extent1] WHERE EXISTS (SELECT 1 AS [C1] FROM [dbo].[PostInterestTags] AS [Extent2] WHERE ([Extent1].[Id] = [Extent2].[PostId]) AND ([Extent2].[TagId] IN (1,2,3)) ) 

在此示例中,查询1返回(1,2,3),因此查询2中的IN子句中返回(1,2,3)。

编辑:我不得不编辑我的post因为我忘记了post和主题之间的很多关系。 现在它应该工作。

我不能告诉你这是否是最有效的方法,但它将是一种使用LINQ查询的方式,因此它应该是高效的:

 var matchingPosts = posts.Where(post => post.Topics.Any(postTopic => person.Interests.Contains(postTopic))); 

如果你想使用并行执行,你可以像这样修改它:

 var matchingPosts = posts.AsParallel().Where(post => post.Topics.Any(postTopic => person.Interests.Contains(postTopic))); 

在使用EF时,您需要以下查询:

 var matchingPosts = from post in posts where post.Topics.Any(topic => person.Interests.Contains(topic)) select post; 

这个怎么样:

 context.Persons .Where(p => p.Name == "x") .SelectMany(p => p.Interests.SelectMany(i => i.Posts)) .Distinct() .Take(10) .ToList(); 

Take()是出于性能原因和分页。 您永远不应该选择所有记录,因为首先没有用户会读取数千条记录的列表,第二条结果集将来可能会增长,查询将无法扩展。