Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
UPDATE [dbo].[Inventory] SET [PetName] = @p0
WHERE [Id] = @p1 AND [TimeStamp] = @p2;
В следующем тесте демонстрируется пример создания исключения, связанного с параллелизмом, его перехвата и применения Entries для получения исходных значений, текущих значений и значений, которые в настоящий момент хранятся в базе данных. Получение текущих значений требует еще одного обращения к базе данных:
[Fact]
public void ShouldThrowConcurrencyException()
{
ExecuteInATransaction(RunTheTest);
void RunTheTest()
{
var car = Context.Cars.First();
// Обновить базу данных за пределами контекста.
Context.Database.ExecuteSqlInterpolated(
$"Update dbo.Inventory set Color='Pink' where Id = {car.Id}");
car.Color = "Yellow";
var ex = Assert.Throws<CustomConcurrencyException>(
() => Context.SaveChanges());
var entry = ((DbUpdateConcurrencyException) ex.InnerException)?.Entries[0];
PropertyValues originalProps = entry.OriginalValues;
PropertyValues currentProps = entry.CurrentValues;
// Требует еще одного обращения к базе данных.
PropertyValues databaseProps = entry.GetDatabaseValues();
}
}
Ниже показаны выполняемые операторы SQL. Первым из них является оператор UPDATE, а вторым — обращение для получения значений базы данных:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [dbo].[Inventory] SET [Color] = @p0
WHERE [Id] = @p1 AND [TimeStamp] = @p2;
SELECT [TimeStamp]
FROM [dbo].[Inventory]
WHERE @@ROWCOUNT = 1 AND [Id] = @p1;'
,N'@p1 int,@p0 nvarchar(50),@p2 varbinary(8)',@p1=1,@p0=N'Yellow',
@p2=0x0000000000008665
exec sp_executesql N'SELECT TOP(1) [i].[Id], [i].[Color],
[i].[IsDrivable], [i].[MakeId], [i].[PetName], [i].[TimeStamp]
FROM [dbo].[Inventory] AS [i]
WHERE [i].[Id] = @__p_0',N'@__p_0 int',@__p_0=1
Удаление записей
Одиночная сущность помечается для удаления путем вызова Remove() на DbSet<T> или установки ее состояния в Deleted. Список записей помечается для удаления вызовом RemoveRange() на DbSet<T>. Процесс удаления будет вызывать эффекты каскадирования для навигационных свойств на основе правил, сконфигурированных в методе OnModelCreating() (и регламентированных соглашениями EF Core). Если удаление не допускается из -за политики каскадирования, тогда генерируется исключение.
Состояние сущности
Когда метод Remove() вызывается на отслеживаемой сущности, свойство EntityState устанавливается в Deleted. После успешного выполнения оператора удаления сущность исключается из ChangeTracker и состояние изменяется на Detached. Обратите внимание, что сущность по-прежнему существует в вашем приложении, если только она не покинула область видимости и не была подвержена сборке мусора.
Удаление отслеживаемых сущностей
Процесс удаления зеркально отображает процесс обновления. Как только сущность начала отслеживаться, вызовите Remove() на контексте и затем вызовите SaveChanges(), чтобы удалить запись из базы данных:
[Fact]
public void ShouldRemoveACar()
{
ExecuteInATransaction(RunTheTest);
void RunTheTest()
{
var carCount = Context.Cars. Count();
var car = Context.Cars.First(c => c.Id == 2);
Context.Cars.Remove(car);
Context.SaveChanges();
var newCarCount = Context.Cars.Count();
Assert.Equal(carCount - 1, newCarCount);
Assert.Equal(
EntityState.Detached,
Context.Entry(car).State);
}
}
После вызова SaveChanges() экземпляр сущности все еще существует, но больше не находится в ChangeTracker. Состоянием EntityState будет Detached. Вот как выглядит выполняемый код SQL:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [dbo].[Inventory]
WHERE [Id] = @p0 AND [TimeStamp] = @p1;
SELECT @@ROWCOUNT;'
,N'@p0 int,@p1 varbinary(8)',@p0=2,
@p1=0x0000000000008680
Удаление неотслеживаемых сущностей
Неотслеживаемые сущности способны удалять записи таким же способом, каким они могут обновлять записи. Удаление производится вызовом Remove()/RemoveRange() или установкой состояния в Deleted и последующим вызовом SaveChanges().
В показанном ниже тесте сначала читается запись как неотслеживаемая и на основе записи создается новый экземпляр класса Car. Затем либо устанавливается состояние в Deleted, либо применяется метод Remove() класса DbSet<T> (в зависимости от того, какая строка кода закомментирована) и вызывается SaveChanges(). Все дополнительные контексты нужны для обеспечения точности теста и отсутствия пересечения между контекстами:
[Fact]
public void ShouldRemoveACarUsingState()
{
ExecuteInASharedTransaction(RunTheTest);
void RunTheTest(IDbContextTransaction trans)
{
var carCount = Context.Cars.Count();
var car = Context.Cars.AsNoTracking().First(c => c.Id == 2);
var context2 = TestHelpers.GetSecondContext(Context, trans);
// Либо модифицировать состояние, либо вызвать Remove().
context2.Entry(car).State = EntityState.Deleted;
// context2.Cars.Remove(car);
context2.SaveChanges();
var newCarCount = Context.Cars.Count();
Assert.Equal(carCount - 1, newCarCount);
Assert.Equal(
EntityState.Detached,
Context.Entry(car).State);
}
}
Перехват отказов каскадного удаления
Когда попытка удаления записи терпит неудачу из-за правил каскадирования, то исполняющая среда EFCore генерирует исключение DbUpdateException. Следующий тест демонстрирует это в действии:
[Fact]
public void ShouldFailToRemoveACar()
{
ExecuteInATransaction(RunTheTest);
void RunTheTest()
{
var car = Context.Cars.First(c => c.Id == 1);
Context.Cars.Remove(car);
Assert.Throws<CustomDbUpdateException>(
()=>Context.SaveChanges());
}
}
Проверка параллелизма
Если сущность имеет свойство TimeStamp, то при удалении также используется проверка параллелизма. Дополнительную информацию ищите в подразделе "Проверка параллелизма" внутри раздела "Обновление записей" ранее в главе.
Резюме
В настоящей главе было закончено построение уровня доступа к данным AutoLot на основе сведений, полученных в предыдущей главе. С помощью инструментов командной строки EF Core вы создали шаблоны сущностей для существующей базы данных, обновили модель до финальной версии, а также создали и применили миграции. Для инкапсуляции доступа к данным вы добавили хранилища. Написанный код инициализации базы данных способен удалять и заново создавать базу данных повторяемым и надежным способом. В заключение готовый уровень доступа к данным главе был протестирован. На этом тема доступа к данным и Entity Framework Core завершена.
Часть VIII
Разработка клиентских приложений для Windows