【问题标题】:How to write T-SQL many-to-many with subquery in EF如何在 EF 中使用子查询编写 T-SQL 多对多
【发布时间】:2018-10-21 22:28:24
【问题描述】:

我在 ASP.NET EF 应用程序中有两个具有多对多关系的类。我正在尝试查找所有Listings,其中包含从视图发布的任何Categories。类别是视图表单上的复选框。

这些是具有简化导航属性的类,例如:

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; }        
}

这是我试图在我的 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;

        }

在 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)

如何使用导航器或 lambda 在 EF 中编写该 SQL 查询? SQL 中的子查询将更改每次搜索,并且可以是任何 id 或 ID。

【问题讨论】:

  • 你试过嵌套Anys吗?类似Listing.Categories.Any(x =&gt; listIds.Any(y =&gt; x.Id = y))..
  • @BagusTesa 我用var selectedItems = listings.Any(x =&gt; model.Categories.Any(y =&gt; x.ID == y.ID)); 试过这个,它只返回true。我需要一组列表实体,我该如何获得?
  • 啊,snap,应该是Listing.Categories.Where(x =&gt; listIds.Any(y =&gt; x.Id = y)) 我不应该在没有完全清醒的时候写建议。请注意listIds 是您想要获得的类别ID 的ListlistIds.Any(y =&gt; x.Id == y) 将转换为 x.id in (select id from Categories where id =1 or id=3)
  • 也许我没有正确阅读它,但您的示例来自Listing,对吗?我的示例将从Listings 的集合中调用,那么它将如何工作?类似IEnumerable listings = "all listings"; `listings.Categories.Where(x => listIds.Any(y => x.Id = y));
  • dbContext.Listing.Where(x =&gt; listIds.Any(y =&gt; x.Categories.Id = y))我的坏

标签: c# sql-server entity-framework linq entity-framework-6


【解决方案1】:

您忘记告诉我们您的收藏中有哪些物品items。我认为他们是Listings。你的情况不行,因为itemsTempCategories的集合,每个item1都是Category,当然不能转换成Listing

建议:调试转换问题,替换单词var 与您实际期望的类型。编译器会警告你 类型不正确。还要在 lambda 表达式中使用正确的标识符。 这使它们更易于阅读

 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

我们稍后会回到这段代码。

如果我查看您的 SQL,您似乎需要以下内容:

我有一个DbContext,(至少)有ListingsCategories。 我想要所有 Listings 和他们的 Categories 类别 ID 1 或 3

很高兴看到您关注了entity framework code-first conventions,但是您忘记声明您的收藏虚拟

在实体框架中,表中的列由 非虚拟属性。虚拟属性表示关系 桌子之间。

只需稍作更改,实体框架即可自动检测到您的多对多关系。注意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;}
}

还有DbContext

public MyDbContext : DbContext
{
    public DbSet<Listing> Listings {get; set;}
    public DbSet<Category> Categories {get; set;}
}

虽然关系数据库实现了与联结表的多对多关系,但您无需在DbContext 中声明它。实体框架检测到您要设计多对多并为您创建联结表。

但我如何在不访问联结表的情况下执行联接?

回答:不要加入,使用ICollections

Entity Framework 知道需要哪些内部连接,并将为您执行连接。

回到你的 SQL 代码:

给我所有Listings 的所有(或部分)属性,这些属性至少有一个Category,ID 等于1 或3

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());

数据库查询中较慢的部分之一是将所选数据从 DBMS 传输到本地进程。因此,明智的做法是仅转移您实际计划使用的属性。例如,您不需要一对多关系的外键,您知道它等于一对多中 one 部分的 Id 值。

返回您的代码

在我看来,您的itemsListings。在这种情况下,您的代码需要所有Listings 至少启用一个Category

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());

【讨论】:

  • 正确,itemsListing 对象的集合。在您发布此内容之前,我以另一种方式工作。我使用了您的一些建议来改进我的代码。
【解决方案2】:

Listing/CategoryCategoryListings 之间有关系吗? 以下是 EF 6 的示例:http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx

如果你有它,查询会很简单,就像这样:

CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId)) .Select(x => new {x.ListingRefId, x.CategoryRefId});

如果您需要ListingCategory 的所有属性,Include() 扩展会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-13
    • 1970-01-01
    相关资源
    最近更新 更多