【问题标题】:MVC 2 and EF4 Self-tracking entities models have bad state on post backMVC 2 和 EF4 自跟踪实体模型在回发时状态不佳
【发布时间】:2011-12-11 19:26:07
【问题描述】:

我的控制器上有标准的 Create() Edit() 和 Delete() 方法,并且我正在使用 EF4 自跟踪实体。

回发编辑时,model.ChangeTracker.ChangeTracking = false 和 model.ChangeTracker.State = ObjectState.Added,尽管我在最初检索记录时确保已设置这些。

提交表单时,自跟踪实体是否没有保留 ChangeTracker 类?如果是这样,我该如何解决?

public virtual ActionResult Edit(int personId)
{
    IContext context = ContextFactory.GetContext();
    EntityRepo Repo = new EntityRepo(context);
    Person d = Repo.Person.GetById(PersonId);
    d.ChangeTracker.ChangeTrackingEnabled = true;
    return View(d);
}

[HttpPost]
public virtual ActionResult Edit(int personId, Person item)
{
    try
    {
        if (ModelState.IsValid)
        {
            IContext context = ContextFactory.GetContext();
            EntityRepo Repo = new EntityRepo(context);

            // the item is returning these properties that are wrong
            //item.ChangeTracker.ChangeTrackingEnabled = false;
            //item.ChangeTracker.State = ObjectState.Added;

            Repo.Person.Update(item);
            Repo.Person.SaveChanges();

            return RedirectToAction("Index");
        }
    }
    catch
    {
    }
    return View();
}

【问题讨论】:

  • "自跟踪实体是否没有通过回发保持 ChangeTracker 类?"我无法想象他们为什么会这样。 MVC 中没有回发。更新 GET 完全独立于更新 POST。您需要一个不假定共享状态的设计。
  • 我不是指 POSTBACK 我是指当表单提交时。
  • 我遵循了在控制器中创建 Create/Edit/Delete 视图和方法的约定。
  • 是的,ChangeTracker 显然没有保留在表单的 http 帖子上。
  • 它不能被“持久化”,因为它们是两个不同的实例。没有什么可以持久化的。您的 GET 视图模型在页面提供后立即被垃圾收集,远在 POST 发生之前。

标签: asp.net-mvc-2 entity-framework-4


【解决方案1】:

让我们从头开始。

什么是自我跟踪实体?

自我跟踪实体是即使未连接到ObjectContext 也可以进行更改跟踪的实体。当您必须更改实体但无法将其连接到 ObjectContext 时,它们很有用。

那么我什么时候想要一个,真的?

大多数情况下,当您必须有分布式对象时。例如,一个用例是当您创建一个与 Silverlight 客户端对话的 Web 服务时。但是,其他工具,如 RIA 服务可能更适合这里。另一个可能的用例是长时间运行的任务。由于 ObjectContext 旨在成为一个工作单元并且通常不应该长期存在,因此在这里使用断开连接的实体可能是有意义的。

它们对 MVC 有意义吗?

不是真的,不。

让我们更深入地研究一下,并检查在 MVC 中更新实体时会发生什么。大致流程是这样的:

  1. 浏览器发出更新页面的 GET 请求。
  2. MVC 应用程序获取一个实体,并使用它来构建一个更新的 HTML 页面。该页面提供给浏览器,并且大多数 C# 对象(包括您的实体)都被释放。此时,您可以重新启动 Web 服务器,浏览器永远不会知道其中的区别。
  3. 浏览器发出 POST 请求以更新实体。
  4. MVC 框架使用 POST 中的数据来实现传递给更新操作的编辑模型的实例。这可能恰好与实体的类型相同,但它是一个新实例。
  5. MVC 应用可以更新实体并将这些更改传回数据库。

现在,您可以通过在 HTML 表单中包含 STE 的完整状态并将其连同实体上的标量值一起 POST 回 MVC 应用程序来使自跟踪实体工作。那么自我跟踪实体至少可以工作。

但这对您有什么好处呢?浏览器显然无法将您的实体作为 C# 对象处理。因此,它不能以自我跟踪实体可以理解的方式对值得跟踪的实体进行任何更改。

【讨论】:

  • 碰巧我正在生成的 repo T4 模板使用 STE。这就是为什么选择它,而不是从设计的角度。
  • 我正在尝试以“正确”的方式将我的 T4 模板尽可能多地适应多种情况,因此它确实适用于 WPF/Silverlight,并且也可以用于 asp.net mvc,但是显然你失去了更改跟踪功能。
【解决方案2】:

你应该将原始STE保留在某个隐藏字段中。这就像您的自定义 ViewState。在提交方法中,您必须合并原始 STE 和新值。

为此使用 ActionFilterAttribute。

喜欢

    public class SerializeOriginalModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResult;

        if (viewResult == null)
            return;

        var viewModel = viewResult.ViewData.Model as ViewModel;

        if (viewModel == null || viewModel.SteObject == null)
            return;

        byte[] bytes;
        using (var stream = new MemoryStream())
        {
            var serializer = new DataContractSerializer(viewModel.SteObject.GetType());
            serializer.WriteObject(stream, viewModel.SteObject);
            bytes = stream.ToArray();
        }

        var compressed = GZipHelper.Compress(bytes);
        viewModel.SerializedSteObject = Convert.ToBase64String(compressed);
    }
}





        public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.ActionParameters == null || filterContext.ActionParameters.Count == 0)
            return;

        var viewModel  = filterContext.ActionParameters.First().Value as ViewModel;
        var serialized = filterContext.HttpContext.Request.Form["SerializedSteObject"];

        if (viewModel == null || String.IsNullOrEmpty(serialized))
            return;

        var type = filterContext.ActionParameters.First().Value.GetType().BaseType.GetGenericArguments()[0];

        var bytes = GZipHelper.Decompress(Convert.FromBase64String(serialized));
        using (var stream = new MemoryStream(bytes))
        {
            var serializer = new DataContractSerializer(type);
            viewModel.SteObject = serializer.ReadObject(stream);
        }
    }
}

【讨论】:

  • 我不同意。我认为他根本不应该使用 STE。
  • 这是解决实际问题的一种聪明方法。即使您使用 STE,Web 浏览器也无法更改其状态,因此跟踪状态对您没有任何好处。
【解决方案3】:

STE 有一个很大的缺点。您必须将它们存储在会话或视图状态(WebForms)中。所以它无非是“新版本的数据集”。如果您不存储 STE,您将有一个用于获取数据的实例和用于发布的不同实例 = 没有更改跟踪。

【讨论】:

    【解决方案4】:

    我认为您缺少存储库的想法。您不应该在存储库中有 Update 方法。提交后,您应该再次获取该项目,应用修改然后保存。

    我更喜欢在客户端和存储库之间有一个服务层。我们总是可以改变我们合并的策略。

    是的,如果您需要在请求之间保留您的 STE,请使用会话或视图状态。

    【讨论】:

      【解决方案5】:

      应该是

      Repo.Person.ApplyChanges(item);  
      Repo.Person.SaveChanges();
      

      而不是

      Repo.Person.Update(item);   
      Repo.Person.SaveChanges();
      

      Self Tracking 与 ApplyChanges 扩展方法一起使用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多