I want to be able to use an attribute on a Row Version property of a model to enable or disable Optimistic Concurrency in the OnModelCreating(ModelBuilder modelBuilder)
method of my DbContext
in EF Core 7.0.0. I was able to make it work in .NET Framework 4.8 but I need to do it in an EF Core project as well.
I have an interface for entities which have a Version
property called IVersionedEntity
and I want to use a custom attribute where I can define if I want to enable or disable optimistic concurrency.
The attribute:
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public class LockAttribute : Attribute
{
public bool IsEnabled { get; private set; }
public LockAttribute(bool isEnabled = true)
{
IsEnabled = isEnabled;
}
}
And I know I can achieve the enable-disable behavior inside the OnModelCreating(DbModelBuilder modelBuilder)
method with this extension method in ASP.NET (.NET Framework):
public static void ConfigureLocking(this DbModelBuilder modelBuilder)
{
modelBuilder.Types<IVersionedEntity>().Configure(configuration =>
{
var versionPropertyInfo = configuration.ClrType.GetProperty("Version");
var lockAttribute = versionPropertyInfo.GetCustomAttribute<LockAttribute>(true);
if (lockAttribute != null)
{
configuration.Property(entity => entity.Version).IsRowVersion().IsConcurrencyToken(lockAttribute.IsEnabled);
}
});
}
My problem is, that the ModelBuilder
class inside EF Core is quite different and I do not know how to configure something like this using that class.
In EF Core 7.0+ it can be done relatively easy with a custom Model building convention. In fact all predefined EF Core data annotations are handled with convention classes similar to this:
public class LockAttributeConvention : PropertyAttributeConventionBase<LockAttribute>
{
public LockAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { }
protected override void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, LockAttribute attribute, MemberInfo clrMember, IConventionContext context)
{
if (attribute.IsEnabled)
{
propertyBuilder.ValueGenerated(ValueGenerated.OnAddOrUpdate, fromDataAnnotation: true);
propertyBuilder.IsConcurrencyToken(true, fromDataAnnotation: true);
}
}
}
Helper method for registering it:
public static ModelConfigurationBuilder AddLockAttributeSupport(this ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Add(sp => new LockAttributeConvention(
sp.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
return configurationBuilder;
}
and finally call it from ConfigureConventions
override:
configurationBuilder.AddLockAttributeSupport();