Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark entities as MultiTenant without using an Attribute #184

Closed
GordonBlahut opened this issue Oct 3, 2019 · 5 comments
Closed

Mark entities as MultiTenant without using an Attribute #184

GordonBlahut opened this issue Oct 3, 2019 · 5 comments

Comments

@GordonBlahut
Copy link
Contributor

If entity classes are in a class library (e.g. MyApp.Core) and the EF configuration in another class library (e.g. MyApp.Data), the MyApp.Core class library has to take on Finbuckle.MultiTenant.EntityFrameworkCore (and therefore EntityFrameworkCore itself) in order to use [MultiTenant] on classes.

A solution to mark entities as MultiTenant without an attribute would be great. It would make a lot of sense (to me anyway) to make this part of model building API. Maybe an extension method on EntityTypeBuilder and EntityTypeBuilder<T> so you could do modelBuilder.Entity<MyEntity>().HasMultiTenant().

That way the MyApp.Core library doesn't need to depend on Finbuckle at all.

For example, the HasMultiTenant extension method could add an annotation or something to the entity type:

public static EntityTypeBuilder<TEntity> HasMultiTenant<TEntity>(this EntityTypeBuilder<TEntity> builder) where TEntity : class
{
    HasMultiTenant((EntityTypeBuilder)builder)

    return builder;
}  
  
public static EntityTypeBuilder HasMultiTenant(this EntityTypeBuilder builder)
{
    builder.HasAnnotation("Finbuckle:MultiTenant", true);

    return builder;
}

Shared could add Shared.IsMultiTenant and look for the annotation instead of the attribute:

public static bool IsMultiTenant(IEntityType t)
{
    return (bool?)t.FindAnnotation("Finbuckle:MultiTenant")?.Value ?? false
}

(or since it's a static method anyway, it could be an extension method of IEntityType).

MultiTenantDbContext.MultiTenantEntityTypes and MultiTenantIdentityDbContext.MultiTenantEntityTypes would call Shared.IsMultiTenant instead of Shared.HasMultiTenantAttribute to build the list of MultiTenant entity types.

public IImmutableList<IEntityType> MultiTenantEntityTypes
{
    get
    {
        if (multiTenantEntityTypes == null)
        {
            multiTenantEntityTypes = Model.GetEntityTypes().
                Where(t => Shared.IsMultiTenant(t)).
                ToImmutableList();
        }

        return multiTenantEntityTypes;
    }
}

MultiTenantIdentityDbContext.OnModelCreating would also have to call Shared.IsMultiTenant when modifying indexes on identity tables.

MultiTenantAttribute could remain as well for backwards compatibility and for those who prefer attribtues to annotate models. Shared.SetupModel would simply get a list of entities with the attribute and call HasMultiTenant on them to add the annotation and then loop through entity types with the annotation instead:

public static void SetupModel(ModelBuilder modelBuilder, Expression<Func<TenantInfo>> currentTenantInfoExpression)
{
    foreach (var t in modelBuilder.Model.GetEntityTypes().
        Where(t => HasMultiTenantAttribute(t.ClrType)))
    {
        modelBuilder.Entity(t.ClrType).HasMultiTenant();
    }

    foreach (var t in moduleBuilder.Model.GetEntityTypes().
        Where(t => IsMultiTenant(t))
    {
        var r = modelBuilder.Entity(t.ClrType);
        
        // ... original logic for setting up model
    }
}

That way both the fluent model builder API as well as the attribute could be used to mark entities as MultiTenant.

Caveat: MultiTenantDbContext.MultiTenantEntityTypes and MultiTenantIdentityDbContext.MultiTenantEntityTypes would not be able to return a list of results until after OnModelCreating is called but I'm not sure how much that would end up being an issue.

Let me know if you're interested in this concept. Maybe I could put together a PR but I don't currently have .NET Core 3.0.

@GordonBlahut
Copy link
Contributor Author

Oops, Shared.EnforceTenantId would also need to be updated to identify MultiTenant entities via the annotation:

public static void EnforceTenantId(TenantInfo tenantInfo,
    ChangeTracker changeTracker,
    TenantNotSetMode tenantNotSetMode,
    TenantMismatchMode tenantMismatchMode)
{
    var changedMultiTenantEntities = changeTracker.Entries().
        Where(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted).
        Where(e => Shared.IsMultiTenant(e.Metadata));

    // ... rest of method

@AndrewTriesToCode
Copy link
Contributor

Hi @GordonBlahut

I had the same thought about using the model builder as I started looking into IdentityServer support. I think this adds a lot of value as well.

If you start a PR that would be great, otherwise maybe I can get you to review mine? Not sure when I will get to it--a few other items on the plate as well.

@GordonBlahut
Copy link
Contributor Author

Ok, I'll look into putting together a PR. I likely won't be able to get to it until the weekend or maybe even next week.

@AndrewTriesToCode
Copy link
Contributor

hi @GordonBlahut, I just submitted PR #191

Thoughts?

@GordonBlahut
Copy link
Contributor Author

No joke I was just about to start my implementation. I will take a look at the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants