This project has moved. For the latest updates, please go here.

Is the "Timestamp" Annotation compatible with JetEntityFrameworkProvider?

May 16, 2016 at 6:23 PM
Hi there,
I tried using the [Timestamp] annotation in one of my class properties like this (using Code First):
        [Timestamp]
        public byte[] rowversion { get; set; }
The Migration works fine. No errors.
But now every time I try to insert something in the DB I get the following error:
"An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll"

When I open the Error details is see the following:
{"Die Sequenz enthält kein übereinstimmendes Element."}
which means, that the sequence could not find a compatible/suitable element.

My Provider String is "Microsoft.ACE.OLEDB.12.0".
The Jet Provider is working fine if I leave the timestamp annotation out.

Is it possible that Access-DB does not support this feature? If so, is there anyway around it? Maybe by defining another concurrency control mechanism?

Thank you so much for your support.
Coordinator
May 17, 2016 at 5:54 AM
It should work but you have to declare rowversion as Guid.
Please let me know if this solve your problem.
May 17, 2016 at 10:13 AM
Edited May 17, 2016 at 10:14 AM
bubibubi wrote:
It should work but you have to declare rowversion as Guid.
Please let me know if this solve your problem.
When rowversion is declared as Guid, the system returns an error during the migration process:
The property 'rowversion' is not a Byte array. IsRowVersion can only be configured for Byte array properties.
According to the EF documentation, all properties with the [Timestamp] annotation (which implicitly means [IsRowVersion] as well) should be declared as byte[].
Coordinator
May 17, 2016 at 10:25 AM
Sorry, my mistake, Timestamps are not supported in Microsoft Access.
I know is not the same but you can still use IsConcurrencyToken on critical/all properties.
May 17, 2016 at 11:55 AM
Edited May 31, 2016 at 9:34 PM
Thank you for your quick response.

I have developed a workaround for this problem in order to compensate for Access's lack of a trigger function.
This allows you to define a rowversion property that will be updated automatically during the save process whenever any of the Entity's properties have been modified or the Entity is new and is being added.

The solution consists of an Interface that defines the rowversion property:
public interface IHasRowversion {
    DateTime rowversion { get; set; }
}
rowversion is declared as a DateTime here, but it could be anything.
All Entities that require this mechanism should implement the IHasRowversion interface.

The second component is a method defined in the context class that will update modified Entitities of type IHasRowversion:
/// <summary>
/// Updates the rowversion property of all IHasRowversion Entities.
/// </summary>
private void updateRowVersions() {
    var entityList = this.ChangeTracker.Entries().Select(e => e.Entity).ToList();

    foreach (Object thisObject in entityList) {
        var dbEntity = this.Entry(thisObject);
                
        if(dbEntity.Entity is IHasRowversion) {
            if (dbEntity.State == EntityState.Modified || dbEntity.State == EntityState.Added) {
                IHasRowversion versionedEntity = (IHasRowversion)dbEntity.Entity;

                //The type of value set here should match the 
                //property type defined in IHasRowVersion
                versionedEntity.rowversion = DateTime.Now; //rowversion updated to current Date and Time.
            }
        }
    }
}
This method should be called before saving changes to the DB so we override the corresponding methods in our context class:
public override int SaveChanges() {
    updateRowVersions();
    return base.SaveChanges();
}

public override Task<int> SaveChangesAsync() {
    updateRowVersions();
    return base.SaveChangesAsync();
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken) {
    updateRowVersions();
    return base.SaveChangesAsync(cancellationToken);
}
So full Context class should now look something like this:
public class ExampleContext : DbContext {

    //Default constructor required for Migration manager.
    public ExampleContext() : base() { } 

    //Constructor required by JetEntityFrameworkProvider.
    public ExampleContext(DbConnection connection) : base(connection, false) { }

    public DbSet<ExampleEntity> ExampleEntities{ get; set; }

    public override int SaveChanges() {
        updateRowVersions();
        return base.SaveChanges();
    }

    //SaveChangesAnsync() may throw an AggregateException instead
    //of a DbUpdateConcurrencyException. 
    //This could be fixed by a try/catch statement here that throws
    //a DbUpdateConcurrencyException instead.
    public override Task<int> SaveChangesAsync() {
        updateRowVersions();
        return base.SaveChangesAsync();
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken) {
        updateRowVersions();
        return base.SaveChangesAsync(cancellationToken);
    }

    /// <summary>
    /// Updates the rowversion property of all IHasRowversion Entities.
    /// </summary>
    private void updateRowVersions() {
            if (!this.ChangeTracker.HasChanges()) {
                return; //Skip if no changes have been tracked.
            }
        var entityList = this.ChangeTracker.Entries().Select(e => e.Entity).ToList();

        foreach (Object thisObject in entityList) {
            var dbEntity = this.Entry(thisObject);

            if (dbEntity.Entity is IHasRowversion) {
                if (dbEntity.State == EntityState.Modified || dbEntity.State == EntityState.Added) {
                    IHasRowversion versionedEntity = (IHasRowversion)dbEntity.Entity;
                    versionedEntity.rowversion = DateTime.Now;
                }
            }
        }
    }
}
Finally, Entity classes that implement IHasRowversion should include the [ConcurrencyCheck] and [Required] annotations/attributes in order for the concurrency check to be performed propertly.
//Any changes to the rowversion property declaration should be reflected in the updateRowVersions() method.
public partial class ExampleEntity : IHasRowversion {
    [ConcurrencyCheck]
    [Required]
    public DateTime rowversion { get; set; } 
}
An alternative would be to define this via Fluent Api in the Context Class:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Entity<ExampleEntity>()
            .Property(p => p.rowversion).IsConcurrencyToken();
}
Thank you so much for the great work, bubibubi. Your JetEntityFrameworkProvider is excellent.
I hope this solution helps others out there like me.