DbContext requires manual load of navigation prope

2019-07-28 05:19发布

I recently upgraded my solution from EF5 to EF6.1.2, and changed my data access layer to use DbContext instead of ObjectContext.

Some of my unit tests are failing, and I don't understand why. Example of old data access code:

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);
        }
}

The Task Entity has a navigation property TaskType. As commented above, this gets set after the AddObject line.

My new code looks like so:

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);
        }
}

Unlike the old code, task.TaskType is not set, which causes an exception in ReadData. LazyLoading is true in both examples.

I can workaround this by manually reloading the TaskType:

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

but I would prefer a better solution, as I am sure there are hundreds of other places in my code where this will need to be changed and it will be difficult for me to find them all.

1条回答
Juvenile、少年°
2楼-- · 2019-07-28 05:57

Task will not load its navigation properties as these are not implemented to be lazily loaded. Take a look at your class definition, do you see any code in the getter? No.

Now, take a look at the model classes created automatically for your legacy code, is there a non empty getter that supports lazy loading? Yes, there is.

The difference is that with code-first, your model classes have no code that supports lazy loading. Lazy loading is supported only on proxy objects that are created by the context when you retrieve data from the database.

One of simplest workarounds would be to force the EF to create a proxy for you:

    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);
    }
查看更多
登录 后发表回答