Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
var car = Context.Cars
.FromSqlInterpolated($"Select * from dbo.Inventory where Id = {carId}")
.Include(x => x.MakeNavigation)
.First();
Механизм трансляции LINQ to SQL объединяет низкоуровневый оператор SQL с остальными операторами LINQ и выполняют следующий запрос:
SELECT TOP(1) [c].[Id], [c].[Color], [c].[IsDrivable], [c].[MakeId],
[c].[PetName], [c].[TimeStamp],
[m].[Id], [m].[Name], [m].[TimeStamp]
FROM (Select * from dbo.Inventory where Id = 1) AS [c]
INNER JOIN [dbo].[Makes] AS [m] ON [c].[MakeId] = [m].[Id]
WHERE [c].[IsDrivable] = CAST(1 AS bit)
Имейте в виду, что есть несколько правил, которые необходимо соблюдать в случае применения низкоуровневых запросов SQL с LINQ.
• Запрос SQL должен возвращать данные для всех свойств сущностного типа.
• Имена столбцов должны совпадать с именами свойств, с которыми они сопоставляются (улучшение по сравнению с версией EF 6, где сопоставления игнорировались).
• Запрос SQL не может содержать связанные данные.
Пакетирование операторов
В EF Core значительно повышена производительность при сохранении изменений в базе данных за счет выполнения операторов в одном и более пакетов. В итоге объем взаимодействия между приложением и базой данных уменьшается, увеличивая производительность и потенциально сокращая затраты (скажем, для облачных баз данных, где за транзакции приходится платить).
Исполняющая среда EF Core пакетирует операторы создания, обновления и удаления с использованием табличных параметров. Количество операторов, которые пакетирует EF Core, зависит от поставщика баз данных. Например, в SQL Server пакетарование неэффективно для менее 4 и более 40 операторов. Независимо от количества пакетов все операторы по- прежнему выполняются в рамках транзакции. Размер пакета можно конфигурировать посредством DbContextOptions, но в большинстве ситуаций (если не во всех) рекомендуется позволять EF Core рассчитывать размер пакета самостоятельно.
Если бы вы вставляли четыре записи об автомобилях в одной транзакции, как показано ниже:
var cars = new List<Car>
{
new Car { Color = "Yellow", MakeId = 1, PetName = "Herbie" },
new Car { Color = "White", MakeId = 2, PetName = "Mach 5" },
new Car { Color = "Pink", MakeId = 3, PetName = "Avon" },
new Car { Color = "Blue", MakeId = 4, PetName = "Blueberry" },
};
Context.Cars.AddRange(cars);
Context.SaveChanges();
то исполняющая среда EF Core пакетировала бы операторы в одиночное обращение.
Вот как выглядел бы сгенерированный запрос:
exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Dbo].[Inventory] USING (
VALUES (@p0, @p1, @p2, 0),
(@p3, @p4, @p5, 1),
(@p6, @p7, @p8, 2),
(@p9, @p10, @p11, 3)) AS i ([Color], [MakeId], [PetName], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Color], [MakeId], [PetName])
VALUES (i.[Color], i.[MakeId], i.[PetName])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;
SELECT [t].[Id], [t].[IsDrivable], [t].[TimeStamp] FROM [Dbo].[Inventory] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];
',N'@p0 nvarchar(50),@p1 int,@p2 nvarchar(50),@p3 nvarchar(50),
@p4 int,@p5 nvarchar(50),@p6 nvarchar(50),@p7 int,@p8 nvarchar(50),
@p9 nvarchar(50),@p10 int,@p11 nvarchar(50)',@p0=N'Yellow',@p1=1,
@p2=N'Herbie',@p3=N'White',@p4=2,@p5=N'Mach 5',@p6=N'Pink',@p7=3,
@p8=N'Avon',@p9=N'Blue',@p10=4,@p11=N'Blueberry'
Принадлежащие сущностные типы
Возможность применения класса C# в качестве свойства сущности с целью определения коллекции свойств для другой сущности впервые появилась в версии EF Core 2.0 и в последующих версиях постоянно обновлялась. Когда типы, помеченные атрибутом [Owned] или сконфигурированные посредством Fluent API, добавлены в виде свойств сущности, инфраструктура EF Core добавит все свойства из сущностного класса [Owned] к владеющему классу. В итоге увеличивается вероятность многократного использования кода С#.
"За кулисами" EF Core считает результат отношением "один к одному". Принадлежащий класс является зависимой сущностью, а владеющий класс — главной сущностью. Хотя принадлежащий класс рассматривается как сущность, он не может существовать без владеющего класса. Имена столбцов из принадлежащего класса по умолчанию получают формат ИмяНавигационногоСвойства_ИмяСвойстваПринадлежащейСущности (например, PersonalNavigation_FirstName). Стандартные имена можно изменять с применением Fluent API.
Взгляните на приведенный далее класс Person (обратите внимание на атрибут [Owned]):
[Owned]
public class Person
{
[Required, StringLength(50)]
public string FirstName { get; set; } = "New";
[Required, StringLength(50)]
public string LastName { get; set; } = "Customer";
}
Он используется классом Customer:
[Table("Customers", Schema = "Dbo")]
public partial class Customer : BaseEntity
{
public Person PersonalInformation { get; set; } = new Person();
[JsonIgnore]
[InverseProperty(nameof(CreditRisk.CustomerNavigation))]
public IEnumerable<CreditRisk> CreditRisks { get; set; } =
new List<CreditRisk>();
[JsonIgnore]
[InverseProperty(nameof(Order.CustomerNavigation))]
public IEnumerable<Order> Orders { get; set; } = new List<Order>();
}
По умолчанию два свойства Person отображаются на столбцы с именами PersonalInformation_FirstName и PersonalInformation_LastName. Чтобы изменить это, добавьте в метод OnConfiguring() следующий код Fluent API:
modelBuilder.Entity<Customer>(entity =>
{
entity.OwnsOne(o => o.PersonalInformation,
pd =>
{
pd.Property<string>(nameof(Person.FirstName))
.HasColumnName(nameof(Person.FirstName))
.HasColumnType("nvarchar(50)");
pd.Property<string>(nameof(Person.LastName))
.HasColumnName(nameof(Person.LastName))
.HasColumnType("nvarchar(50)");
});
});
Вот как будет создаваться результирующая таблица (обратите внимание, что допустимость значений null в столбцах FirstName и LastName не соответствует аннотациям данных в принадлежащей сущности Person):
CREATE TABLE [dbo].[Customers](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [nvarchar](50) NULL,
[LastName] [nvarchar](50) NULL,
[TimeStamp] [timestamp] NULL,
[FullName] AS (([LastName]+', ')+[FirstName]),
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON,
OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
Проблема с принадлежащими сущностями, которая может быть не видна вам, но приводить к сложной ситуации, в версии EF Core 5 устранена. Легко заметить, что класс Person содержит аннотации данных Required для обоих своих свойств, но оба столбца SQL Server определены как допускающие NULL.Так происходит из-за