Consider following classes
[Table("Base")]
public class Base
{
public int Id { get; set; }
public BaseTypes BaseTypeId{ get; set; }
}
[Table("Derived")]
public class Derived : Base
{
public string Description { get; set; }
public DateTime CreatedDate { get; set; }
public byte[] Timestamp { get; set; }
}
As you know we can not add Timestamp/concurrency check on Derived class. so the suggested solution for checking concurrency is using stored procedure and checking concurrency inside the procedure. you can use stored procedure instead of EF automatic generated SQL by configuring entities. something like this
modelBuilder.Entity<Base>().MapToStoredProcedures();
modelBuilder.Entity<Derived>().MapToStoredProcedures();
it works good until you have scenario like this
var context = dbContextFactory.Create(null);
var derived = new Derived()
{
BaseTypeId = BaseTypes.PropertySale,
CreatedDate = DateTime.Now.AddDays(-1),
Description = "test "
};
context.Deriveds.Add(derived);
context.SaveChanges();
// later on (A)
var savedEntity=context.Derived.Where(x=>x.Id==derived)
// in this place timestamp is not populated(but Id gets populated)
savedEntity.Description = "changed";
context.SaveChanges();
It throws exception because it savedEntity.timestamp is null but in database it isn't null(timestamp column get populated automatically). It looks like after insert happens, entity framework only updates properties those have DatabaseGenerated attribute.
I tried with putting DatabaseGenerated(DatabaseGeneratedOption.Computed) on timestamp column. it passes the entity framework model validation.By doing this, after insertion the Timestamp property gets populated but the problem is as this propety is Computed column therefore it is not sent to update query/Procedure. so you cannot test the concurrency inside the procedure.
I also tried to develop a interceptor for DBUpdateCommandTree / DBFunctionCommandTree for adding timestamp property to the command manually, I successfully managed to add parameter to the command but the problem is inside the interceptor I have no access to the entity itself to read timestamp values and pass it to procedure.
FYI: using interceptor like this you can intercept database command
public class HistoryQueryInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
{
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var dbContexts = interceptionContext.DbContexts;
var snapshotDate = DateTime.Now; //replace with SystemTime
var snapshotSupportDbContext =
dbContexts.FirstOrDefault(context => context is ISupportSnapshotViewDbContext);
if (snapshotSupportDbContext != null)
{
snapshotDate = (snapshotSupportDbContext as ISupportSnapshotViewDbContext).SnapShotDate;
}
var newQuery = queryCommand.Query.Accept(new HistoryRecordQueryVisitor(snapshotDate));
interceptionContext.Result = new DbQueryCommandTree(
queryCommand.MetadataWorkspace,
queryCommand.DataSpace,
newQuery);
}
var updateCommand = interceptionContext.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
var binding = updateCommand.Target;
var revisedPredicate = DbExpressionBuilder.And(updateCommand.Predicate,
DbExpressionBuilder.Equal(
DbExpressionBuilder.Property(DbExpressionBuilder.Variable(binding.VariableType, binding.VariableName), "Timestamp"),
DbExpressionBuilder.Parameter(TypeUsage.CreateBinaryTypeUsage(PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary), true), "Timestamp")));
interceptionContext.Result = new DbUpdateCommandTree(updateCommand.MetadataWorkspace,
updateCommand.DataSpace,
updateCommand.Target,
revisedPredicate,
new ReadOnlyCollection<DbModificationClause>(updateCommand.SetClauses),
updateCommand.Returning);
}
}
}
}
Any help?