如何在实体框架模型中使用通用导航属性?

Posted

技术标签:

【中文标题】如何在实体框架模型中使用通用导航属性?【英文标题】:How am I able to use a generic Navigation property in a Entity Framework Model? 【发布时间】:2021-12-08 15:43:51 【问题描述】:

我在运行时创建了 2 个 poco 类型(使用反射)。这两个应该互相引用。这些 pocos 的后续实例可以使用实体框架存储在数据库中。

目前我面临两个问题:

    这是不可能的,或者至少我不知道如何编写这种双向引用(因为在描述 Poco 时,另一个 poco 的类型不存在)。

2.由于我没有找到问题 1 的答案,所以我决定使用 object 作为引用的类型。所以模型现在包含以下行:

$ 公共对象 Poco1 get;放; 并且:

public object Poco2 get; set;

对象的使用现在让我面临另一个问题。因为,在 OnModelCreating 期间会引发异常,该对象需要包含一个 id。

据我所知,这意味着 ef core 认为,“对象”将是模型的类型,应该被引用。

有人知道我可以如何做我想做的事吗?

谢谢:)

【问题讨论】:

如果它们不符合已知的类定义,以后如何存储到数据库中?不同的表不只是随意地相互引用。 (至少在任何不承诺为每个查询运行表扫描的(非)关系数据库中) 【参考方案1】:

每当我遇到涉及泛型和反射的问题时。我发现以通用方法解决问题的通用部分会更容易;

public class Parent<TChild> where TChild : class

    public ICollection<TChild> Children  get; set; 

public class Child<TParent> where TParent : class

    public TParent Parent  get; set; 


public void DefineRelationship<TParent, TChild>(ModelBuilder modelBuilder) 
    where TChild : Child<TParent>
    where TParent : Parent<TChild>

    modelBuilder.Entity<TParent>()
        .HasMany(p => p.Children)
        .WithOne(c => c.Parent);

现在您需要使用反射来调用具有正确类型的方法。

【讨论】:

非常感谢,太好了。祝你有美好的一天。【参考方案2】:

您的实体应如下所示:

public class Poco1

  public int Id get; private set;
  ...
  public Poco2 Poco2 get; private set;
  
  public void SetPoco2(Poco2 poco2)
  
    Poco2 = poco2;
  

public class Poco2

  public int Id get; private set;
  ...
  public int Poco1Id get; private set;
  public Poco1 Poco1 get; private set;

然后设置它们:

async Task SomeMethod()

  var poco1 = new Poco1();
  var poco2 = new Poco2();
  poco1.SetPoco2(poco2);

  //at first only poco1 has reference to poco2
  //and poco2 does not have reference to poco1 yet
  Debug.Assert(poco1.Poco2 == poco2);
  Debug.Assert(poco2.Poco1 != poco1);


  await _someRepository.AddAsync(poco1);
  await _someRepository.SaveChnagesAsync();

  //After saving changes EF core manages the primary keys and references
  Debug.Assert(poco1.Poco2 == poco2);
  Debug.Assert(poco2.Poco1 == poco1);

和模型构建器:

protected override void OnModelCreating(ModelBuilder modelBuilder)

  var poco1Builder = modelBuilder.Entity<Poco1>();
  poco1Builder.HasKey(x => x.Id);
  poco1Builder
    .HasOne(x => x.Poco2)
    .WithOne(x => x.Poco1)
    //Poco2 will have Poco1Id in db that will be used for reference
    //Also Poco1Id does not have to be set manually, EF core takes care of that
    .HasForeignKey<Poco2>(x => x.Poco1Id);
  ;

  var poco12Builder = modelBuilder.Entity<Poco2>();

如果在保存到数据库之前需要参考 SetPoco2 可以修改为:

public void SetPoco2(Poco2 poco2)

  Poco2 = poco2;
  poco2.SetPoco1(this);

并在 Poco2 中设置方法:

public void SetPoco1(Poco1 poco1)

  Poco1 = poco1;

对于可维护性差的大型项目而言,过多的引用是危险的

【讨论】:

以上是关于如何在实体框架模型中使用通用导航属性?的主要内容,如果未能解决你的问题,请参考以下文章

实体框架Linq查询:如何在多个导航属性上从何处选择并从第三个导航属性中选择

如何使用代码优先实体框架在 ASP.Net MVC3 中重新加载多对多导航属性

实体框架对导航属性的约束

如何修复实体框架中的“无法确定导航属性表示的关系”错误

C#,Winform绑定实体框架(Entity Framework)的实体,如何去掉或隐藏导航属性?

如何在实体框架中包含排序的导航属性[重复]