Форум программистов, компьютерный форум, киберфорум
Наши страницы
C#: Базы данных, ADO.NET
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.89/9: Рейтинг темы: голосов - 9, средняя оценка - 4.89
ribastar
4 / 4 / 2
Регистрация: 29.10.2015
Сообщений: 73
1

LINQ-запрос при отношении "многие-ко-многим" (Entity Framework)

26.12.2016, 22:15. Просмотров 1717. Ответов 10
Метки нет (Все метки)

Добрый день! Второй вечер подряд не могу понять как в случае EF и Linq To Entites составить запрос при отношении "многие-ко-многим".

Вот, например, есть классический пример, даны 3 таблицы: Продукты, Категории, КатегорииПродукты

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE [dbo].[Products] (
    [ProductId]   INT             IDENTITY (1, 1) NOT NULL PRIMARY KEY CLUSTERED,
    [Name]        NVARCHAR (100)  NOT NULL
);
 
CREATE TABLE [dbo].[Categories] (
    [CategoryId]   INT             IDENTITY (1, 1) NOT NULL PRIMARY KEY CLUSTERED,
    [ParentCategoryId]   INT ,
    [Name]        NVARCHAR (100)  NOT NULL
)
 
CREATE TABLE [dbo].[ProductsCategories] (
    Products_ProductId INT NOT NULL,
    Categories_CategoryId INT NOT NULL,
 
    CONSTRAINT FK_ProductsCategories_Categories FOREIGN KEY ([Categories_CategoryId])
        REFERENCES dbo.Categories (CategoryId),
    CONSTRAINT FK_ProductsCategories_Products FOREIGN KEY ([Products_ProductId])
        REFERENCES dbo.Products (ProductId)
);

Допустим на входе у меня есть от пользователя запрос на categoryId = 4, как мне из таблицы продуктов выбрать все продукты, которые включены в категорию с CategoryId = 4?
(внешние ключи были и EF через FluentApi автоматически сформировал связь многие ко многим (code frist from database))

Я перепробовал так, но не срабатывает и вылетает:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1.
var catId = 4;
 
var products = DataContext.Set<Product>()
       .Include(p => p.Categories)
       .Where(p => (p.Categories.Select(c => c.CategoryId).Contains(catId)))
       .ToList();
 
// 2.
var catId = 4;
 
var category = DataContext.Set<Category>()
         .Where(c => c.CategoryId == catId)
         .FirstOrDefault();
 
var products = DataContext.Set<Product>()
         .Include(p => p.Categories)
         .Where(p => (p.Categories.Contains(category)))
         .ToList();
Думаю через двойное применение linq-метода .Join(x,y =>{}) удастся это сделать, но выглядеть будет неуклюже, да и навигационные поля мне на что тогда?
Подскажите, пожалуйста, как правильно выбрать продукты, принадлежащие к опред. категории через промежуточную таблицу?
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
26.12.2016, 22:15
Ответы с готовыми решениями:

Entity Framework: не создается промежуточная таблица для связи "многие-ко-многим"
Здравствуйте. Пытаюсь создать базу данных и наполнить ее чем то. База создается и наполняется,...

Entity Framework 6. Code First. Связь один-ко-многим и многие-ко-многим одновременно
Здрасти. Есть таблица юзеров и объявлений. У юзера может быть множество объявлений. Здесь действует...

Связь многие-ко-многим в Entity Framework
делаю задания с моделями сущностей entity framework. на примере студентов и курсов, у каждого...

Связь многие ко многим (Entity Framework)
Мне нужна связь многие ко многим. Нашел вот тут хороший туториал который объясняет как ее сделать...

Entity Framework в модель не добавляется таблица со связью многие ко многим
Всем привет. Проблема в следующем, в модель представления данных Entity Framework не могу добавить...

10
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
26.12.2016, 23:07 2
Так что ли? Если я вас правильно понял.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace ConsoleApplication273
{
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
 
    internal class Program
    {
        private static void Main()
        {
            var categoryId = 4;
 
            using (
                var db =
                    new ApplicationDbContext("Data Source=192.168.1.37;Initial Catalog=FooDb;Integrated Security=SSPI;")
            )
            {
                var query = db.ProductsCategories.Where(n => n.CategoryId == 4).ToList();
            }
        }
    }
 
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class Category
    {
        public int Id { get; set; }
        public int ParentCategoryId { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductsCategory
    {
        public virtual int ProductId { get; set; }
        public virtual Product Product { get; set; }
 
        public virtual int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
 
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
        }
 
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<ProductsCategory> ProductsCategories { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProductsCategory>()
                .HasKey(id => new {id.ProductId, id.CategoryId});
        }
    }
}
0
kol
23 / 9 / 5
Регистрация: 30.01.2015
Сообщений: 175
26.12.2016, 23:37 3
C#
1
2
3
4
List<Product> products = DataContext.Set<Product>
    .Include(p => p.Categories)
    .Where(p => p.Categories.Any(c => c.Id == catId))
    .ToList();
1
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
26.12.2016, 23:41 4
А все я допер что вам надо. Вот готовое решение.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
namespace ConsoleApplication273
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Linq;
    using AutoMapper;
 
    internal class Program
    {
        private static void Main()
        {
            var categoryId = 1;
 
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<ProductsCategory, ProductDTO>()
                    .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Product.Name));
            });
 
 
            using (
                var db =
                    new ApplicationDbContext("Data Source=192.168.1.37;Initial Catalog=FooDb;Integrated Security=SSPI;")
            )
            {
                var query = db.ProductsCategories
                    .Where(n => n.CategoryId == categoryId)
                    .ProjectToList<ProductDTO>();
 
                Debugger.Break();
            }
        }
    }
 
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductDTO
    {
        public string Name { get; set; }
    }
 
    public class Category
    {
        public int Id { get; set; }
        public int ParentCategoryId { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductsCategory
    {
        public virtual int ProductId { get; set; }
        public virtual Product Product { get; set; }
 
        public virtual int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
 
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            Database.Log = Console.WriteLine;
        }
 
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<ProductsCategory> ProductsCategories { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProductsCategory>()
                .HasKey(id => new {id.ProductId, id.CategoryId});
        }
    }
}
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Opened connection at 12/26/2016 11:41:26 PM +03:00
 
SELECT
    [Extent1].[ProductId] AS [ProductId],
    [Extent2].[Name] AS [Name]
    FROM  [dbo].[ProductsCategories] AS [Extent1]
    INNER JOIN [dbo].[Products] AS [Extent2] ON [Extent1].[ProductId] = [Extent2
].[Id]
    WHERE [Extent1].[CategoryId] = @p__linq__0
 
 
-- p__linq__0: '1' (Type = Int32, IsNullable = false)
 
-- Executing at 12/26/2016 11:41:26 PM +03:00
 
-- Completed in 50 ms with result: SqlDataReader
 
 
 
Closed connection at 12/26/2016 11:41:26 PM +03:00
1
Вложения
Тип файла: zip ConsoleApplication273.zip (5.3 Кб, 1 просмотров)
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
26.12.2016, 23:48 5
DelegateDecompiler + EntityFramework
0
ribastar
4 / 4 / 2
Регистрация: 29.10.2015
Сообщений: 73
27.12.2016, 10:42  [ТС] 6
Спасибо! Вечером опробую.
_exp10der_,
а что это у вас за метод указан такой: .ProjectToList<ProductDTO>(); ?
И да, у вас впрочем правильно, только связь у вас два раза один-ко-многим изображена, но этого кстати в случае, когда поиск по categoryId, будет достаточно, но вот если мне нужно выбрать все продукты, у которых категория, например, имеет столбец Alias = 'Apple'? (то есть по айди-то да, достаточно просто в ProductsCategories обратиться, а если по другим столбцам искать категорию? в ProductsCategories ведь только айдишники)

kol,
интересно! про .Any я даже и не думал, в чистом SQL никогда этот оператор не применяю, использую IN или EXISTS, но может тут самое оно, вечером обязат. проверю, как я понял, в этом примере как раз можно по любому столбцу таблицы Categories выполнять отбор, а не только по Id?
0
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
27.12.2016, 11:08 7
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
namespace ManyToManyEF
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Linq;
    using AutoMapper;
 
    internal class Program
    {
        private static void Main()
        {
            var categoryId = 1;
 
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<ProductsCategory, ProductDTO>()
                    .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Product.Name));
            });
 
 
            using (
                var db =
                    new ApplicationDbContext("Data Source=192.168.1.37;Initial Catalog=FooDb;Integrated Security=SSPI;")
            )
            {
 
                List<ProductDTO> productDtos = db.ProductsCategories
                    .Where(n => n.Category.Alias == "Apple")
                    .ProjectToList<ProductDTO>();
 
 
                Debugger.Break();
            }
        }
    }
 
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductDTO
    {
        public string Name { get; set; }
    }
 
    public class Category
    {
        public int Id { get; set; }
        public int ParentCategoryId { get; set; }
        public string Name { get; set; }
        public string Alias { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductsCategory
    {
        public virtual int ProductId { get; set; }
        public virtual Product Product { get; set; }
 
        public virtual int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
 
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            Database.Log = Console.WriteLine;
        }
 
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<ProductsCategory> ProductsCategories { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProductsCategory>()
                .HasKey(id => new {id.ProductId, id.CategoryId});
        }
    }
}
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Opened connection at 12/27/2016 11:04:54 AM +03:00
 
SELECT
    [Extent1].[ProductId] AS [ProductId],
    [Extent3].[Name] AS [Name]
    FROM   [dbo].[ProductsCategories] AS [Extent1]
    INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryId] = [Exte
nt2].[Id]
    INNER JOIN [dbo].[Products] AS [Extent3] ON [Extent1].[ProductId] = [Extent3
].[Id]
    WHERE N'Apple' = [Extent2].[Alias]
 
 
-- Executing at 12/27/2016 11:04:54 AM +03:00
 
-- Completed in 10 ms with result: SqlDataReader
 
 
 
Closed connection at 12/27/2016 11:04:54 AM +03:00
Цитата Сообщение от ribastar Посмотреть сообщение
а что это у вас за метод указан такой: .ProjectToList<ProductDTO>(); ?
Просто для проекции типа из этой либы https://www.nuget.org/packages/AutoMapper.EF6/
Запустите пример и посмотрите он рабочий

А еще лучше открыть документацию по EF
0
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
27.12.2016, 11:16 8
Цитата Сообщение от ribastar Посмотреть сообщение
только связь у вас два раза один-ко-многим изображена
???
0
Миниатюры
LINQ-запрос при отношении "многие-ко-многим" (Entity Framework)  
ribastar
4 / 4 / 2
Регистрация: 29.10.2015
Сообщений: 73
27.12.2016, 11:38  [ТС] 9
Библиотекой AutoMapper.EF6 не пользовался, я вообще думал, что и без библиотек в EF 6 можно все что хочешь делать, с этой библиотекой попроще что ли? Какие плюсы её использования? (EF же включает и FluentApi и аттрибутами можно маппить)


А насчет того, что я сказал, что у вас два раза "один-ко-многим", так это я немного неправильно выразился, так-то да, многие-ко-многим, но я имел ввиду, что у вас навигационное поле в сущности Products такое как "один-ко-многим" EF :
C#
1
2
3
4
5
6
7
8
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
 
    // навигационное поле "один-ко-многим"
    public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
}

а чистый EF в случае "многие-ко-многим" формирует такой класс:

C#
1
2
3
4
5
6
7
8
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
 
    // навигационное поле "многие-ко-многим"
    public virtual ICollection<Category> Categories { get; set; }
}
0
_exp10der_
Warrior
490 / 417 / 177
Регистрация: 23.11.2014
Сообщений: 932
27.12.2016, 22:15 10
Цитата Сообщение от ribastar Посмотреть сообщение
Библиотекой AutoMapper.EF6 не пользовался, я вообще думал, что и без библиотек в EF 6 можно все что хочешь делать, с этой библиотекой попроще что ли? Какие плюсы её использования? (EF же включает и FluentApi и аттрибутами можно маппить)
Всего лишь для проекции я же написал, если вы не знаете что это можете почитать https://habrahabr.ru/post/145381/

Вот тоже самое только entity framework
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
namespace ManyToManyEF
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Linq;
 
    internal class Program
    {
        private static void Main()
        {
            using (
                var db =
                    new ApplicationDbContext("Data Source=192.168.1.37;Initial Catalog=FooDb;Integrated Security=SSPI;")
            )
            {
                var productDtos = db.ProductsCategories
                    .Where(n => n.Category.Alias == "Apple")
                    .Select(n => new
                    {
                        n.Product.Id,
                        n.Product.Name
                    }).ToList();
 
 
                Debugger.Break();
            }
        }
    }
 
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class Category
    {
        public int Id { get; set; }
        public int ParentCategoryId { get; set; }
        public string Name { get; set; }
        public string Alias { get; set; }
 
        public virtual ICollection<ProductsCategory> ProductsCategories { get; set; }
    }
 
    public class ProductsCategory
    {
        public virtual int ProductId { get; set; }
        public virtual Product Product { get; set; }
 
        public virtual int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
 
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            Database.Log = Console.WriteLine;
        }
 
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<ProductsCategory> ProductsCategories { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ProductsCategory>()
                .HasKey(id => new {id.ProductId, id.CategoryId});
        }
    }
}
Цитата Сообщение от ribastar Посмотреть сообщение
А насчет того, что я сказал, что у вас два раза "один-ко-многим", так это я немного неправильно выразился, так-то да, многие-ко-многим, но я имел ввиду, что у вас навигационное поле в сущности Products такое как "один-ко-многим" EF
обычно так и делают как я
вот первая ссылка в гугле
посмотрите чуть ниже пример https://docs.microsoft.com/en-us/ef/...nship-patterns
Many-to-many
1
ribastar
4 / 4 / 2
Регистрация: 29.10.2015
Сообщений: 73
28.12.2016, 14:14  [ТС] 11
Ну вообще выглядит нормально, просто я и не подумал что надо плясать от ProductsCategories, а не от Products, да и EF создал мне почему-то навигационное поле другого типа, ну да ладно, переделаю тогда, спасибо большое!
Хотя также попробую и другой предложенный метод ( .Where(p => p.Categories.Any(c => c.Id == catId)))

А про реляционную алгебру надо почитать будет.
0
28.12.2016, 14:14
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
28.12.2016, 14:14

DataSet, выборка данных в таблицах со связью "многие ко многим"
На рис. показана схема БД. С БД работаю через DataSet. По сути, организовал 2 таблицы со связью...

Связь "Многие ко многим"
подскажите как организовать связку один ко многим и многие ко многим сколько не рыл студию и sql...

Entity Framework. SQL Exception "Invalid column name"
Здравствуйте, мне надо создать базу данных, используя Entity Framework В базу надо записать...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
11
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru