Optimistic Concurrency and Transactions

Thread is closed for posting
1 posts, 0 answers
  1. Chris
    Chris avatar
    1 posts
    Member since:
    May 2016

    Posted 06 Jan 2017 Link to this post

    Hi

    I came across an issue today with the behaviour of OpenAccessContext.FlushChanges() when using optimistic concurrency. To explain I have created a minimal example based on the example project from nuget.


    My metadata configuration and product class looks like this…

    namespace TelerikDataAccessTimestampFlushIssue
    {
        public class TelerikDataAccessTimestampFlushIssueMetadataSource : FluentMetadataSource
        {
            protected override IList<MappingConfiguration> PrepareMapping()
            {
                // Getting Started with the Fluent Mapping API
     
                List<MappingConfiguration> configurations = new List<MappingConfiguration>();
     
                MappingConfiguration<Product> productConfiguration = new MappingConfiguration<Product>();
                productConfiguration.MapType(x => new
                {
                    ID = x.ID,
                    Price = x.Price,
                    ProductName = x.ProductName,
                    Timestamp = x.Timestamp,
                }).WithConcurencyControl(OptimisticConcurrencyControlStrategy.Backend)
                .ToTable("Products");
     
                productConfiguration.HasProperty(x => x.ID)
                    .IsIdentity(KeyGenerator.Autoinc);
     
                productConfiguration.HasProperty(x => x.Timestamp)
                    .IsVersion()
                    .WithDataAccessKind(DataAccessKind.ReadWrite)
                    .IsNotNullable()
                    .HasColumnType("timestamp")
                    .HasPrecision(0)
                    .HasScale(0);
     
                configurations.Add(productConfiguration);
     
                return configurations;
            }
        }
    }
    namespace TelerikDataAccessTimestampFlushIssue
    {
        public class Product
        {
            public int ID { get; set; }
            public string ProductName { get; set; }
            public decimal Price { get; set; }
            public long Timestamp { get; set; }
        }
    }

    The following code demonstrates the issue…

    namespace TelerikDataAccessTimestampFlushIssue
    {
        class Program
        {
            private static readonly Logger _LOG = LogManager.GetCurrentClassLogger();
     
            static void Main(string[] args)
            {
                _LOG.Debug("Start");
     
                // Create the database (if required)
                using (FluentContext context = new FluentContext())
                {
                    Telerik.OpenAccess.ISchemaHandler schemaHandler = context.GetSchemaHandler();
                    string script = null;
                    if (schemaHandler.DatabaseExists())
                    {
                        _LOG.Debug("Database exists");
                        script = schemaHandler.CreateUpdateDDLScript(null);
                    }
                    else
                    {
                        _LOG.Debug("Create database");
                        schemaHandler.CreateDatabase();
                        script = schemaHandler.CreateDDLScript();
                    }
                    if (!string.IsNullOrEmpty(script))
                    {
                        _LOG.Debug("Execute DDL Script");
                        schemaHandler.ExecuteDDLScript(script);
                    }
                }
     
                // Create a product
                int productId;
                using (FluentContext context = new FluentContext())
                {
                    Product myProduct = new Product()
                    {
                        Price = 100,
                        ProductName = "My Product",
                    };
                    context.Add(myProduct);
                    context.SaveChanges();
                    productId = myProduct.ID;
                    _LOG.Debug("Created product {0}", productId);
                }
     
                // Try to flush changes and make another change before committing the transaction.
                using (FluentContext context = new FluentContext())
                {
                    Product myProduct = context.Products.Single(p => p.ID == productId);
     
                    myProduct.Price = 101;
                    context.FlushChanges();
     
                    myProduct.Price = 102;
     
                    // Uncomment to workaround/fix?
                    //context.Refresh(RefreshMode.OverwriteChangesFromStore, myProduct);
     
                    // This line fails because the timestamp has changed in the database
                    // (in our transaction anyway) but still has its old value on the
                    // instance.
                    context.SaveChanges();
                    _LOG.Debug("DONE");
                }
     
            }
        }
    }


    I was expecting calls to be able to call FlushChanges() and then SaveChanges() to work in the same way that it would if I wasn't using optimistic concurrency but that is not the case. Flushing the change to the database advances the Timestamp but this change is not automatically refreshed in the tracked entity.

    Could you please clarify if this is by design (perhaps for performance?) or if it is a bug? If it is by design perhaps someone could make a note in the documentation that Refresh() has to be called if you want to modify an entity multiple times in a transaction.

    Thanks
    Chris

Back to Top