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

Posted

技术标签:

【中文标题】在 EF Core6 中使用 Many2Many 的正确方法?【英文标题】:Correct way to use Many2Many in EF Core6? 【发布时间】:2022-01-10 04:36:36 【问题描述】:

我对 EF Core 6.0 还是很陌生。我们目前有一个要升级的项目,我们无法更改实际的表(由另一个程序使用),所以我们使用数据库优先方法。 所以我需要为用户添加一些权限(数据库是法语)我们目前有一个UsagerEW表(用户表),我们为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 类中添加了这个导航属性

public virtual ICollection<UsagerEW> UsagerEW  get; set; 

这在 UsagerEW 类中:

public virtual ICollection<Permission> Permission  get; set; 

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

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

System.InvalidOperationException: '不能使用表 实体类型“PermissionUsagerEW”的“PermissionUsagerEW” (Dictionary)' 因为它被用于实体类型 'PermissionUsagerEW' 和可能的其他实体类型,但有 没有链接关系。将外键添加到 'PermissionUsagerEW (Dictionary)' 在主键属性和 指向映射到的另一个实体类型上的主键 '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 表:

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

当使用 UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();

现在我收到了这个错误:

Microsoft.Data.SqlClient.SqlException: '无效的列名 'UsagerEWcode_int'。'

我认为 EF 会尝试自动链接某些内容。我的解决方案中没有任何 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]

【问题讨论】:

这个modelBuilder.Entity&lt;PermissionUsagerEW&gt;() 是不必要的,并且会导致虚假列。 p =&gt; p.usagerCodeIntNavigation 已在上一条语句中配置。 【参考方案1】:

您可以配置与现有数据库的直接多对多关系,并且可以在模型中包含链接实体或将其排除。 docs 中有几个例子。您可以将外键属性保留在模型中,也可以将它们替换为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 模型,或“普通”的脚手架模型。如果您自定义模型,则无法再重新生成它,需要手动对其进行更改以匹配未来的数据库更改。

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

【讨论】:

我同意我需要更改实体以匹配 .net 约定。但是当我需要多次搭建脚手架时,我怎么能做到这一点。现在我获得了用于添加一些属性的部分类,并覆盖了使用 Fluent 进行调优的上下文。但是,例如,如果 EF Scaffold 创建了一个导航属性,例如 usagerCodeInNavigation,因为 UsagerEW 没有 id 字段。我怎样才能覆盖它? 这是一个权衡。查看更新的答案。 感谢您的回答。我尝试你的代码,但我得到一个无效的列错误,检查我的编辑。 我了解您更新的答案。所以,对于一些不遵循 EF 规则的 OldDB,数据库优先的方式比较复杂。在这种情况下,像您这样的专家是什么?手动使用实体和 Fluent 代码创建旧数据库的 EF 对象映射并避免脚手架? 视情况而定™。两者都是合理的方法。

以上是关于在 EF Core6 中使用 Many2Many 的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章

Fedora Core6 DVD盘安装问题

.net core6 检测ip网址是否可访问。不需要端口号。

版本发布公告HMS Core6.5.0来啦

如何根据在另一个模型中的 Many2many 字段中添加或删除数据自动在模型中创建记录

如何从gorm中的many2many表中获取对象

在Odoo中的Many2Many字段插入请求在android中不起作用