我已经声明了一个这样的实体(实际的类显然也具有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具有受保护的导航属性,我应该怎么做?

最佳答案

这就是我的工作方式。

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访问器。

10-06 03:28