在 EF Core6 中使用 Many2Many 的正确方法?

Correct way to use Many2Many in EF Core6?

我是 EF Core 6.0 的新手。我们目前有一个项目要升级,我们无法更改实际的 tables(由另一个程序使用)所以我们使用数据库 fisrt approch。 所以我需要在用户上添加一些权限(数据库是法语的)我们目前有一个 UsagrEW table(用户 table),我们添加一个权限 Table 和一个联合 table Many2Many 的 PermissionUsagerEW。在执行 Scaffold-dbContect 后,结果如下:

UsagerEW(主键是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 和 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; }
    }

编译后我可以从 UsagerEW 中“使用包含导航”并获取特定 UsagerEW 的 PermissionUsagerEW 列表。

现在就像我在应该支持 Many2Many 的 EF CORe 6.0 中一样 我在 Permnission class

中添加了这个导航属性
public virtual ICollection<UsagerEW> UsagerEW { get; set; }

这在 UsageREW class:

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

但是我遇到了执行错误或者我只是尝试加载一些用户 wintout 任何包括:

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'.'

FK 被脚手架检测到:

            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");
            });

有什么想法吗?

---编辑1 我更改了您的代码以反映脚手架 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);

测试时 UserEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();

现在我得到这个错误:

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

我认为 EF 会尝试 link 自动执行某些操作。我的解决方案中没有任何 UsagerEWcode_int。

编辑2: 生成了SQL。奇怪的列名和一些重复...

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]

您可以配置与现有数据库的直接多对多关系,并且可以在模型中包含链接实体或将其排除。 docs. And you can leave the foreign key properties in the model, or you can replace them with shadow properties中有几个例子。但是脚手架代码不会为您做任何这些。它为数据库模式创建了最简单的正确模型。

此外,您通常应该重命名实体和属性以符合 .NET 编码约定。

无论如何这样的事情应该有效:

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);
    }

但是当您在数据库优先的工作流中工作时,深度自定义 EF 模型有一个缺点:您失去了从数据库重新生成 EF 模型的能力。

因此,您可以使用“不错”的自定义 EF 模型,或“普通”的脚手架模型。如果自定义模型,则无法再重新生成它,需要手动更改它以匹配将来的数据库更改。

不过,您可以应用 一些 自定义,例如基于约定的 属性 到列和实体到 table 映射例子。但是把生成的“间接多对多”改成“直接多对多”会阻止你通过脚手架重新生成EF模型