I was wondering if there is any way to set a value to an entity onsave?
Because I'm working on a multi tenant web application and I would like to set the the current tenant ID (through simple DI service).
I tried using HasDefaultValue()
in Fluent API, however this will try to convert to a SQL function. So this doesn't work for me.
builder.Entity<Order>( )
.HasQueryFilter(p => p.TenantId == _tenantProvider.GetTenantId())
.Property(p => p.TenantId)
.HasDefaultValue(_tenantProvider.GetTenantId());
Any suggestions are greatly appreciated.
You could override the DbContext.SaveChanges() method and iterate the ChangeTracker entries:
public override int SaveChanges()
{
foreach (var entityEntry in ChangeTracker.Entries()) // Iterate all made changes
{
if (entityEntry.Entity is Order order)
{
if (entityEntry.State == EntityState.Added) // If you want to update TenantId when Order is added
{
order.TenantId = _tenantProvider.GetTenantId();
}
else if (entityEntry.State == EntityState.Modified) // If you want to update TenantId when Order is modified
{
order.TenantId = _tenantProvider.GetTenantId();
}
}
}
return base.SaveChanges();
}
Of course, this needs the tenant provider to be injected into your context.
EF Core value generation on add with custom ValueGenerator
Generates values for properties when an entity is added to a context.
could be utilized to assign TenantId
to the new entities. Inside the Next
method you could obtain the TenantId
from the context (or some service).
Taking your sample, the value generator could be a nested class inside your DbContext
like this:
class TenantIdValueGenerator : ValueGenerator<int>
{
public override bool GeneratesTemporaryValues => false;
public override int Next(EntityEntry entry) => GetTenantId(entry.Context);
int GetTenantId(DbContext context) => ((YourDbContext)context)._tenantProvider.GetTenantId();
}
The all you need is to assign the generator to TenantId
property using some of the HasValueGenerator fluent API.
The only problem is that by design the value generators are called only if the property does not have explicitly set value (for int
property - if the value is 0
).
So the better approach it to abstract (and fully control) the TenantId
property by removing it from entity models and replacing it with shadow property.
Hence my suggestion is, remove the TenantId
from entity classes and call the following method inside your OnModelCreating
for each entity that needs TenantId
column:
void ConfigureTenant<TEntity>(ModelBuilder modelBuilder) where TEntity : class
{
modelBuilder.Entity<TEntity>(builder =>
{
builder.Property<int>("TenantId")
.HasValueGenerator<TenantIdValueGenerator>();
builder.HasQueryFilter(e => EF.Property<int>(e, "TenantId") == _tenantProvider.GetTenantId());
});
}