Шрифт:
Интервал:
Закладка:
context2.Entry(updatedCar).State = EntityState.Modified;
В любом случае для сохранения значений все равно должен вызываться метод SaveChanges().
В представленном далее тесте читается неотслеживаемая запись, из нее создается новый экземпляр класса Car и изменяется одно его свойство (Color). Затем в зависимости от того, с какой строки кода вы уберете комментарий, либо устанавливается состояние, либо использует метод Update() на DbSet<T>. Метод Update() также изменяет состояние на Modified. Затем в тесте вызывается метод SaveChanges(). Все дополнительные контексты нужны для обеспечения точности теста и отсутствия пересечения между контекстами:
[Fact]
public void ShouldUpdateACarUsingState()
{
ExecuteInASharedTransaction(RunTheTest);
void RunTheTest(IDbContextTransaction trans)
{
var car = Context.Cars.AsNoTracking().First(c => c.Id == 1);
Assert.Equal("Black", car.Color);
var updatedCar = new Car
{
Color = "White", //Original is Black
Id = car.Id,
MakeId = car.MakeId,
PetName = car.PetName,
TimeStamp = car.TimeStamp
IsDrivable = car.IsDrivable
};
var context2 = TestHelpers.GetSecondContext(Context, trans);
// Либо вызвать Update(), либо модифицировать состояние.
context2.Entry(updatedCar).State = EntityState.Modified;
// context2.Cars.Update(updatedCar);
context2.SaveChanges();
var context3 =
TestHelpers.GetSecondContext(Context, trans);
var car2 = context3.Cars.First(c => c.Id == 1);
Assert.Equal("White", car2.Color);
}
}
Ниже показан выполняющийся оператор SQL:
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'White',
(window.adrunTag = window.adrunTag || []).push({v: 1, el: 'adrun-4-390', c: 4, b: 390})@p2=0x000000000000862D
Проверка параллелизма
Проверка параллелизма подробно обсуждалась в предыдущей главе. Вспомните, что когда внутри сущности определено свойство TimeStamp, то значение этого свойства используется в конструкции WHERE при сохранении изменений (обновлений или удалений) в базе данных. Вместо поиска только первичного ключа к запросу добавляется поиск значения TimeStamp, например:
UPDATE [dbo].[Inventory] SET [PetName] = @p0
WHERE [Id] = @p1 AND <b>[TimeStamp] = @p2</b>;
В следующем тесте демонстрируется пример создания исключения, связанного с параллелизмом, его перехвата и применения 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',
- Понимание SQL - Мартин Грубер - Базы данных