简体   繁体   English

如何在EF中使用子查询编写T-SQL多对多

[英]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/CategoryCategoryListings之间有关系吗? 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. 如果需要ListingCategory所有属性,则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 . 您的案例不起作用,因为itemsTempCategories的集合,而每个item1Category ,当然不能将其转换为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 and Categories . 我有一个DbContext (至少)具有ListingsCategories I want all Listings with their Categories 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 one Category 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 . 在我看来,您的itemsListings 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM