2

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?

4 Answers 4

1

Mate,

The case is a little bit complex than you think.
The problem here is that you can not add Timestamp/concurrency check on Derived class(via annotation or Fluent Api or ...). When you add a timestamp attribute to a property of the derived class then model it fails with model validation

Type 'Derived' defines new concurrency requirements that are not allowed for subtypes of base EntitySet types. That means exactly what error says. Entity Framework does not support concurrency checks in derived types. You will see same error if you'll add simple concurrency check instead of timestamp:

Because of a very good technical reason we don't want to put timestamp property on the base class . from our database grue point of view, each table should have its own timestamp.

Sign up to request clarification or add additional context in comments.

Comments

0

I maybe miss something here but the solution is easier than the path you chose. If you want entity framework to use the Timestamp column for concurrency check then you have to do the appropriate configuration. This means either decorate the property with the [Timestamp] attribute or do it through fluent configuration which I prefer

modelBuilder.Entity<Derived >().Property(p => p.Timestamp ).IsConcurrencyToken();

Is this a sufficient solution?

1 Comment

Maybe you don't read may comment,It doesn't matter how you configure the timestamp via attribute([Timestamp],[ConcurrencyToken]) or Fluent Api(IsTimestamp,IsConcurrencyToken), it doesn't work. it is not a bug , it is by design(Look at source of Entity framework from Codeplex). I was through all this ways/tricks early days when I just started that task. if you think that magic line of code solves the issue. just try it. you will get exactly same error. If you look at books(Programming Entity framework by Julia Lerman)and MSDN documents. it clearly says that you have to use stored procedure
0

Maybe you don't read may comment,It doesn't matter how you configure the timestamp via attribute([Timestamp],[ConcurrencyToken]) or Fluent Api(IsTimestamp,IsConcurrencyToken), it doesn't work. it is not a bug , it is by design(Look at source of Entity framework from Codeplex).

I was through all this ways/tricks early days when I just started that task. if you think that magic line of code solves the issue. just try it. you will get exactly same error.

If you look at books(Programming Entity framework by Julia Lerman)and MSDN documents. it clearly says that you have to use stored procedure.

Anyway, thanks for your time Regards

Comments

0

I did read your answer and understood why the solution i proposed in my previous answer is not applicable. The goal you want to achieve inside the command tree interceptor is to manually add the value for the timestamp column as this is not sent by Entity Framework, right? In this case You don't have to change the predicate but just add another set clause.

1 Comment

Thanks. As you may probably agree, we cannot use on the fly SQL which is generated automatically by EF(as it doesn't check the concurrency when we the property doesn't have Timestamp attribute). so we should add new parameter(timestamp) and its value. I think the right place for this is inside the interceptor by revising DbFunctionCommand and add timestamp parameter to it. I can manage to to this, the problem is getting the timestamp value of the entity which is not accessible there. you have access to interception context but not the instance of entity.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.