Set Value before save

c# entity-framework-core

Question

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.

1
3
10/23/2018 8:58:44 PM

Accepted Answer

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.

3
10/23/2018 5:31:08 PM

Popular Answer

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());
    });
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow