Vorrei aggiungere il TenantId alle tabelle Identity Asp.Net (ad esempio: Utente).
Il seguente frammento funziona bene. Il contesto del titolare verrà iniettato tramite DI e il tenant cambia a seconda del dominio del contesto http:
private readonly ITenantContext<ApplicationTenant> tenantContext;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenantContext<ApplicationTenant> tenantContext) : base(options)
{
this.tenantContext = tenantContext;
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>(b =>
{
// add tenant
b.Property(typeof(int), "TenantId");
b.HasQueryFilter(x => EF.Property<int>(x, "TenantId") == this.tenantContext.Tenant.Id);
});
}
Per riutilizzare voglio creare un metodo di estensione per entityBuilder:
public static class EntityTypeBuilderExtensions
{
public static void AddTenancy<TEntity>(
this EntityTypeBuilder<TEntity> builder,
Expression<Func<int>> tenantId,
string propertyName = "TenantId")
where TEntity : class
{
// validate
Ensure.Argument.NotNull("builder", builder);
// add property to entity
builder.Property(typeof(int), propertyName).IsRequired();
/* THIS WORKS BUT WILL BE EVALUATED LOCALLY */
// left
var parameterExp = Expression.Parameter(typeof(TEntity), "x"); // e = TEntity => e.g: User
var propertyNameExp = Expression.Constant(propertyName, typeof(string)); // the name of the tenant column - eg.: TenantId
// right
var tTenantId = Expression.Convert(tenantId.Body, typeof(int)); // tenantId
var propertyMethod = typeof(EF).GetMethod(nameof(EF.Property), BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(typeof(int)); // build EF.Property
var propertyMethodExec = Expression.Call(propertyMethod, parameterExp, propertyNameExp); // represents EF.Property(e, "TenantId")
var bodyExp = Expression.Equal(propertyMethodExec, tTenantId);
var lambda = Expression.Lambda(bodyExp, parameterExp);
builder.HasQueryFilter(lambda);
}
}
E nel contesto db:
private Func<int> tenantId => () =>
{
// return tenant id
if (this.tenantContext != null && this.tenantContext.Tenant != null)
{
return this.tenantContext.Tenant.Id;
}
return -1;
};
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>(b =>
{
b.AddTenancy(() => this.tenantId(), "TenantId");
});
}
Il metodo di estensione funziona anche bene ma l'espressione viene valutata localmente :-(. Qualcuno può aiutarmi a risolverlo?
L'espressione LINQ 'where (Proprietà ([x], "TenantId") == Invoke (__ ef_filter__tenantId_0)) "non può essere tradotta e verrà valutata localmente. L'espressione LINQ 'where ([x] .NormalizedUserName == __normalizedUserName_0)' non può essere tradotta e verrà valutata localmente. L'espressione LINQ 'FirstOrDefault ()' non può essere tradotta e verrà valutata localmente.
Il problema è il Func
qui
private Func<int> tenantId => ...
che causa una traduzione errata di Invoke(__ef_filter__tenantId_0))
e la valutazione del client.
La soluzione è rendere tenantId
int
semplice restituendo proprietà o metodo. Ad esempio, per mantenere le chiamate
b.AddTenancy(() => this.tenantId(), "TenantId");
dovrebbe essere cambiato in
private int tenantId()
{
// return tenant id
if (this.tenantContext != null && this.tenantContext.Tenant != null)
{
return this.tenantContext.Tenant.Id;
}
return -1;
};