[英]How to write T-SQL many-to-many with subquery in EF
I have two classes with a many-to-many relationship in a ASP.NET EF application. 我在ASP.NET EF应用程序中有两个具有多对多关系的类。 I'm trying to find all
Listings
that have any Categories
which is posted from a view. 我正在尝试查找具有从视图发布的任何
Categories
所有Listings
。 The categories are checkboxes on the view form. 类别是视图表单上的复选框。
These are the classes with navigation properties simplified for example: 这些是简化了导航属性的类,例如:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller
but it doesn't work and I don't really know what else to try: 这是我想在我的MVC
Controller
使用的查询,但是它不起作用,我也不知道该尝试什么:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for): 在SQL中,我将使用子查询来编写此代码(子查询代表可以搜索的多个类别):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? 如何使用导航器或lambda在EF中编写该SQL查询? The subquery in the SQL will change each search and can be any id or IDs.
SQL中的子查询将更改每次搜索,并且可以是任何ID。
Do you have a relation between Listing/Category
and CategoryListings
? Listing/Category
和CategoryListings
之间有关系吗? Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx 这是EF 6的示例: http : //www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that: 如果有它,查询将很简单,如下所示:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId)) .Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing
or Category
, Include()
extension will help. 如果需要
Listing
或Category
所有属性,则Include()
扩展名会有所帮助。
You forgot to tell us what objects are in your collection items
. 您忘记告诉我们您的收藏
items
有什么items
。 I think they are Listings
. 我认为它们是
Listings
。 Your case doesn't work, because itemsTemp
is a collection of Categories
, and every item1
is a Category
, which of course can't be cast to a Listing
. 您的案例不起作用,因为
itemsTemp
是Categories
的集合,而每个item1
是Category
,当然不能将其转换为Listing
。
Advice: to debug casting problems, replace the word
var
with the type you actually expect.建议:要调试转换问题,请将
var
替换为您实际期望的类型。 The compiler will warn you about incorrect types.编译器会警告您有关错误类型的信息。 Also use proper identifiers in your lambda expressions.
还要在lambda表达式中使用适当的标识符。 This makes them easier to read
这使它们更易于阅读
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later. 稍后我们将返回此代码。
If I look at your SQL it seems that you want the following: 如果我查看您的SQL,似乎您需要以下内容:
I have a
DbContext
with (at least)Listings
andCategories
.我有一个
DbContext
(至少)具有Listings
和Categories
。 I want allListings
with theirCategories
that have category Id 1 or 3我希望所有
Listings
用自己的Categories
有分类标识1或3
It's good to see that you followed the entity framework code-first conventions , however you forgot to declare your collections virtual : 很高兴看到您遵循实体框架的代码优先约定 ,但是忘记了将集合声明为virtual :
In entity framework the columns in a table are represented by non-virtual properties.
在实体框架中,表中的列由非虚拟属性表示。 The virtual properties represent the relations between the table.
虚拟属性表示表之间的关系。
With a slight change your many-to-many relation can be detected automatically by entity framework. 稍作更改,实体框架即可自动检测到多对多关系。 Note the
virtual
before the ICollection
注意
ICollection
之前的virtual
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
还有
DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext
. 尽管关系数据库通过联结表实现了多对多关系,但您无需在
DbContext
声明它。 Entity framework detects that you want to design a many-to-many and creates the junction table for you. 实体框架检测到您要设计多对多并为您创建联结表。
But how can I perform my joins without access to the junction table? 但是如何在不访问联结表的情况下执行联接?
Answer: Don't do joins, use the ICollections
! 答:不要加入,请使用
ICollections
!
Entity Framework knows which inner joins are needed and will do the joins for you. Entity Framework知道需要哪些内部联接,并将为您进行联接。
Back to your SQL code: 返回您的SQL代码:
Give me all (or some) properties of all
Listings
that have at least oneCategory
with Id equal to 1 or 3给我所有具有至少一个Id等于1或3的
Category
的所有Listings
(全部)属性
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. 数据库查询的最慢部分之一是将所选数据从DBMS传输到本地进程。 Hence it is wise to only transfer the properties you actually plan to use.
因此,明智的做法是仅转移您实际打算使用的属性。 For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the
one
part in the one-to-many. 例如,你不会需要一个一对多关系的外键,你就知道它等于的ID值
one
部分在一个一对多。
Back to your code 返回您的代码
It seems to me, that your items
are Listings
. 在我看来,您的
items
是Listings
。 In that case your code wants all Listings
that have at least one enabled Category
在这种情况下,您的代码需要具有至少一个已启用
Category
所有Listings
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.