【问题标题】:Correct way to use Many2Many in EF Core6?在 EF Core6 中使用 Many2Many 的正确方法?
【发布时间】: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 已在上一条语句中配置。

标签: entity-framework


【解决方案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 对象映射并避免脚手架?
  • 视情况而定™。两者都是合理的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 1970-01-01
  • 2023-01-07
  • 1970-01-01
  • 2013-04-06
  • 2012-11-27
相关资源
最近更新 更多