EF Core 继承问题

3
我正在使用Entity Framework Core(EF Core)工作,遇到了与延迟加载和继承有关的问题。我已经定义了以下实体类:
public class Pool
{        
    public int Id { get; set; }
    public virtual Profile? Profile { get; set; }
    public int? AProfileId { get; set; }
    public int? BProfileId { get; set; }
}

public abstract class Profile
{
    public Profile() { }
    public int Id { get; set; }
}

public class AProfile : Profile
{
    public AProfile() { }
    public virtual Pool? Pool { get; set; } 
}

public class BProfile : Profile
{
    public BProfile() { }
    public virtual Pool? Pool { get; set; } 
}

public class CProfile : Profile
{
    public CProfile() { }
    // CProfile does not have the Pool property
}

// Other profile classes

在我的代码中,我使用HasOne方法配置了AProfilePool之间的关系,同样也配置了BProfile的关系。我还在OnConfiguring方法中启用了延迟加载。
以下是我在OnModelCreating方法中配置关系的方式:
protected override void OnModelCreating(ModelBuilder builder)
{    
    base.OnModelCreating(builder);
    .
    .
    .

    builder.Entity<AProfile>().HasOne(P => P.Pool).WithOne(Q => Q.Profile as AProfile).HasForeignKey<Pool>(Q => Q.AProfileId).IsRequired(false);

    builder.Entity<BProfile>().HasOne(P => P.Pool).WithOne(Q => Q.Profile as BProfile).HasForeignKey<Pool>(Q => Q.BProfileId).IsRequired(false);
}

然而,当我检索一个Pool对象并尝试访问其Profile属性时,我注意到aPool中的Profile为空,而它不应该为空。而bPool中的Profile正常工作。
var aPool = dbContext.Pools.FirstOrDefault(p => p.Id == 1);

Console.WriteLine($"Profile in aPool is \"{aPool.Profile}\"; aPool.AProfileId is {aPool.AProfileId}");

输出显示,在aPool中的Profile为空,而实际上不应该为空。而在bPool中的Profile正常工作。 在这两种情况下,AProfileId和BProfileId字段都有值。
我还尝试了另一种方式。我尝试将aProfile添加到aPool.Profile,并将aPool添加到context.Pools,但引发了以下错误:
System.InvalidOperationException:“无法将实体类型“AProfile”的实例跟踪为实体类型“BProfile”,因为这两个类型不在同一层次结构中。”
附加信息:
- .NET 6.0 - Entity Framework Core版本:6.0.21 - 数据库提供程序:Microsoft.EntityFrameworkCore.SqlServer 6.0.19 - EF Core工具版本:Microsoft.EntityFrameworkCore.Tools 6.0.19
我想知道为什么会出现这个问题,以及如何解决它。在这种继承情况下,是否需要在EF Core中进行特定的配置,以确保它按预期工作?
我希望这个操作能够将所有的个人资料类型填充到个人资料字段中。
非常感谢您提供任何见解或建议。

更新:

感谢@sorosh_sabz解决了这个问题。

解决方案摘要:

为了解决EF Core的延迟加载和继承的初始问题,我在Pool类中实现了代理属性,以处理不同的Profile类型。我在OnModelCreating方法中使用HasOneHasForeignKey配置了模型关系,允许AProfilePool之间的关联,以及BProfilePool之间的关联。虽然这个解决方案确保了正确的数据库更新,并适当设置了外键,但在其他地方检索Pool对象时,Profile字段为空。

public class Pool
{
    public int Id { get; set; }

    public Profile? Profile
    {
        get
        {
            return ORMAProfileAccessor as Profile ?? ORMBProfileAccessor;
        }

        set
        {
            switch (value)
            {
                case AProfile aProfile:
                    ORMAProfileAccessor = aProfile;
                    break;
                case BProfile bProfile:
                    ORMBProfileAccessor = bProfile;
                    break;
                default:
                    throw new InvalidCastException("Invalid profile type");
            }
        }
    }

    internal virtual AProfile? ORMAProfileAccessor { get; set; }
    internal virtual BProfile? ORMBProfileAccessor { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<AProfile>().HasOne(P => P.Pool).WithOne(Q => Q.ORMAProfileAccessor).HasForeignKey<Pool>("AProfileId").IsRequired(false);
    modelBuilder.Entity<BProfile>().HasOne(P => P.Pool).WithOne(Q => Q.ORMBProfileAccessor).HasForeignKey<Pool>("BProfileId").IsRequired(false);
    modelBuilder.Entity<Pool>().Ignore(P => P.Profile);
}

新问题:
在实施了涉及代理属性和配置EF Core中继承关系的建议解决方案后,当检索一个Pool对象时,我仍然遇到一个问题。尽管数据库中的外键是正确的,但Pool对象中的Profile字段仍然为空。然而,当单独检索一个Profile时,Profile对象中的关联Pool字段被正确填充。
在EF Core中,是否需要特定的配置或额外的步骤,以确保在检索Pool时,Profile字段会根据外键关系填充相应的Profile类型?
非常感谢您提供的任何见解、建议或替代方法。提前感谢您!

https://www.learnentityframeworkcore.com/inheritance - undefined
你忘了(或者没有展示)这个:https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy#lazy-loading-with-proxies - undefined
FYI,.NET Framework 6.0 并不存在。.NET Framework 这个术语仅适用于 .NET Framework 4.8.1 及以下版本。您正在使用的是 .NET 6,也被称为 .NET Core 6 - undefined
你应该在你的 OnModelCreating 方法中配置 TPH 继承,并确保 EF Core 知道如何区分不同的配置类型。 - undefined
@YasinARLI 你能给一些关于TPH的代码示例吗? - undefined
我在https://github.com/dotnet/EntityFramework.Docs/issues/4486上创建了相关问题。 - undefined
2个回答

2
我觉得也许使用代理属性对于这个需求来说是足够的。
我将你的Pool类修改如下。

    public class Pool
    {
        public int Id { get; set; }

        public Profile? Profile
        {
            get
            {
                return ORMAProfileAccessor as Profile ?? ORMBProfileAccessor;
            }

            set
            {
                switch (value)
                {
                    case AProfile aProfile:
                        ORMAProfileAccessor = aProfile;
                        break;
                    case BProfile bProfile:
                        ORMBProfileAccessor = bProfile;
                        break;
                    default:
                        throw new InvalidCastException("Invalid profile type");
                }
            }
        }

        internal virtual AProfile? ORMAProfileAccessor { get; set; }
        internal virtual BProfile? ORMBProfileAccessor { get; set; }
    }


并且将模型创建更改如下
            modelBuilder.Entity<AProfile>().HasOne(P => P.Pool).WithOne(Q => Q.ORMAProfileAccessor).HasForeignKey<Pool>("AProfileId").IsRequired(false);
            modelBuilder.Entity<BProfile>().HasOne(P => P.Pool).WithOne(Q => Q.ORMBProfileAccessor).HasForeignKey<Pool>("BProfileId").IsRequired(false);

            modelBuilder.Entity<Pool>().Ignore(P => P.Profile);

然后我们可以像下面这样轻松地使用池和配置文件
                var aProfile = new AProfile();
                var bProfile = new BProfile();
                var cProfile = new CProfile();

                var pool = new Pool
                {
                    Profile = bProfile
                };

                bProfile.Pool = pool;

                db.Add(aProfile);
                db.Add(bProfile);
                db.Add(cProfile);
                db.Add(pool);

                db.SaveChanges();

                var pool2 = db.Pools.FirstOrDefault();

                if (pool2 != null)
                {
                    Console.WriteLine($"Pool2: {pool2.Id}");
                    Console.WriteLine($"Pool2.Profile: {pool2.Profile?.Id}");
                }

0
感觉游泳池应该是这样的
public class Pool
{        
    public int Id { get; set; }
    public virtual BProfile? BProfile { get; set; }
    public virtual AProfile? AProfile { get; set; }
    public int? AProfileId { get; set; }
    public int? BProfileId { get; set; }
}

然后你的查询可以写成
var aPool = dbContext.Pools
               .Include(s => s.AProfile)
               .Include(s => s.BProfile)
               .FirstOrDefault(p => p.Id == 1);

1
我想在池类中创建一个通用的Profile父类。 - undefined
1
@akshay_zz 这个设计有一个主要问题,即BProfile和AProfile具有独占性存在,而这种表示方式不符合面向对象编程的原则。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接