DbContext 需要手动加载导航属性

本文关键字:加载 导航 属性 DbContext | 更新日期: 2023-09-27 18:37:16

我最近将解决方案从 EF5 升级到 EF6.1.2,并将数据访问层更改为使用 DbContext 而不是 ObjectContext。

我的一些单元测试失败了,我不明白为什么。旧数据访问代码示例:

public virtual T Insert(T item)
{
        if (item == null)
        {
            throw new ArgumentNullException("item", @"TaskDal.Insert");
        }
        using (var ctx = ObjectContextManager<StoreDataContext>.GetManager("StoreDataContext"))
        {
            var task = new Task();
            WriteNonKeyData(task, item);
            ctx.ObjectContext.Tasks.AddObject(task); // task.taskType null
            ctx.ObjectContext.SaveChanges(); // task.TaskType set
            return ReadData(task);
        }
}

Task实体具有导航属性TaskType 。如上所述,这是在 AddObject 行之后设置的。

我的新代码如下所示:

public virtual T Insert(T item)
{
        if (item == null)
        {
            throw new ArgumentNullException("item", @"TaskDal.Insert");
        }
        using (var ctx = DbContextManager<StoreDataContext>.GetManager())
        {
            var task = new Task();
            WriteNonKeyData(task, item);
            ctx.DbContext.Tasks.Add(task); // task.TaskType null
            ctx.DbContext.SaveChanges(); // task.TaskType still null
            return ReadData(task);
        }
}

与旧代码不同,未设置task.TaskType,这会导致 ReadData 中的异常。在这两个示例中,LazyLoading 都是正确的。

我可以通过手动重新加载TaskType来解决此问题:

if (task.TaskType == null)
    ctx.DbContext.Entry(task).Reference(p => p.TaskType).Load();

但我更喜欢更好的解决方案,因为我确信我的代码中有数百个其他地方需要更改,我很难找到它们。

DbContext 需要手动加载导航属性

Task不会加载其导航属性,因为这些属性不是为了延迟加载而实现的。看看你的类定义,你在getter中看到任何代码吗?不。

现在,看看为您的遗留代码自动创建的模型类,是否有支持延迟加载的非空 getter?是的,有。

不同之处在于,使用代码优先时,模型类没有支持延迟加载的代码。仅当您从数据库中检索数据时由上下文创建的代理对象上,才支持延迟加载。

最简单的解决方法之一是强制 EF 为您创建代理:

    using (var ctx = DbContextManager<StoreDataContext>.GetManager())
    {
        var task = new Task();
        WriteNonKeyData(task, item);
        ctx.DbContext.Tasks.Add(task); // task.TaskType null
        ctx.DbContext.SaveChanges(); // task.TaskType still null
        // let ef create a proxy for the very same database object
        var ptask = ctx.DbContext.Tasks.First( p => p.ID == task.ID );
        // ptask.TaskType is now available as the actual type of
        // ptask is not Task but rather a TaskProxy that inherits from Task
        // and is created automatically by ef
        return ReadData(ptask);
    }