Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
EF Core обеспечивает большую гибкость при сопоставлении типов сущностей с таблицами в базе данных. Это становится еще более полезным, если необходимо использовать базу данных, которая не была создана EF.
Приведенные ниже методы описаны в таблицах, но при сопоставлении с представлениями также можно добиться того же результата.
Разделение таблицы
EF Core позволяет сопоставить две или несколько сущностей с одной строкой данных. Это называется разделением таблиц или общим доступом к таблицам.
Конфигурация
Чтобы использовать разделение таблицы, типы сущностей необходимо сопоставить с той же таблицей, их первичные ключи должны быть сопоставлены с теми же столбцами и должна быть по крайней мере одна связь, настроенная между первичным ключом одного типа сущности и другого в той же таблице.
Распространенный сценарий разделения таблиц использует только подмножество столбцов в таблице для повышения производительности или инкапсуляции.
В этом примере Order
представляет подмножество DetailedOrder
.
public class Order
{
public int Id { get; set; }
public OrderStatus? Status { get; set; }
public DetailedOrder DetailedOrder { get; set; }
}
public class DetailedOrder
{
public int Id { get; set; }
public OrderStatus? Status { get; set; }
public string BillingAddress { get; set; }
public string ShippingAddress { get; set; }
public byte[] Version { get; set; }
}
Помимо обязательной конфигурации, мы используем Property(o => o.Status).HasColumnName("Status")
, чтобы сопоставить DetailedOrder.Status
с тем же столбцом, что и Order.Status
.
modelBuilder.Entity<DetailedOrder>(
dob =>
{
dob.ToTable("Orders");
dob.Property(o => o.Status).HasColumnName("Status");
});
modelBuilder.Entity<Order>(
ob =>
{
ob.ToTable("Orders");
ob.Property(o => o.Status).HasColumnName("Status");
ob.HasOne(o => o.DetailedOrder).WithOne()
.HasForeignKey<DetailedOrder>(o => o.Id);
ob.Navigation(o => o.DetailedOrder).IsRequired();
});
Подсказка
Дополнительные сведения см. в полном примере проекта .
Использование
Сохранение и запрос сущностей с помощью разделения таблиц выполняется так же, как и другие сущности:
using (var context = new TableSplittingContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
context.Add(
new Order
{
Status = OrderStatus.Pending,
DetailedOrder = new DetailedOrder
{
Status = OrderStatus.Pending,
ShippingAddress = "221 B Baker St, London",
BillingAddress = "11 Wall Street, New York"
}
});
await context.SaveChangesAsync();
}
using (var context = new TableSplittingContext())
{
var pendingCount = await context.Orders.CountAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"Current number of pending orders: {pendingCount}");
}
using (var context = new TableSplittingContext())
{
var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.ShippingAddress}");
}
Необязательная зависимая сущность
Если все столбцы, используемые зависимой сущностью, находятся NULL
в базе данных, то при выполнении запроса экземпляр этой сущности не будет создан. Это позволяет моделировать необязательную зависимую сущность, где свойство связи в главном объекте будет иметь значение null. Обратите внимание, что это также произойдет, если все свойства зависимого являются необязательными и заданы на null
, что может быть неожиданным.
Однако дополнительная проверка может повлиять на производительность запросов. Кроме того, если зависимый тип сущности имеет собственные зависимости, то определить, нужно ли создавать экземпляр, становится нетривиальной задачей. Чтобы избежать этих проблем, тип зависимой сущности можно пометить как обязательный, дополнительные сведения см. в разделе "Обязательные зависимые от одного к одному ".
Маркеры параллелизма
Если любой из типов сущностей, которые разделяют таблицу, имеет маркер параллелизма, то он должен быть включен и в остальные типы сущностей. Это необходимо, чтобы избежать устаревшего значения маркера параллелизма, если обновляется только одна из сущностей, сопоставленных с той же таблицей.
Чтобы избежать предоставления маркера параллелизма в потребляемом коде, можно создать его как теневое свойство:
modelBuilder.Entity<Order>()
.Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");
modelBuilder.Entity<DetailedOrder>()
.Property(o => o.Version).IsRowVersion().HasColumnName("Version");
Наследство
Прежде чем продолжить работу с этим разделом, рекомендуется прочитать посвященную наследованию страницу.
Зависимые типы, использующие разделение таблиц, могут иметь иерархию наследования, но существуют некоторые ограничения:
- Тип зависимой сущности не может использовать сопоставление TPC, так как производные типы не смогут сопоставить с той же таблицей.
- Тип зависимой сущности может использовать сопоставление TPT, но только тип корневой сущности может использовать разделение таблиц.
- Если основной тип сущности использует TPC, то только типы сущностей, у которых нет потомков, могут использовать разделение таблиц. В противном случае зависимые столбцы должны быть дублируются в таблицах, соответствующих производным типам, что усложняет все взаимодействия.
Разбиение сущностей
EF Core позволяет сопоставить сущность с строками в двух или более таблицах. Это называется разделением сущностей.
Конфигурация
Например, рассмотрим базу данных с тремя таблицами, в которые хранятся данные клиента:
- Таблица с информацией о клиенте
Customers
-
PhoneNumbers
Таблица с номером телефона клиента -
Addresses
Адресная таблица клиента
Ниже приведены определения для этих таблиц в SQL Server:
CREATE TABLE [Customers] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
CREATE TABLE [PhoneNumbers] (
[CustomerId] int NOT NULL,
[PhoneNumber] nvarchar(max) NULL,
CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY ([CustomerId]),
CONSTRAINT [FK_PhoneNumbers_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Addresses] (
[CustomerId] int NOT NULL,
[Street] nvarchar(max) NOT NULL,
[City] nvarchar(max) NOT NULL,
[PostCode] nvarchar(max) NULL,
[Country] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Addresses] PRIMARY KEY ([CustomerId]),
CONSTRAINT [FK_Addresses_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);
Каждая из этих таблиц обычно сопоставляется с собственным типом сущности с связями между типами. Однако если все три таблицы всегда используются вместе, то их можно сопоставить с одним типом сущности. Рассмотрим пример.
public class Customer
{
public Customer(string name, string street, string city, string? postCode, string country)
{
Name = name;
Street = street;
City = city;
PostCode = postCode;
Country = country;
}
public int Id { get; set; }
public string Name { get; set; }
public string? PhoneNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string? PostCode { get; set; }
public string Country { get; set; }
}
Это достигается в EF7 путем вызова SplitToTable
для каждого разделения в типе сущности. Например, следующий код разбивает Customer
тип сущности на Customers
PhoneNumbers
Addresses
и таблицы, показанные выше:
modelBuilder.Entity<Customer>(
entityBuilder =>
{
entityBuilder
.ToTable("Customers")
.SplitToTable(
"PhoneNumbers",
tableBuilder =>
{
tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
tableBuilder.Property(customer => customer.PhoneNumber);
})
.SplitToTable(
"Addresses",
tableBuilder =>
{
tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
tableBuilder.Property(customer => customer.Street);
tableBuilder.Property(customer => customer.City);
tableBuilder.Property(customer => customer.PostCode);
tableBuilder.Property(customer => customer.Country);
});
});
Обратите внимание, что при необходимости для каждой таблицы можно указать разные имена столбцов. Чтобы настроить название столбца для основной таблицы, см. конфигурацию аспектов, относящихся к таблице.
Настройка связывающего внешнего ключа
FK, связывающие сопоставленные таблицы, нацелены на те же свойства, на которые они объявлены. Обычно он не будет создан в базе данных, так как он будет избыточным. Но существует исключение, если тип сущности сопоставляется с несколькими таблицами. Чтобы изменить аспекты отношений, можно использовать Fluent API для конфигурации отношений:
modelBuilder.Entity<Customer>()
.HasOne<Customer>()
.WithOne()
.HasForeignKey<Customer>(a => a.Id)
.OnDelete(DeleteBehavior.Restrict);
Ограничения
- Разделение сущностей нельзя использовать для типов сущностей в иерархиях.
- Для любой строки в главной таблице должна быть строка в каждой из разделенных таблиц (фрагменты не являются необязательными).
Конфигурация граней для конкретной таблицы
Некоторые шаблоны сопоставления приводят к тому, что одно и то же свойство CLR сопоставляется с столбцом в каждой из нескольких разных таблиц. EF7 позволяет этим столбцам иметь разные имена. Например, рассмотрим простую иерархию наследования:
public abstract class Animal
{
public int Id { get; set; }
public string Breed { get; set; } = null!;
}
public class Cat : Animal
{
public string? EducationalLevel { get; set; }
}
public class Dog : Animal
{
public string? FavoriteToy { get; set; }
}
С использованием стратегии сопоставления наследования TPT эти типы будут отображены в трех таблицах. Однако столбец первичного ключа в каждой таблице может иметь другое имя. Рассмотрим пример.
CREATE TABLE [Animals] (
[Id] int NOT NULL IDENTITY,
[Breed] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);
CREATE TABLE [Cats] (
[CatId] int NOT NULL,
[EducationalLevel] nvarchar(max) NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId]),
CONSTRAINT [FK_Cats_Animals_CatId] FOREIGN KEY ([CatId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [Dogs] (
[DogId] int NOT NULL,
[FavoriteToy] nvarchar(max) NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId]),
CONSTRAINT [FK_Dogs_Animals_DogId] FOREIGN KEY ([DogId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);
EF7 позволяет настроить это сопоставление с помощью вложенного построителя таблиц:
modelBuilder.Entity<Animal>().ToTable("Animals");
modelBuilder.Entity<Cat>()
.ToTable(
"Cats",
tableBuilder => tableBuilder.Property(cat => cat.Id).HasColumnName("CatId"));
modelBuilder.Entity<Dog>()
.ToTable(
"Dogs",
tableBuilder => tableBuilder.Property(dog => dog.Id).HasColumnName("DogId"));
При сопоставлении наследования TPC свойство Breed
можно также сопоставить с разными именами столбцов в различных таблицах. Например, рассмотрим следующие таблицы TPC:
CREATE TABLE [Cats] (
[CatId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[CatBreed] nvarchar(max) NOT NULL,
[EducationalLevel] nvarchar(max) NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId])
);
CREATE TABLE [Dogs] (
[DogId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[DogBreed] nvarchar(max) NOT NULL,
[FavoriteToy] nvarchar(max) NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId])
);
EF7 поддерживает это сопоставление таблиц:
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
modelBuilder.Entity<Cat>()
.ToTable(
"Cats",
builder =>
{
builder.Property(cat => cat.Id).HasColumnName("CatId");
builder.Property(cat => cat.Breed).HasColumnName("CatBreed");
});
modelBuilder.Entity<Dog>()
.ToTable(
"Dogs",
builder =>
{
builder.Property(dog => dog.Id).HasColumnName("DogId");
builder.Property(dog => dog.Breed).HasColumnName("DogBreed");
});