Операции с множествами: объединение, пересечение, разность
Ряд методов Linq позволяют работать с результатами выборки как со множествами, производя операции на объединение, пересечение, разность двух выборок.
Но перед использованием данных методов надо учитывать, что они проводятся над однородными выборками с одинаковым определением строк, то есть которые совпадают по составу столбцов.
Для примеров возьмем модели из прошлых тем:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; } = new List<User>();
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
public class ApplicationContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
}
}
На примере следующих данных:
using (ApplicationContext db = new ApplicationContext())
{
// пересоздаем базу данных
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
Company microsoft = new Company { Name = "Microsoft"};
Company google = new Company { Name = "Google"};
db.Companies.AddRange(microsoft, google);
User tom = new User { Name = "Tom", Age = 36, Company = microsoft };
User bob = new User { Name = "Bob", Age = 39, Company = google };
User alice = new User { Name = "Alice", Age = 28, Company = microsoft };
User kate = new User { Name = "Kate", Age = 25, Company = google };
db.Users.AddRange(tom, bob, alice, kate);
db.SaveChanges();
}
Объединение
Для объединения двух выборок используется метод Union():
using(ApplicationContext db = new ApplicationContext())
{
var users = db.Users.Where(u => u.Age < 30)
.Union(db.Users.Where(u=>u.Name.Contains("Tom")));
foreach (var user in users)
Console.WriteLine(user.Name);
}
Консольный вывод:
Tom Alice Kate
Метод Union в качестве параметра принимает результаты второй выборки и объединяет ее с исходной. В результате мы получим два подзапроса SQL, результаты которых объединяются в общий набор:
SELECT [u].[Id], [u].[Age], [u].[CompanyId], [u].[Name]
FROM [Users] AS [u]
WHERE [u].[Age] < 30
UNION
SELECT [u0].[Id], [u0].[Age], [u0].[CompanyId], [u0].[Name]
FROM [Users] AS [u0]
WHERE [u0].[Name] LIKE N'%Tom%'
При этом мы не можем объединить две разнородные выборки, например, таблицу, пользователей и таблицу компаний. Однако уместна следующая запись:
var result = db.Users.Select(p => new { Name = p.Name })
.Union(db.Companies.Select(c => new { Name = c.Name }));
Первая выборка после метода Select будет формировать набор элементов с одним столбцом Name. Вторая выборка из таблицы компаний после метода Select также будет формировать набор элементов с одним столбцом Name. Поэтому строки в обоих выборках будут однородны, и мы их сможем объединять.
Пересечение
Чтобы найти пересечение выборок, то есть те элементы, которые присутствуют сразу в двух выборках, используется метод Intersect(). Например, выберем все пользователей, у которых возраст больше 30 и в имени содержится подстрока "Tom":
using(ApplicationContext db = new ApplicationContext())
{
var users = db.Users.Where(u => u.Age > 30)
.Intersect(db.Users.Where(u=>u.Name.Contains("Tom")));
foreach (var user in users)
Console.WriteLine(user.Name);
}
Консольный вывод:
Tom
Несмотря на то, что операция пересечения не равна операции объединения, рассмотренной выше, но при выполнении пересечение будет выполнять те же SQL-выражения:
SELECT [u].[Id], [u].[Age], [u].[CompanyId], [u].[Name]
FROM [Users] AS [u]
WHERE [u].[Age] > 30
INTERSECT
SELECT [u0].[Id], [u0].[Age], [u0].[CompanyId], [u0].[Name]
FROM [Users] AS [u0]
WHERE [u0].[Name] LIKE N'%Tom%'
Разность
Если нам надо найти элементы первой выборки, которые отсутствуют во второй выборке, то мы можем использовать метод Except:
using(ApplicationContext db = new ApplicationContext())
{
var selector1 = db.Users.Where(u => u.Age > 30); //
var selector2 = db.Users.Where(u => u.Name.Contains("Tom")); // Samsung Galaxy S8, Samsung Galaxy S7
var users = selector1.Except(selector2); // результат - iUser 6S
foreach (var user in users)
Console.WriteLine(user.Name);
}
Bob
В результате будет формироваться следующий SQL-запрос:
SELECT [u].[Id], [u].[Age], [u].[CompanyId], [u].[Name]
FROM [Users] AS [u]
WHERE [u].[Age] > 30
EXCEPT
SELECT [u0].[Id], [u0].[Age], [u0].[CompanyId], [u0].[Name]
FROM [Users] AS [u0]
WHERE [u0].[Name] LIKE N'%Tom%'
Только полноправные пользователи могут оставлять комментарии. Аутентифицируйтесь пожалуйста, используя сервисы.