-
-
Notifications
You must be signed in to change notification settings - Fork 269
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
Feat/184 set multitenant onmodelcreating #191
Feat/184 set multitenant onmodelcreating #191
Conversation
-All logic relies on EFCore annotations -EntityBuilder.IsMultiTenant sets the annotation -[MultiTenant] attributed classes get the annotation automatically -MultiTenantIdentidyDbContext updated to use annotation on standard types -MultiTenant Identity classes all deprecated.
-Data Isolation (netcoreapp3.0) -Identity Data Isolation (netcoreapp2.2, netcoreapp3.0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One concern I have as a library consumer is forcing all of the Identity-related classes to be MultiTenant
. That would actually break my usage today.
In my current use case, my User
is marked as MultiTenant
, but my Role
is not since in my case they are standard regardless of the tenant. I also don't bother marking my UserClaim
, UserLogin
, etc. as MultiTenant
because they are dependent on the User
which is already MultiTenant
so I didn't see any added benefit for the overhead of marking them so.
I suppose it could also be possible for a library user to want to have MultiTenant
entities but not have the Identity classes be MultiTenant
if they had a single sign-on type thing where users work across tenants.
I think it would offer greater flexibility to leave it up to the library user to call IsMultiTenant()
for the Identity classes they want to enroll. That flexibility exists today so it would be a loss to remove it.
Obsoleting the MultTenantIdentity*
classes is good.
I was also toying with the idea of doing the entity configuration right inside FinbuckleEntityTypeBuilderExtensions.IsMultiTenant
instead of Shared.SetupModel
(Shared.SetupModel
would only check for the attribute and call IsMultiTenant()
when found), which would give the library consumer even greater flexibility on model configuration. I have to think about how this impacts modifying the indexes on Identity entities though. I have some ideas around it.
In your documentation you recommend calling base.OnModelCreating()
last. You could extend the guideline to say that if you have a call to HasQueryFilter()
on an entity, you should call IsMultiTenant()
for the entity after the call to HasQueryFilter()
.
I do all my entity configurations in IEntityTypeConfiguration<T>
classes so being able to control when stuff is setup is very helpful. In my case I have to keep base.OnModelCreating()
call first because I override some of the default ASP.NET Identity class configurations and calling it last would overwrite my configuration with the default. (Note: I have my own AddQueryFilter
implementation to make sure I don't remove the filters added by your library).
I also wonder if there could be any benefit to moving HasMultiTenantAnnotation
to a public extension class. There could be cases where that information could be useful, maybe in a SaveChanges
override for example.
@GordonBlahut I took a quick stab at moving all the global query filter logic right into the builder extension method but hit some problems and it was getting late so I left it as-is. I think had trouble passing the expression needed to get the tenant id to the extension method cleanly -- I think a default parameter on the extension method would probably do the trick. I've given you access to the downstream repo--please submit any of the other changes you mentioned. I understand your situation regarding calling the base As long as the unit tests all pass I'm happy to accept changes. Once we get things working how we like it, I'll update the docs and merge. |
Pulled down your feature branch. Do you want me to commit directly to that branch or to a new branch from there? |
Feel free to commit straight to this one. |
Ok, will do. I figured out how to do the global query filter expression without needing to pass any expression in. I just need to do the index modifications for the identity classes from the extension method as well and then it should be good for review. |
Running the tests raised something I didn't consider: with I'm conflicted because on the one hand, Edit: I have an idea that I think maintains backward-compatibility for upgrades without sacrificing any flexibility:
That should mean everything continues to work exactly as it previously did and the Thoughts? |
Hm, go ahead and push what you have so I can take a look. I've been thinking about how to support a DbContext not needing to inherit from With respect to the breaking change you describe above, I think your approach makes a lot of sense. Then in your case you would derive from the long form |
That's funny, I've been thinking about something like that too. I was imaging a case where there'd be 2 different libraries that both required an inherited
Yes, exactly. |
- Entity is configured as soon as IsMultiTenant is called - MultiTenantIdentityDbContext classes automatically call IsMuliTenant on certain entities to match previous behavior - IMultiTenantDbContext interface added (currently internal) as common interface needed for expression building.
I pushed my changes so far. Please take a look. All tests pass, but I do want to do some cleanup still. |
@GordonBlahut awesome. I'll have to take a close look at how you did the expression for the filter--looks interesting. Do you mind if I extract out some more properties like |
I'll add some comments when I do cleanup. It was a lot of trial and error. I am by no means an expert at expression building. EF will rewrite an expression when it finds a type that is assignable from the context type so that it uses the actual context type. I had some trouble with that because there are 2 separate context hierarchies until I realized the obvious solution to use an interface to cover both. The nested private class is just used to create a closure over the context variable in the expression. It's never actually instantiated. Sure, feel free to extend the interface where it makes sense. I made it internal for now but maybe it could be made public if there's any value in it? |
-Remaining setup moved to extension methods.
@GordonBlahut Now to utilize the features a DbContext need not inherit from All that is needed:
Still to do: any needed test cases and documentation. |
Seems like we're on the same page regarding moving stuff to extension methods. I'll take a look when I clean up my changes. |
Something else I have in mind -- using Roslyn to create a wrapper/decorator for an existing DbContext at runtime. That way you don't even have to derive a subclass or implementation yourself. I'll probably do it in a followup PR. My use case for that is making Finbuckle work with IdentityServer4 easily although I could probably do so just with what we have so far. |
Working on cleaning up my changes.
Then I've tried it locally and all tests still pass. I noticed you removed the |
Hi, I agree your approach to remove the |
- Renamed HasMultiTenantAnnotation to IsMultiTenant (annotation is an implementation detail that shouldn't matter to consumers) - Updated EnforceMultiTenant signature to eliminate need for IMultiTenantDbContext.Context (and remove from interface) - Removed Shared class and methods moved to appropriate extension classes. - Moved other methods to extension classes where it makes sense. - Made Constants internal (no real benefit to expose annotation name used) - Added missing license declarations in new files. - Fixed xUnit warning
@achandlerwhite I did not re-add the protected set for TenantInfo. |
@GordonBlahut Thanks, I'll take a look this weekend. I have some family visiting so I might not be able to get to it until Sunday. |
@GordonBlahut looks great. I'm going to update the docs, ideally tomorrow, and then get this merged. |
@achandlerwhite Sounds good. Not sure if this will be released in a major or minor release, but next time you're doing a major release, consider moving That would serve 2 purposes:
Just an idea. |
@GordonBlahut
If you have time do you mind taking a look at the documentation and other changes? Thanks again for your help on this. |
@achandlerwhite I will try to review it tonight. |
- more content and examples for fluent API - minor spelling/grammar fixes
@achandlerwhite Code looks good. I added some changes to the EFCore doc. Maybe need a tests for each of the various Also maybe some tests involving contexts that do not derive from It will probably be a few days before I can look at that unless you can do it sooner. |
MultiTenantIdentityDbContext
I added test for a db context directly deriving from I'll add more tests for the other items you mentioned hopefully tomorrow. |
I submitted test for all the |
1 similar comment
I submitted test for all the |
@GordonBlahut I've got the last of the extension unit tests completed and everything looks good. I plan to do some more experimenting with it, but I think we have it pretty much where we want it. If you have any more changes let me know. Thanks for your help! |
@achandlerwhite The only thing I'm not crazy about is Also I don't really have any better suggestions (naming stuff sucks) so it's just an overly nitpicky thing. Maybe Or just leave it as is. I was also thinking about an |
I like |
@GordonBlahut What do you think about this?
Added
EntityModelBuilder.IsMultiTenant()
extension method that can be used inOnModelCreating
to set the annotation.Types with
[MultiTenant]
get the annotation inShared.SetupModel
All logic uses the annotations
MultiTenantIdentityDbContext
now appliesIsMultiTenant()
to each of the standard Identity types.MultiTenantIdentityUser
and similar all marked obsolete.Added unit test for an entity using the extension method
Updated IdentityDataIsolationSample for netcoreapp2.2 and netcoreapp3.0
Updated DataIsolationSample for netcoreapp3.0
Still need to update documentation.