Home > Net >  Correct way to use Many2Many in EF Core6?
Correct way to use Many2Many in EF Core6?

Time:12-11

I am quite new to EF Core 6.0. We currently have a projet to upgrade, we cannot change the actual tables (use by another program) so we use Database fisrt approch. So I need to add some Permission on user (the database are in french) We curently have an UsagerEW table (user table) and we add an Permission Table and an joint table PermissionUsagerEW for the Many2Many. After doing Scaffold-dbContect here is the result:

UsagerEW (primary key is Code_Int)

public partial class UsagerEW
    {
        public UsagerEW()
        {
            PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
            RefreshToken = new HashSet<RefreshToken>();
        }

        public string Code { get; set; }
        public string Nom { get; set; }
        public string Email { get; set; }
        public string ModeLogin { get; set; }
        public string PasswordTemp { get; set; }
        public DateTime? PasswordTempExp { get; set; }
        public int code_int { get; set; }
        

        public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }

    }

Pemrssion and PermissionUsagerEW

    public partial class Permission
    {
        public Permission()
        {
            PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
        }

        public int id { get; set; }
        public string code { get; set; }
        public string description { get; set; }
        public int? moduleId { get; set; }

        public virtual Module module { get; set; }
        public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
    }

    public partial class PermissionUsagerEW
    {
        public int id { get; set; }
        public int permissionId { get; set; }
        public int usagerCodeInt { get; set; }

        public virtual Permission permission { get; set; }
        public virtual UsagerEW usagerCodeIntNavigation { get; set; }
    }

That compile and I can "navigate with include" from UsagerEW and get an list of PermissionUsagerEW for a specific UsagerEW.

Now like I am in EF COre 6.0 that supposed to support Many2Many I add this nav propertie in the Permnission class

public virtual ICollection<UsagerEW> UsagerEW { get; set; }

and this in the UsagerEW class:

public virtual ICollection<Permission> Permission { get; set; }

But I got execution error either I just try to load some user wintout any include:

UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).SingleOrDefault();

System.InvalidOperationException: 'Cannot use table 'PermissionUsagerEW' for entity type 'PermissionUsagerEW (Dictionary<string, object>)' since it is being used for entity type 'PermissionUsagerEW' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'PermissionUsagerEW (Dictionary<string, object>)' on the primary key properties and pointing to the primary key on another entity type mapped to 'PermissionUsagerEW'.'

The FK are detect by the scaffold:

            modelBuilder.Entity<PermissionUsagerEW>(entity =>
            {
                entity.HasOne(d => d.permission)
                    .WithMany(p => p.PermissionUsagerEW)
                    .HasForeignKey(d => d.permissionId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_PermissionUsager_Permission");

                entity.HasOne(d => d.usagerCodeIntNavigation)
                    .WithMany(p => p.PermissionUsagerEW)
                    .HasForeignKey(d => d.usagerCodeInt)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_PermissionUsager_Usager");
            });

Any idea?

---EDIT 1 I change your code to reflect the scaffolded PermissionUsagerEW table:

            //--UsagewrEW
            modelBuilder.Entity<UsagerEW>()
                .HasKey(u => u.code_int);

            modelBuilder.Entity<UsagerEW>()
                .HasMany(u => u.Permissions)            
                .WithMany(p => p.Users)
                .UsingEntity<PermissionUsagerEW>(
                    p => p.HasOne(e => e.permission)
                        .WithMany()
                        .HasForeignKey(e => e.permissionId),
                    p => p.HasOne(p => p.usagerCodeIntNavigation)
                        .WithMany()
                        .HasForeignKey(e => e.usagerCodeInt)
                );

            modelBuilder.Entity<PermissionUsagerEW>()
                .HasOne(p => p.usagerCodeIntNavigation)
                .WithMany()
                .HasForeignKey(p => p.usagerCodeInt);

When testing with UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();

Now I got this error:

Microsoft.Data.SqlClient.SqlException: 'Invalid column name 'UsagerEWcode_int'.'

I think EF tries to link something automatically. I do not have any UsagerEWcode_int in my solution.

EDIT2: There is the SQL generated. Wierd column name and some repetition...

SELECT [u].[code_int], [u].[Administrateur], [u].[Code], [u].[Email], [u].[EmpContact], [u].[Inactif], [u].[KelvinConfig], [u].[LectureSeule], [u].[ModeLogin], [u].[Nom], [u].[ParamRole], [u].[Password], [u].[PasswordTemp], [u].[PasswordTempExp], [u].[RestreintCommContrat], [u].[RestreintProjet], [u].[Role], [u].[UsagerAD], [u].[doitChangerPW], [u].[estSuperviseur], [u].[idSuperviseur], [u].[infoSession], [u].[paramRole2], [u].[permsGrps], [t].[id], [t].[Permissionid], [t].[UsagerEWcode_int], [t].[permissionId0], [t].[usagerCodeInt], [t].[id0], [t].[code], [t].[description], [t].[moduleId]
FROM [UsagerEW] AS [u]
LEFT JOIN (
    SELECT [p].[id], [p].[Permissionid], [p].[UsagerEWcode_int], [p].[permissionId] AS [permissionId0], [p].[usagerCodeInt], [p0].[id] AS [id0], [p0].[code], [p0].[description], [p0].[moduleId]
    FROM [PermissionUsagerEW] AS [p]
    INNER JOIN [Permission] AS [p0] ON [p].[permissionId] = [p0].[id]
) AS [t] ON [u].[code_int] = [t].[usagerCodeInt]
WHERE [u].[Code] = @__usagerId_0
ORDER BY [u].[code_int], [t].[id]

CodePudding user response:

You can configure direct Many-to-Many relationships with an existing database, and you can have the linking entity in the model or exclude it. There are several examples in the docs. And you can leave the foreign key properties in the model, or you can replace them with shadow properties. But the Scaffolding code doesn't do any of this for you. It creates the simplest correct model for the database schema.

Also you usually should rename the entities and properties to align with .NET coding conventions.

Anyway something like this should work:

public partial class UsagerEW
{

    public string Code { get; set; }
    public string Nom { get; set; }
    public string Email { get; set; }
    public string ModeLogin { get; set; }
    public string PasswordTemp { get; set; }
    public DateTime? PasswordTempExp { get; set; }
    public int code_int { get; set; }


    public virtual ICollection<Permission> Permissions { get;  } = new HashSet<Permission>();

}
public partial class Permission
{

    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public int? ModuleId { get; set; }

    //public virtual Module module { get; set; }
    public virtual ICollection<UsagerEW> Users { get; } = new HashSet<UsagerEW>();
}

public partial class PermissionUsagerEW
{
    public int Id { get; set; }
    public int PermissionId { get; set; }
    public int UsagerCodeInt { get; set; }
    public virtual Permission Permission { get; set; }
    public virtual UsagerEW User { get; set; }
}

public class Db : DbContext
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<UsagerEW>()
            .HasKey(u => u.code_int);

        builder.Entity<UsagerEW>()
            .HasMany(u => u.Permissions)
            .WithMany(p => p.Users)
            .UsingEntity<PermissionUsagerEW>(
              p => p.HasOne(e => e.Permission)
                    .WithMany()
                    .HasForeignKey(e => e.PermissionId),
              p => p.HasOne(p => p.User)
                    .WithMany()
                    .HasForeignKey( e => e.UsagerCodeInt)
            ); 


        builder.Entity<PermissionUsagerEW>()
            .HasOne(p => p.User)
            .WithMany()
            .HasForeignKey(p => p.UsagerCodeInt);

        foreach (var prop in builder.Model.GetEntityTypes().SelectMany(e => e.GetProperties()))
        {
            prop.SetColumnName(char.ToLower(prop.Name[0])   prop.Name.Substring(1));
        }
        base.OnModelCreating(builder);
    }

But when you're working in a database-first workflow, there's a downside to deeply customizing the EF model: you loose the ability to regenerate the EF model from the database.

So you can use a "nice" customized EF model, or a "plain" scaffolded model. If you customize the model, you can no longer regenerate it, and need to alter it to match future database changes by hand.

You can apply some customizations, though, like the convention-based property-to-column and entity-to-table mappings in the example. But changing the generated "indirect many-to-many" to "direct many-to-many" will prevent you from regenerating the EF model through scaffolding.

  • Related