实体框架加载相关实体

3
我正在使用基本上只包含标准IDbSet集合的基本上代码优先Entity Framework上下文,其中T只是一个POCO类。在我的上下文中,我已禁用延迟加载。虽然我的模型类中有“导航属性”,但我已从它们中删除了virtual关键字。
存储库中的“获取全部”方法会进行一些自定义过滤,以确保当前用户只能查看他们拥有的数据,除非他们是管理员。我遇到了一个特殊问题,即我以管理员身份登录,并且还与某些记录关联。由于我登录的实体加载在上下文中,即使我已禁用了延迟加载、删除了virtual和不使用Include或Load,结果中与我的个人资料有关系的对象仍会自动设置导航属性。
这不是来自我的项目的代码,只是一个示例,说明我正在做的事情的想法。它可能有拼写错误和语法错误。
public class Record
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Owner Owner { get; set; } //No virtual keyword
    public Guid OwnerId { get; set; }
}

public class Owner
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Collection<Record> Records { get; set; } //No virtual keyword
}

public class Context : DbContext
{
    IDbSet<Owner> Owners { get; set; }
    IDbSet<Record> Records { get; set; }
    public static Context Create()
    {
        Context context = new Context();
        context.Configuration.LazyLoadingEnabled = false; //Lazy loading disabled
        return context;
    }
}

public class Repository
{
    private Context Context { get; set; }
    public Owner CurrentOwner { get; private set; }
    public Repository()
    {
        Context = Context.Create();

        //Code here to get the application user and look up an associated "owner"
        //entity if the user is an "owner" (they could just be an administrator)
        //but the GetCurrentOwnerOrNull uses the Context to find the user
        CurrentOwner = GetCurrentOwnerOrNull();
    }

    public IQueryable<Record> GetRecords(bool asAdmin)
    {
        IQueryable<Record> records = Context.Records; //Not including or loading Owner
        if (asAdmin)
        {
           //Verify that the application user is an admin and throw exception otherwise
        }
        else
        {
            if (CurrentOwner == null)
            {
                //Throw a security exception
            }
            records = records.Where(r => r.OwnerId == CurrentOwner.Id);
        }
        return records;
    }
}

所以,上述代码的问题在于,如果我以所有者身份(无论是管理员还是其他身份)运行该代码,则我拥有的那些记录将具有Owner属性设置,而不是null。我希望实体框架离开我的业务,不要自动设置这个属性。这会导致下游出现问题,特别是在以管理员和所有者身份运行代码时,因此您会得到一些Owner = null的记录和一些Owner已设置的记录。这很烦人。请帮我停止它。


作为更新,我刚刚进行了测试,如果在代码中对GetCurrentOwnerOrNull创建一个新的上下文来进行查询,然后将其处理掉,就可以解决这个问题...但是我现在仍然保持问题开放状态,以防有人知道另一种简单关闭Entity Framework中这个“特性”的方法。 - Dewey Vozel
你需要让上下文在其缓存中“持有”这些对象吗? - Erik Philips
1个回答

4
这个“bug”其实是一项特性。Entity Framework会自动地在同一上下文中连接关联,即使这些实体是独立加载的。
我们假设以下情况:
public class Person
{
  public Person()
  {
    this.Pets = new List<Pet>();
  }

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

  public virtual ICollection<Pet> Pets { get; set; }
}

public class Pet
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int PersonId { get; set; }

  public virtual Person Owner { get; set; }
}

假设这些与数据库的关联是正确的,无论是 DB-First 还是通过 Attributes/Fluent API code-first 方式。
数据库:
Persons
Id Name
1  Erik Philips

Pets
Id Name PersonId
1  Joe  1

以下是将要发生的事情:
var pet = DbContext.Pets.FirstOrDefault(id => id == 1);
var person = DbContext.Persons.FirstOrDefault(id => id == 1);

Assert.AreEqual(person.Pets.Count(), 1);
Assert.IsNotNull(pet.Person);
Assert.AreEqual(pet.Person, person);

这种情况发生的原因是上下文将保留其缓存中的对象。如果您不关心上下文保留这些对象,您需要使用 AsNoTracking()


我尝试在上下文中将ProxyCreationEnabled设置为false,但它并没有解决问题。我没有尝试使用AsNoTracking()路线,因为每次查询都要确保我指定了这个会非常麻烦,但还有其他几个实例需要检查所有权,而且它会导致问题,所以我会尝试一下。如果在上下文级别上有一个简单的设置可以关闭缓存,那就太好了,但是ProxyCreationEnabled似乎不是那个设置。 - Dewey Vozel
好的,我尝试了AsNoTracking(),它完美地解决了问题。标记为答案...谢谢! - Dewey Vozel
那真是奇怪,谢谢你告诉我。我得弄清楚两者之间的差异所在。我原本以为将ProxyCreationEnabled设置为false会导致所有对象都返回AsNoTracking()。 - Erik Philips
1
ProxyCreationEnabled 设置为 false 仅强制 EF 创建普通 POCO(因此,没有延迟加载并且在序列化时出现错误的可能性较小)。这些 POCO 仍然会被跟踪,关系修复功能也会发挥作用。 - Gert Arnold

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