2

I am trying to create a many to many relationship using EFCore on the same user class which I have based on EntityFrameworkCore.IdentityUser following the instructions described here, here and here. I have the following user class:

public class MyApplicationUser : IdentityUser
{
    public virtual ICollection<MyApplicationUserJunction> MyApplicationUsers { get; set; }
    public virtual ICollection<MyApplicationUserJunction> ManagingMyApplicationUsers { get; set; }
}

Here is my joining table:

public class MyApplicationUserJunction
{
    public int MyApplicationUserId { get; set; }
    public virtual MyApplicationUser MyApplicationUser { get; set; }
    public int ManagedMyApplicationUserId { get; set; }
    public virtual MyApplicationUser ManagedMyApplicationUser { get; set; }
}

Here is my OnModelConfiguring:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.Entity<MyApplicationUserJunction>()
        .HasKey(uj => new { uj.ManagedMyApplicationUserId, uj.MyApplicationUserId});

    builder.Entity<MyApplicationUserJunction>()
        .HasOne(uj => uj.MyApplicationUser)
        .WithMany(qu => qu.MyApplicationUsers)
        .HasForeignKey(uj => uj.MyApplicationUserId).OnDelete(DeleteBehavior.Restrict);

    builder.Entity<MyApplicationUserJunction>()
        .HasOne(uj => uj.ManagedMyApplicationUser)
        .WithMany(qu => qu.ManagingMyApplicationUsers)
        .HasForeignKey(uj => uj.ManagedMyApplicationUserId);
}

Whenever I run the EFCore tools add migrations command, I get the following error:

The relationship from 'MyApplicationUserJunction.MyApplicationUser' to 'MyApplicationUser.MyApplicationUsers' with foreign key properties {'MyApplicationUserId' : int} cannot target the primary key {'Id' : string} because it is not compatible. Configure a principal key or a set of compatible foreign key properties for this relationship.

I am using the EntityFrameworkCore.Tools.DotNet package 1.0.0-preview3-final.

I have tried only using a single navigation property on the User class, and I've tried not specifying the WithMany function as described here but with no success.

majjam
  • 1,286
  • 2
  • 15
  • 32
  • 1
    Read the docs for [IdentityUser.Id](https://msdn.microsoft.com/library/microsoft.aspnet.identity.entityframework.identityuser.id(v=vs.111).aspx) and have a look at the type. The foreign key property has to be the same type – Sir Rufo Sep 10 '17 at 22:17
  • Thank you! I'll look at using int as the primary key in UserIdentity. – majjam Sep 10 '17 at 22:52
  • Have you managed to get it working? if yes please answer the question to share the knowledge. – Mohammed Noureldin Dec 14 '17 at 05:11
  • 1
    Apologies @MohammedNoureldin I did get it working, I'll post an answer later today. – majjam Dec 14 '17 at 10:18
  • @MohammedNoureldin answered, please let me know if I've missed anything, its been a while since I made those changes – majjam Dec 14 '17 at 22:36

1 Answers1

2

To get this working with EF Core 1, following Sir Rufo's suggestion above I specified the type of the TKey:

public class MyApplicationUser : IdentityUser<int>
{
    public virtual ICollection<MyApplicationUserJunction> MyApplicationUsers { get; set; }
    public virtual ICollection<MyApplicationUserJunction> ManagingMyApplicationUsers { get; set; }
}

and in the context:

public class MyApplicationContext : IdentityDbContext<MyApplicationUser, IdentityRole<int>, int>
{
    ...

and in the AddIdentity section in ConfigureServices which also uses the generic IdentityRole:

services.AddIdentity<MyApplicationUser, IdentityRole<int>>(config =>
        {
            config.User.RequireUniqueEmail = true;
            config.Password.RequiredLength = 8;
            config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
        })
        .AddEntityFrameworkStores<MyApplicationContext, int>();

I then upgraded this to EF Core 2 which require some other alterations to get the Identity working. As described by pealmeid here you have to derive from the IdentityRole:

public class MyRole : IdentityRole<int>
{
    public MyRole() : base()
    {
    }

    public MyRole(string roleName)
    {
        Name = roleName;
    }
}

Used in ConfigureServices:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentity<MyApplicationUser, MyRole>(config =>
            {
                config.User.RequireUniqueEmail = true;
                config.Password.RequiredLength = 8;
            }).AddEntityFrameworkStores<MyApplicationContext>();
            ...

I was also using cookie authentication before which is no longer configured in the AddIdentity config lambda but is instead done at the services level:

public void ConfigureServices(IServiceCollection services)
    {
        services.ConfigureApplicationCookie(options => options.LoginPath = "/Auth/Login");
        ...

Also a slight change to the DbContext signature to use the derived role type:

public class MyApplicationContext : IdentityDbContext<MyApplicationUser, MyRole, int>
{
majjam
  • 1,286
  • 2
  • 15
  • 32