问题描述
在我的数据库,我有以下表格:
- 人
- 发表
- InterestTag
多对多的关系与人InterestTag和Post-InterestTag
之间存在我需要执行EF 4.1 LINQ查询给拉了回来,其中包含与给定用户至少一个兴趣标签匹配至少一个兴趣标签任何职务。
示例
一个人有以下利益:
- 汽车
- 体育
- 健身
我需要返回的是与不是汽车,体育,健身或张贴任何
什么是写在性能方面此查询的最有效方法是什么?
修改
运行到一个错误的基础上下面给出了答案......
这编译正常,但在运行时抛出一个错误:
VAR matchingPosts = posts.Where(后= GT; post.Topics.Any(postTopic => person.Interests.Contains(postTopic)));
该错误是:
无法创建类型的常值'System.Collections.Generic.ICollection`1。只有原始类型('如的Int32,String和的Guid')在这方面的支持。
任何想法如何解决这一问题?
编辑2
所以我的课的结构如此:
公共类Person
{
公众诠释是PersonID {搞定;组;}
公共字符串名字{获得;组;}
公共字符串名字{获得;组;}
字符串类型,整型,日期时间等//其他属性 公众的ICollection< InterestTag> InterestTags {搞定;组;}
}
公共类帖子
{
公众诠释{帖子ID获取;组;}
公共字符串名称{搞定;组;}
公共字符串内容{搞定;组;}
字符串类型,整型,日期时间等//其他属性 公众的ICollection< InterestTag> InterestTags {搞定;组;}
}公共类InterestTag
{
公众诠释InterestTagID {搞定;组; }
公共字符串InterestDescription {搞定;组; }
公共BOOL活跃{搞定;组; } 公众的ICollection<&人GT;人{搞定;组; }
公众的ICollection<邮政和GT;帖子{搞定;组; }
}
在我的上下文类我重写OnModelCreating来定义我的数据库表名
modelBuilder.Entity<&人GT;()的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(帖子ID);
m.MapRightKey(InterestTagID);
m.ToTable(PostInterestTags);
});
在我的查询方法,我带回后的一个IQueryable和应用一些过滤器,包括其中我想在这个问题上要完成的条款。
变种人= personRepository.Get(X => x.PersonID = 5);
VAR帖= postRepository.GetQueryable(); //我曾经尝试这样做,并得到上述错误
帖= posts.Where(X => x.InterestTags.Any(标签=> person.InterestTags.Contains(标签)));
如果你开始只是一个给定的 PERSONID
(或用户id
),你可以做这个查询在一个往返像这样:
VAR帖= context.Posts
.Intersect(context.People
。凡(P => p.Id == givenPersonId)
.SelectMany(P => p.InterestTags.SelectMany(T => t.Posts)))
.ToList();
这意味着在SQL的 INTERSECT
语句。
您也可以做到这一点在两个往返:
VAR interestTagsOfPerson = context.People.Where(P => p.Id == givenPersonId)
。选择(P => p.InterestTags.Select(T => t.Id))
.SingleOrDefault();
//结果是一个IEnumerable< INT>其中包含此人的标签的标识VAR帖= context.Posts
。凡(P => p.InterestTags.Any(T => interestTagsOfPerson.Contains(t.Id)))
.ToList();
//包含转化成SQL IN子句
使用第二个查询原始类型( interestTagsOfPerson
是 INT
的集合)还修复了列表错误你在你的问题中提到编辑。对于包含
您不能使用LINQ中的对象引用到实体,因为EF不知道如何翻译成SQL。
我不知道这两种方法哪个更快(SQL专家可能有一个更好的主意),但可能会开始测试的第一个选项。 (我测试过一点点,它似乎返回正确的结果,但它是我用相交
。第一次)
修改
要给予生成的SQL(从SQL事件探查器捕获)的一个想法:
第一个查询(用相交
)创建此SQL查询:
SELECT
[Intersect1]。[ID] AS [C1]
[Intersect1]。[名] AS [C2]
FROM(SELECT
[Extent1]。[ID] AS [ID]
[Extent1]。[名] AS [名]
FROM [DBO]。[文章] AS [Extent1]
相交
选择
[Join1]。[ID] AS [ID]
[Join1]。[名] AS [名]
FROM [DBO]。[PersonInterestTags] AS [Extent2]
INNER JOIN(SELECT [Extent3]。[TAGID] AS [TAGID]
[Extent4]。[ID] AS [ID]
[Extent4]。[名] AS [名]
FROM [DBO]。[PostInterestTags] AS [Extent3]
INNER JOIN [DBO]。[文章] AS [Extent4]
ON [Extent3] [帖子ID] = [Extent4]。[ID])AS [Join1]
ON [Extent2]。[TAGID] = [Join1]。[TAGID]
WHERE 1 = [Extent2]。[PERSONID])AS [Intersect1]
第二个选项:
查询1(此人的标签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)
再投(NULL为int)
ELSE 1
END AS [C1]
FROM(SELECT TOP(2)[Extent1]。[ID] AS [ID]
FROM [DBO]。[人物] 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的最后的帖子:
SELECT
[Extent1]。[ID] AS [ID]
[Extent1]。[名] AS [名]
FROM [DBO]。[文章] AS [Extent1]
在存在(选择
1 AS [C1]
FROM [DBO]。[PostInterestTags] AS [Extent2]
WHERE([Extent1]。[ID] = [Extent2] [帖子ID])AND([Extent2]。[TAGID] IN(1,2,3))
)
在这个例子中,查询1返回(1,2,3),因此(1,2,3)中的查询2的中
子句中
In my database I have the following tables:
- Person
- Post
- InterestTag
Many-Many relationships exist between Person-InterestTag and Post-InterestTag
I need to perform a linq query in EF 4.1 to pull back any post that contains at least one interest tag that matches at least one interest tag related to the given user.
Example
A person has the following interests:
- Cars
- Sports
- Fitness
I need to return any Post that is related to either cars, sports, or fitness.
What is the most efficient way to write this query in terms of performance?
Edit
Running into an error based on the answer given below...
This compiles fine but throws an error at runtime:
var matchingPosts = posts.Where(post => post.Topics.Any(postTopic => person.Interests.Contains(postTopic)));
The error is:
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.
Any ideas how to fix this?
EDIT 2
So my classes are structured as such:
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<InterestTag> 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<InterestTag> InterestTags {get; set;}
}
public class InterestTag
{
public int InterestTagID { get; set; }
public string InterestDescription { get; set; }
public bool Active { get; set; }
public ICollection<Person> Persons { get; set; }
public ICollection<Post> Posts { get; set; }
}
In my Context class I am overriding OnModelCreating to define my DB table names
modelBuilder.Entity<Person>().HasMany(u => u.InterestTags).WithMany(t => t.Persons)
.Map(m =>
{
m.MapLeftKey("PersonID");
m.MapRightKey("InterestTagID");
m.ToTable("PersonInterestTags");
});
modelBuilder.Entity<Post>().HasMany(u => u.InterestTags).WithMany(t => t.Posts)
.Map(m =>
{
m.MapLeftKey("PostID");
m.MapRightKey("InterestTagID");
m.ToTable("PostInterestTags");
});
In my query method I am bring back an IQueryable of Post and applying some filters, including the clause in which I am trying to accomplish in this question.
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)));
If you start with just a given personId
(or userId
) you can do this query in one roundtrip like so:
var posts = context.Posts
.Intersect(context.People
.Where(p => p.Id == givenPersonId)
.SelectMany(p => p.InterestTags.SelectMany(t => t.Posts)))
.ToList();
This translates into an INTERSECT
statement in SQL.
You can also do this in two roundtrips:
var interestTagsOfPerson = context.People.Where(p => p.Id == givenPersonId)
.Select(p => p.InterestTags.Select(t => t.Id))
.SingleOrDefault();
// Result is an IEnumerable<int> 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
Using a list of primitive types in the second query (interestTagsOfPerson
is a collection of int
) also fixes the error you mentioned in your Edit in the question. For Contains
you cannot use object references in LINQ to Entities because EF doesn't know how to translate that into SQL.
I have no clue which of the two approaches is faster (SQL experts might have a better idea) but would probably start to test the first option. (I've tested a little bit and it seemed to return the correct results, but it's the first time that I have used Intersect
.)
Edit
To give an idea of the generated SQL (captured from SQL Profiler):
The first query (with Intersect
) creates this SQL query:
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]
The second option:
Query1 (for the list of the person's tag Ids):
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
Query 2 for the final posts:
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))
)
In this example the query 1 returned (1,2,3), hence the (1,2,3) in the IN
clause in query 2.
这篇关于什么是最有效的方式做到涉及与LINQ多对多的关系,EF 4.1的比较?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!