实体框架代码第一个子导航属性null

时间:2015-03-04 13:49:17

标签: c# .net asp.net-mvc entity-framework

我有一个使用延迟加载的实体框架代码优先版本6的项目。在模型级别,它有一个包含模块的课程。课程类声明如下:

public class Course : BaseEntity
{
    public String Title { get; set; }
    public String Description { get; set; }
    public int Revision { get; set; }

    //private IList<Module> _modules;
    //public virtual IList<Module> Modules
    //{
    //    get { return _modules ?? (_modules = new List<Module>()); }
    //    set { _modules = value; }
    //}
    public virtual ICollection<Module> Modules { get; set; }
}

我的模块类声明如下:

public class Module : BaseEntity
{
    [ForeignKey("Course")]
    public Int64 CourseID { get; set; }
    public virtual Course Course { get; set; }

    public String Title { get; set; }
    public Int32 SequenceNo { get; set; }

    public override string HumanDisplay
    {
        get { return Title; }
    }

    private IList<ModuleItem> _items;
    public virtual IList<ModuleItem> Items
    {
        get { return _items ?? (_items = new List<ModuleItem>()); }
        set { _items = value; }
    }


}

他们继承的BaseEntity类只是为了减少常见应用程序属性的代码重复。它声明了所有实体的主键:

[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int64 Id { get; set; }

如果相关,我可以在其中包含更多详细信息,但我无法看到它们如何干扰,并且那里有一堆非主题框架。

当我加载视图以编辑数据库中已存在的课程时,当在MVC视图中使用时,课程的Modules属性始终为null,即使应该有记录。例如,尽管有几个模块的课程ID为3,但编号为3的编辑课程的模块属性为空。

关系的反向导航 DOES 然而有效。如果我转到视图来编辑模块ID 5,它的Course属性按预期设置,我可以访问课程值。该模块链接到课程ID 3。

我的实体框架代码优先使用默认配置,即启用了延迟加载。我已经通过检查DBContext的调试器值来验证了这一点,但这也解释了为什么Course属性自动从Module运行到课程。

从模块到课程的关系有哪些可能的原因,而不是从课程到模块集合?

正如您从Course类中的注释代码中看到的那样,我之前通过确保Modules属性永远不为null来处理null情况,但这只适用于没有针对不是这种情况的课程的模块这里我已经尝试将其更改回基本ICollection属性以将其排除为问题。

我在调度DBContext时也进行了调试,并且肯定是在视图代码运行之后,所以在访问Collection之前没有处理DBContext的危险。

我对显式使用Include语句不感兴趣。我想使用延迟加载和EF自动获取数据,我知道这对性能等的影响。

理想情况下,除了使用上述属性之外,我不想描述与Entity Framework的关系。如果我必须这样做,但请解释为什么因为我确信我已经成功完成了这个简单的父母 - 孩子,1 - 很多关系之前没有问题。

更新1

控制器动作代码如下

public virtual ActionResult Edit(long id = 0)
    {
        if (Session.GetUser().GetCombinedPrivilegeForEntity(Entity).CanRead)
        {
            String saveSuccess = TempData["successMessage"] as String;
            currentModel = GetID(id);

            if (currentModel == null)
                throw new RecordNotFoundException(id, typeof(Model).Name);

            currentVM = Activator.CreateInstance<ViewModel>();

            currentVM.Model = currentModel;
            currentVM.DB = DB;
            currentVM.ViewMode = ViewMode.Edit;
            currentVM.SuccessMessage = saveSuccess;
            SetViewModelPermissions();

            //Cache.AddEntry(Session.SessionID, Entity, currentVM.Model.Id, currentVM);
            if (currentModel == null)
            {
                return HttpNotFound();
            }

            if (ForcePartial || Request.IsAjaxRequest())
                return PartialView(GetViewName("Edit"), currentVM);

            else
                return View(GetViewName("Edit"), MasterName, currentVM);

        }
        else
        {
            throw new PermissionException("read", Entity);
        }
    }

如果我在以下行之后进行调试并检查属性,则会按预期自动填充。

currentModel = GetID(id);

正如@Zaphod所评论的那样,在视图开始渲染之前,数据库连接似乎已关闭。我不明白为什么会发生这种情况,因为我们仍然是服务器端,还没有将标记返回给浏览器。有没有办法在视图中启用延迟加载,只在关闭控制器时关闭连接?

更新2

实际的GetID代码:

 protected virtual Model GetID(long id = 0)
 {
        return DbSet.Find(id);
 }

在Controller的构造函数中相应地设置DbSet以指向DbContext上的DbSet:

public CourseController()
        : base()
    {
        this.DbSet = DB.Courses;
    }

我无法看到DbSet如何以某种方式破坏调用视图的控制器操作的最后一行与开始运行的视图之间的查询。

更新3

我应该已经包含了在继承的控制器构造函数中创建DbContext的位置的详细信息:

public ApplicationCRUDController()
        : base()
    {
       this.DB = eLearn.Models.DbContext.CreateContext(Session);
    }

这只在继承控制器的Dispose方法中处理一次,该方法直到视图渲染后才被调用:

    protected override void Dispose(bool disposing)
    {
        if (DB != null)
            DB.Dispose();

        base.Dispose(disposing);
    }

我仍然不明白为什么反向关系场景会正常工作,因为它应该在视图中延迟加载,因为它都使用相同的框架。

2 个答案:

答案 0 :(得分:0)

看起来您正在使用通用控制器,但它没有显示实体的加载方式。如果在执行视图之前处理了连接,则无法使用延迟加载。例如,请考虑以下事项:

Course firstCourse;
using (var context = new TestContext())
{
    firstCourse = context.Courses.First();
    Console.WriteLine("Course {0} has {1} modules", firstCourse.Title,
        firstCourse.Modules.Count);
}

这很好用。重新安排事情......

Course firstCourse;
using (var context = new TestContext())
{
    firstCourse = context.Courses.First();
}
Console.WriteLine("Course {0} has {1} modules", firstCourse.Title, 
    firstCourse.Modules.Count);    // ObjectDisposedException when Modules tries to lazy load

要在MVC中完成这项工作,您有很多选择:

  • 使用包含或显式延迟加载来填充模块
  • 确保DbContext在请求结束前不会被释放(如果您正在使用IoC,例如Autofac中的RequestLifetimeScope)。
  • 使用Model / ViewModel方法,在连接打开时填充viewmodel

答案 1 :(得分:0)

在控制器框架中有一个流氓将模型对象添加到DbContext DbSet中的OnActionExecuted:

//We seem to need to add the model to the context because the context it was added on has been disposed....for some reason.
DbSet.Add(vm.Model);

采取此添加呼叫解决了问题。我仍然不明白为什么它只打破一个方向的关系。另一个问题是代码被用来修复另一个问题,以便再次被打破,但我不知道那是什么,所以只需要用它!