Как написать T-SQL многие-ко-многим с подзапросом в EF - PullRequest
0 голосов
/ 22 октября 2018

У меня есть два класса с отношением «многие ко многим» в приложении 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)

Как мне написать этот SQL-запрос в EF, используя навигаторы или лямбду?Подзапрос в SQL изменит каждый поиск и может быть любым идентификатором или идентификаторами.

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

Вы забыли сообщить нам, какие объекты находятся в вашей коллекции items.Я думаю, что они Listings.Ваш случай не работает, потому что itemsTemp - это набор Categories, а каждый item1 - это Category, что, конечно, не может быть приведено к Listing.

.

Совет: для устранения проблем с кастингом замените слово var на тип, который вы на самом деле ожидаете.Компилятор предупредит вас о неверных типах.Также используйте правильные идентификаторы в ваших лямбда-выражениях.Это облегчает их чтение

 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 с (как минимум) Listings и Categories.Я хочу, чтобы все Listings с их Categories, которые имели Id категории 1 или 3

Приятно видеть, что вы следовали соглашениям о коде в начале структуры лица , однако вызабыл объявить ваши коллекции virtual :

В каркасе сущностей столбцы в таблице представлены не виртуальными свойствами.Виртуальные свойства представляют отношения между таблицей.

С небольшим изменением ваше отношение «многие ко многим» может быть автоматически определено структурой сущностей.Обратите внимание на virtual перед ICollection

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.Entity Framework обнаруживает, что вы хотите спроектировать «многие ко многим», и создает для вас соединительную таблицу.

Но как я могу выполнить свои объединения без доступа к соединительной таблице?

Ответ: Не делайте объединений, используйте 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());

Одна из более медленных частей запросов к базе данных - передача выбранных данных из СУБД в локальный процесс.Следовательно, целесообразно передавать только те свойства, которые вы действительно планируете использовать.Например, вам не понадобятся внешние ключи отношений «один ко многим», вы знаете, что они равны значению Id части one в «один ко многим».

Назадна ваш код

Мне кажется, что ваши items равны Listings.В этом случае ваш код хочет, чтобы все 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());
0 голосов
/ 22 октября 2018

Есть ли у вас связь между Listing/Category и CategoryListings?Вот пример для 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});

Если вам нужны все свойстваListing или Category, расширение Include() поможет.

...