Open mmarinchenko opened 5 years ago
Intriguing. I'll look into it. Thanks for the suggestion.
As an aside, your comment above:
... EFDesigner lacks:
- support for generated entity to inherit any type (not just other entity type defined on a diagram);
isn't completely accurate. Since generated entities are partial classes, you can declare a base class in a custom partial file as long, of course, as there isn't any other base class for that type in your model.
Thanks, @msawczyn!
You're right, I was not completely accurate about entity inheritance. I guess I wrote too many words :) In fact I created this feature request mainly becuase of point 3.
As for the entity inheritance... This is difficult. Since the base type (e.g. IdentityUser
) is not a part of a diagram the model for its properities will not be autogenerated. This requires some kind of import feature in EFDesigner. Inherited properties must be a part of a diagram and must be generated in data context class but at the same time they must be readonly on a diagram and must be excluded from entity class generation. Besides that the base class may be changed in future. So EFDesigner must check this and reimport base properties... This is difficult :)
I really think this is going to need totally separate tt template generators.
Several critical factors:
<TKey>
variation (int, guid(string), etc)override
instead of virtual
)OnModelCreationImpl
must call IdentityDbContext
's base.OnModelCreating
base class impl, or re-implement the associations it defined for ef6. The last item I think is the game changer. I'm not sure how to even approach this.
Hmm. Am speaking aloud here. Welcome any thoughts.
I truly wish I had more time to work on this aspect. Pulling hair out as it is. This extension is overly well deserving of community support on this. And imagine the possibilities with base-class inheritance + control in gui, and abstraction through generated partials adhering to above principles. You'd simply drag n' drop, and expose a scoped interface in your services. How cool.
+1 vote on feature request from me. If i can find time, I'll do what I can to contribute ideas/concept-work to help edge this along.
Thanks for the excellent input! Let's hit those up in order:
<TKey>
changes: not a big deal
Design-time restrictions: not a huge deal. The designer could pre-populate identity base classes and lock them down from modification (see below).
Any code generation changes can do the Right Thing depending on what the base class is, checking whether it's one of the identity bases. Customization points, as always, are handled by generated partials.
I think you're right in that we'll need separate T4 templates for this, but that isn't such a bad thing. This is enough of a standardized compartment case that custom templates would be fitting.
I was thinking along the same lines regarding a pre-provisioned template. It would be a separate "Add New Item" choice, and would add a model with the necessary base classes, likely created based on reflection from the library classes so that, when they changed, a new model would have the correct properties pre-set for those classes.
I'm not entirely sure what you mean by "should the drag/drop processor be expanded to process DbContext classes and their ef6 relations". Entities are independent of the context, and sticking with the concept of POCO entities is a bit of a Prime Directive for the modeler.
Any changes in how inheritance is handled can be done by the T4, looking at the base class and generating appropriate code depending on whether it were user-created or one of the identity base classes, if indeed such a change is needed.
As always, I welcome any contributions you might have time for. I'm a bit swamped at work right now and don't have a lot of time to work in many (any?) hours for the designer, but that should change in a couple of handfuls of weeks. This is an intriguing problem!
Thanks again for the input.
I was going to create a sample project, but ran out of time.
The committed TT templates work for Identity if you feed it a baseClass followed by subclass (drag/drop), where base-class is a basic "class model" of decompiled AspnetCore 2.1 Identity public properties with a Identity[Superclass]<TKey>
, and subClass extends said "base".
https://github.com/ensemblebd/EFDesigner/commit/d49079ea2244a958e38db92f1a709b4700186bf8
And here's the .efmodel vstemplate (bypass feeding requirement, throw into project):
https://github.com/ensemblebd/EFDesigner/commit/1c8ee692d72a5acd2091099bba8d95d4ab8cdd6d
The commits are (I believe) unworthy of production, let alone a pull request.
But provides a sample changeset for intended goal. aka "works for me, wish you luck!"
If I can find time this week, I'll submit a git repo in this issue for a working proof of concept AspNetCore project. I have it working for my project, but gotta get rid of all the company stuff to provide such a thing publicly.
<TKey>
is hardcoded both at template level and efmodel level (need an MEF prop to procure designer field)MyNamespace.IdentityUser
is derived from a base class of AspNetCore.IdentityUser<TKey>
(for generator usage/purposes due to # 2 below), and is subclassed by ApplicationUser
(the custom impl).public
to be used in Type Parameters for Identity class registration<TKey>
, need MEF modification to support this. It tries to make use of it as a DbSet, in this case a.) no dbset is desired and b.) nor can it be textually procured given the raw text [< , > ] symbols..efmodel
template for Identity
may be necessary.Additional /etc Not related to Identity but valuable...
Some tables have multiple primary keys, which need an Order specified. I used the custom attr prop on modelClass
, however it required some hackery to prevent duplicate [Key] column. Perhaps an exposed prop for "Key Order" could be used, or an improvement to prevent Validation error on tables with no primary key.
Owin (anyone use that? hmm) requires access to the DbContextOptions<TClass>
constructor, so added a partial for that
I performed some additional tests for ASP.NET Core scenario and realize that first 2 points of original request are not needed for default identity support :) The only thing that needs to be done is point 3.
P.S. I also mention this in issue msawczyn/EFDesigner#72.
Just wanted to let you know that I've started implementing this, both in EF6 and EFCore. If you'd like to follow the progress (and contribute!) the branch is called identity-context
.
I've rewritten the Asp Core Identity for Mvc. Feel free to take a look at https://github.com/prince272/AspCoreIdentityMvc
Simply change the url for the area 'Identity' to 'IdentityMvc' and you'll be automatically directed to the Mvc version for Identity.
Nice ... thanks! I'll dig into that. I've got the changes needed in the designer pretty much done, so the next step is the code generation. This will certainly help.
Hi @msawczyn , I'm also interested in this topic. Is there way to speed up things by helping you out in this issue/enhancement ?
Absolutely! Always happy to have the help!
The goal, obviously, is to create a mechanism where the user can easily scaffold a model that will work with asp.net identity, then be able to modify it to their needs without breaking its compatability.
There's an older branch named identity-context
that I had started some time ago; the approach was having a different model starting template. Feel free to see if that's valid or needs scrapped and redone.
Thanks for volunteering.
Hey all, just wanted to let you know that, even after being on the list for a year, this isn't being ignored ... it just keeps getting bumped down in priority. It's not a simple task to get this right so that it can be used as a general modeling aid (will definitely require a custom starting project item) and will need some basic infrastructure before it can be implemented.
The EFCore5 release is taking up all available hours to get solid. But I haven't forgotten!
Greetings, I've been using your extension myself and with fellow developers. I hope changing the code base for 2022 made some room to give priority for this request. Using this great tool with built-in identity solution would makes us more than happy. Thanks again.
I have been using Entity Framework Visual Editor for a few years now; it has been a great tool for quickly reasoning about my data models. However, I am accumulating tech debt in a few different projects now from lack of integration of this tool with Microsoft Identity. I first attempted to resolve this on my own here. I have since started up a few projects where I maintain 2 separate "user"/"person" tables with a 1-1 relationship. This has resulted in lots of extra files, classes, lines of code, SQL queries, and general obscurity that probably leads down paths away from best practices. Here is an example of a "best" solution I could come up with after way more time than I would like to have spent:
public partial class Person
{
[NotMapped]
public string Email => AspNetUser.Email;
/// <summary>
/// THIS MUST BE EXPLICITLY SET
/// - it will not be auto-populated
/// </summary>
public IdentityUser AspNetUser { get; set; }
public override string ToString()
{
return $"{this.UserName} [{this.Email}]"; // unnecessary: ({this.Id}, {this.AspNetUserId})
}
}
And then on the Razor index page:
public class IndexModel : PageModel
{
private readonly MyModel _context;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<IndexModel> _logger;
public IndexModel(MyModel context, UserManager<IdentityUser> userManager,
ILogger<IndexModel> logger)
{
_context = context;
_userManager = userManager;
_logger = logger;
}
public List<Person> people { get;set; } = default!;
public async Task OnGetAsync()
{
List<IdentityUser> allUsers = await _userManager.Users
.ToListAsync();
people = await _context.People
.ToListAsync();
foreach (var person in people)
{
person.AspNetUser = allUsers
.Where(x => x.Id == person.AspNetUserId)
.First();
}
}
}
Does anyone have any better workarounds, or solutions for this disconnection of EF Visual Editor from Microsoft's Identity model?
And, the code I just posted above has broken another line of code where I was doing:
person.Email = User.FindFirstValue(ClaimTypes.Email);
in another Razor page. I will have to write even more logic to gracefully handle when I have the User ClaimsPrincipal, but not the IdentityUser - in all of my projects. I google search get IdentityUser from ClaimsPrincipal, and there are no results - this is a big red flag for me, and I am very concerned about the viability of my projects being designed with this tool now.
/// <summary> /// THIS MUST BE EXPLICITLY SET /// - it will not be auto-populated /// </summary> public IdentityUser AspNetUser { get; set; }
@Mattnificent, the data context class generated from the EF model is partial. It has partial methods for customization purposes:
partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
Therefore, in this particular case, you can manually add the \<YourEFModelName>.custom.cs file with the following text:
public partial class <YourEFModelName>
{
partial void OnModelCreatedImpl(ModelBuilder modelBuilder) =>
modelBuilder.Entity<Person>()
.HasOne<IdentityUser>(p => p.AspNetUser)
.WithOne();
}
This adds the Person.AspNetUser property to the model as navigation property with a One-to-One association.
Do not forget to generate a new migration. And check the Context Base Class property of the EF model - it should be Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext.
Hope this helps!😉
I google search get IdentityUser from ClaimsPrincipal, and there are no results
I guess you can use var aspNetUser = await _userManager.GetUserAsync(claimsPrincipal);
@mmarinchenko Thank you!!! You are a saint, and a king! I had tried to use the ForeignKey("AspNetUserId") attribute on the custom AspNetUser property to get the context to understand the 1-1 relationship between those tables, but it gave me some strange constructor error, so I assumed it was not possible. The Fluent API approach did the trick. Even the database deployment didn't have to drop any data, and my new OnGetAsync method worked first try out of the box:
public async Task OnGetAsync()
{
people = await _context.People
.Include(x => x.AspNetUser)
.ToListAsync();
}
If you ever need a favor, I'm your guy.
To support custom ASP.NET Core Identity scenario with EFDesigner some manual work has to be done.
Microsoft.AspNetCore.Identity
namespace:IdentityUser
IdentityRole
IdentityUserClaim<TKey>
IdentityUserRole<TKey>
IdentityRoleClaim<TKey>
IdentityUserLogin<TKey>
IdentityUserToken<TKey>
Note:
TKey
type parameter defaults tostring
type.Implement custom part of EFDesigner-generated DbContext class to define 7 respective DbSets and create model using partial
OnModelCreatedImpl()
method.Somehow inherit
IdentityDbContext<TUser,TRole,TKey,TUserClaim,TUserRole,TUserLogin,TRoleClaim,TUserToken>
type fromMicrosoft.AspNetCore.Identity.EntityFrameworkCore
namespace instead of defaultDbContext
fromMicrosoft.EntityFrameworkCore
.Note:
IdentityDbContext<>
in turn inheritsDbContext
.First 2 points are manual work because EFDesigner lacks:
string
type to be used as identity (actually not a strong requirement becauseInt64
may be used forTKey
type parameter).This is safe to implement. Not a big problem actually.
But 3rd point needs to copy
EFCoreDesigner.ttinclude
template to a project directory and remove the: Microsoft.EntityFrameworkCore.DbContext
text from it. Then in file from point 2 add something like: Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<MyUser, MyRole, string/*TKey*/, MyUserClaim, MyUserRole, MyUserLogin, MyRoleClaim, MyUserToken>
.This introduces a problem to EFDesigner extension updates management. So it would be great to implement string property in EFDesigner for setting custom base class for DbContext (like
ConnectionString
). Truth to be told it's the only crusial part of all this request :)