【问题标题】:Doesn't Entity Framework support protected navigation properties?实体框架不支持受保护的导航属性吗?
【发布时间】:2016-07-15 12:29:44
【问题描述】:

我已经声明了一个这样的实体(实际的类显然也有一个 ID 属性,映射完成等,但这不是问题,所以我在这里跳过它):

public class Parent
{
    public virtual ICollection<Child> Children {get; set;}
}

这很完美:

public class Consumer
{
    void DoBusiness()
    {
        using (var ctx = new MyDbContext())
        {
            var entity = ctx.Parents.Find(keyOfParent);
            // This is as expected: entity.Children refers to a collection which 
            // Entity Framework has assigned, a collection which supports lazy loading.
        }
    }
}

现在我更改要保护的 Children 集合的可见性:

public class Parent
{
    protected virtual ICollection<Child> Children {get; set;}
}

这带来了意想不到的结果:

public class Consumer
{
    void DoBusiness()
    {
        using (var ctx = new MyDbContext())
        {
            var entity = ctx.Parents.Find(keyOfParent);
            // This is NOT as expected: entity.Children is null. I would expect, that it 
            // had been referring to a collection which Entity Framework would have been 
            // assigning, a collection which should support lazy loading.
        }
    }
}

此外,如果我处于受保护儿童的情况,请尝试通过以下方式显式加载儿童:

ctc.Entry(entity).Collection(x => x.Children)

然后我得到这个异常:

“Parent”类型的属性“Children”不是导航属性。Reference 和 Collection 方法只能与导航属性一起使用。请使用 Property 或 ComplexProperty 方法。

因此:为了使用 Entity Framework 获得受保护的导航属性,我应该具体做什么?

【问题讨论】:

  • 你到底为什么要这么做? EF 中的“实体”只不过是数据库表、记录和关系的对象表示。它们不应该包含业务逻辑。因此,它们和它们的成员应该是可见的,以便在查询中使用。示例代码ctc.Entry(entity).Collection(x =&gt; x.Children) 只能从违反这些原则的Parent 类内部运行。
  • 因为与示例中的 Children 相对应的具体属性将仅通过反射使用,因此您给出的有关查询的基本考虑不适用。关于您的编码评论:是的,很明显,但它强调实体框架在属性受到保护后不会将其视为导航属性。请问您也有一些贡献信息吗?
  • 如果属性标记为protected,框架将如何使用该属性并加载它?它无法“看到”它;只有子类可以。
  • 通过实际实现它,如下所示:owencraig.com/…。当我加载 Parent 时,我的 Children 集合仍然为空,这就是我觉得奇怪的地方。你知道怎么解决吗?
  • 链接失效了,能重新发一下吗?建设性地说,我尝试设置这样的模型和Children,并且能够使其既懒惰又显式加载。 Child 类中是否有类似 Parent Parent { get; set; } 的反向导航属性?也受到保护?

标签: c# entity-framework


【解决方案1】:

这是我的工作方式。

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }

    internal ICollection<Child> children;
    protected virtual ICollection<Child> Children { get { return children; } set { children = value; } }
    internal ICollection<Child> GetChildren() => Children;
    internal static Expression<Func<Parent, ICollection<Child>>> ChildrenSelector => p => p.Children;
}

public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }

    internal Parent parent;
    protected virtual Parent Parent { get { return parent; } set { parent = value; } }
    internal Parent GetParent() => Parent;
    internal static Expression<Func<Child, Parent>> ParentSelector => c => c.Parent;
}

public class MyDbContext : DbContext
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Parent>()
            .HasMany(Parent.ChildrenSelector)
            .WithRequired(Child.ParentSelector)
            .Map(a => a.MapKey("ParentId"));

        base.OnModelCreating(modelBuilder);
    }
}

使用的显式字段不是必需的 - 我将它们放置是为了能够查看其中的内容,您可以继续使用自动属性。

基本部分是提供属性访问器表达式并将它们与 Fluent API 配置一起使用。如果没有显式配置,您将获得所描述的行为。有了它,一切正常。

例如延迟加载:

var parent = ctx.Parents.Find(keyOfParent);
var children = parent.GetChildren();

或显式加载:

var parent = ctx.Parents.Find(keyOfParent);
db.Entry(parent).Collection("Children").Load();
var children = parent.children;

更新:很奇怪,如果我们替换配置代码

modelBuilder.Entity<Parent>()
    .HasMany(Parent.ChildrenSelector)
    .WithRequired(Child.ParentSelector)
    .Map(a => a.MapKey("ParentId"));

完全等价的定义

modelBuilder.Entity<Child>()
    .HasRequired(Child.ParentSelector)
    .WithMany(Parent.ChildrenSelector)
    .Map(a => a.MapKey("ParentId"));

数据库表和FK是一样的,但是加载不行!因此,要么工作解决方案不小心撞到后门,要么 EF 中存在错误。在这两种情况下,该功能对我来说似乎都有问题,我会简单地使用 public 访问器以避免意外。

【讨论】:

  • 非常感谢,伊万。你真的很好,很有建设性!您不能稍微更改模型的定义,即将您的模型替换为: modelBuilder.Entity().HasRequired(Child.ParentSelector).WithMany(Parent.ChildrenSelector).Map(a => a.MapKey( “父母身份”));看看代理的东西是否仍然有效?这两种方法不应该是多余的吗?您使用 Map 而不是 HasForeignKey 是否真的有原因?
  • HasForeignKey 是当您在模型中有显式字段时。例如如果Child 类具有public int ParentId { get; set; },那么您将使用HasForeignKey(c =&gt; c.ParentId)。而MapKey 用于当您没有这样的显式字段并且只是为该字段指定表列名称时。它是可选的,如果你不包括它,你会默认得到“Parent_Id”。配置非常重要,具体取决于您在实体类中明确包含哪些字段。
  • 正确的配置对于使解决方案正常工作至关重要。最初我没有包括配置,并且得到的行为与您在帖子中解释的完全相同。
  • 是的,但实际上您在 Child 中有一个明确的 Id 字段,但使用的是 Map 而不是 HasForeignKey,这让我感到疑惑。您不能尝试按照我之前评论中的建议配置映射,即从子配置映射而不是从父配置,因为它实际上对我不起作用?我觉得这很奇怪,因为我认为这两种方法是等效的,还是我忽略了一些东西?你知道是否应该同时存在这两个映射?
  • modelBuilder.Entity&lt;Child&gt;().HasRequired(Child.ParentSelector).WithMany(Parent.C‌​hildrenSelector).Map(a =&gt; a.MapKey("ParentId")); 也有效,因为它与我使用的相同。配置关联时,您可以使用其中一种方式或另一种方式。无需同时拥有它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-17
  • 2016-03-29
  • 2016-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多