Как в EF6, так и в EF-core, при работе с Sql Server вы должны использовать это сопоставление:
modelBuilder.Entity<Product>()
.Property(t => t.RowVersion)
.IsRowVersion(); // Not: IsConcurrencyToken
IsConcurrencyToken настраивает свойство как токен параллелизма, но (при использовании его для byte[]
)
varbinary(max)
null
, если вы его не инициализируете IsRowVersion , с другой стороны,
rowversion
(в Sql Server или timestamp
в более ранних версиях), поэтому Теперь, когда вы обновляете Car
, вы увидите два оператора обновления:
DECLARE @p int
UPDATE [dbo].[Product]
SET @p = 0
WHERE (([Id] = @0) AND ([Rowversion] = @1))
SELECT [Rowversion]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = @0
UPDATE [dbo].[Car]
SET ...
Первый оператор ничего не обновляет, но увеличивает приращение rowversion и генерирует исключение параллелизма, если переменная rowversion была изменена между ними.
[System.ComponentModel.DataAnnotations.Schema.Timestamp]
ttribute - это аннотации данных, эквивалентные IsRowVersion()
:
[Timestamp]
public byte[] RowVersion { get; set; }
Проблема заключается не в настройке. Случается, что OriginalValue
вашей записи RowVersion
устанавливается на новое значение, как только вы вытаскиваете его из контекста.
var carInstance = dbContext.Cars.First();
carInstance.RowVersion = carDTO.RowVerison;
carInstance.Color = carDTO.Color ;
var entry = dbContext.Entry(carInstance); //Can also come from ChangeTrack in override of SaveChanges (to do it automatically)
entry.Property(e => e.RowVersion)
.OriginalValue = entry.Entity.RowVersion;
После небольшого исследования я смог использовать IsConcurrencyToken в столбце байта [8], названном RowVersion в Entity Framework 6.
Поскольку мы хотим использовать один и тот же тип данных в DB2 (который не имеют rowversion в самой базе данных), мы не можем использовать параметр IsRowVersion ()!
Я немного поработал над тем, как работать с IsConcurrencyToken.
Я сделал следующее для достижения решение, которое, кажется, работает:
Моя модель:
public interface IConcurrencyEnabled
{
byte[] RowVersion { get; set; }
}
public class Product : AuditableEntity<Guid>,IProduct,IConcurrencyEnabled
{
public string Name
{
get; set;
}
public string Description
{
get; set;
}
private byte[] _rowVersion = new byte[8];
public byte[] RowVersion
{
get
{
return _rowVersion;
}
set
{
System.Array.Copy(value, _rowVersion, 8);
}
}
}
IConcurrencyEnabled используется для идентификации сущностей, которые имеют rowversion, которые нуждаются в особой обработке.
I использующий свободный API для конфигурирования конструктора:
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(e => e.RowVersion).IsFixedLength().HasMaxLength(8).IsConcurrencyToken();
}
}
И, наконец, я добавил метод к моему производному классу DBContext для обновления поля до базы. ВызываетсяSaveChanges:
public void OnBeforeSaveChanges(DbContext dbContext)
{
foreach (var dbEntityEntry in dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified))
{
IConcurrencyEnabled entity = dbEntityEntry.Entity as IConcurrencyEnabled;
if (entity != null)
{
if (dbEntityEntry.State == EntityState.Added)
{
var rowversion = dbEntityEntry.Property("RowVersion");
rowversion.CurrentValue = BitConverter.GetBytes((Int64)1);
}
else if (dbEntityEntry.State == EntityState.Modified)
{
var valueBefore = new byte[8];
System.Array.Copy(dbEntityEntry.OriginalValues.GetValue<byte[]>("RowVersion"), valueBefore, 8);
var value = BitConverter.ToInt64(entity.RowVersion, 0);
if (value == Int64.MaxValue)
value = 1;
else value++;
var rowversion = dbEntityEntry.Property("RowVersion");
rowversion.CurrentValue = BitConverter.GetBytes((Int64)value);
rowversion.OriginalValue = valueBefore;//This is the magic line!!
}
}
}
}
Проблема, с которой сталкивается большинство людей, заключается в том, что после установки значения сущности мы всегда получаем исключение UpdateDBConcurrencyException, потому что свойство OriginalValue изменилось ... даже если оно не было!
Причина в том, что для бют e [] как оригинал, так и currentValue, если вы установите CurrentValue отдельно (?? странное и неожиданное поведение).
Итак, я снова установил исходное значение в исходное значение перед обновлением rowversion ... Также я копирую массив, чтобы не ссылаться на один и тот же байт-массив!
Внимание: здесь я использую инкрементный подход для изменения rowversion, вы можете использовать свою собственную стратегию для заполнения этого значения. (Случайные или временные)
[Timestamp]
, а затем указывает, чтоIsConcurrencyToken
является эквивалентным беглому методу. Но это неправда.IsRowVersion
эквивалентен. – Gert Arnold 10 February 2016 в 22:41