【问题标题】:Cleanly updating a hierarchy in Entity Framework干净地更新实体框架中的层次结构
【发布时间】:2015-09-04 18:43:57
【问题描述】:

我正在为StoredProcedureReport 提交一个表单,其中包含许多StoredProcedureParameters。 Create 工作正常,但尝试更新让我问微软是否可以认真对待。

我来自 Rails 背景,@report.update_attributes(params[:report]) 将确切地知道如何处理它在其中找到的任何关联数据。据我所知,与此等效的 .NET 是 TryUpdateModel,看起来很有希望。首先。所以我尝试了一些这样的参数

IDGUID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureName:GetUsers
Name:search again UPDATED
StoredProcedureReportParameters[0].IDGUID:d70008a5-aba3-7560-a6ef-30a5524fac72
StoredProcedureReportParameters[0].StoredProcedureReportID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureReportParameters[0].Name:RowsPerPage
StoredProcedureReportParameters[0].Label:rows
StoredProcedureReportParameters[0].StoredProcedureReportParameterDataTypeID:a50008a5-2755-54c0-b052-865abf459f7f
StoredProcedureReportParameters[0].StoredProcedureReportParameterInputTypeID:a50008a5-2955-a593-d00f-00cd4543babf
StoredProcedureReportParameters[0].DefaultValue:10
StoredProcedureReportParameters[0].AllowMultiple:false
StoredProcedureReportParameters[0].Hidden:false
StoredProcedureReportParameters[1].IDGUID:d70008a5-a7a3-e35e-28b6-36dd9e448ee5
StoredProcedureReportParameters[1].StoredProcedureReportID:d70008a5-a1a3-03d2-7baa-e39c5044ad41
StoredProcedureReportParameters[1].Name:PageNumber
StoredProcedureReportParameters[1].Label:page was MODIFIEIIEIEIED!!!
StoredProcedureReportParameters[1].StoredProcedureReportParameterDataTypeID:a50008a5-2755-54c0-b052-865abf459f7f
StoredProcedureReportParameters[1].StoredProcedureReportParameterInputTypeID:a50008a5-2955-a593-d00f-00cd4543babf
StoredProcedureReportParameters[1].DefaultValue:1
StoredProcedureReportParameters[1].AllowMultiple:false
StoredProcedureReportParameters[1].Hidden:false

我假设在设置了所有主键和外键后,EF 会知道如何在我执行此操作时更新 StoredProcedureReportParameter 对象:

var report = context.StoredProcedureReports.FirstOrDefault(r => r.IDGUID == reportID);

if (report != null)
{
    succeeded = TryUpdateModel(report);
    context.SaveChanges();
}

现在,如果我在context.SaveChanges() 上放置一个断点,我的report 对象及其关联的StoredProcedureReportParameters 看起来就像我期望的那样。设置了外键和主键,所有值都被检查出来。但是SaveChanges 引发了这个错误:

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

此消息中的一个建议是我应该为外键属性分配一个非空值,但正如我所说,StoredProcedureReportID 具有两个StoredProcedureReportParameter 的正确值对象。

我读过的其他帖子处理Update 操作循环关联并将它们附加到上下文。这真的是我一直在做的事情吗? EF真的那么密集吗?我希望 .NET 专业人士在这里向我展示光明。 必须有比这更简单的方法。

【问题讨论】:

  • 你能显示准确的c#代码吗?

标签: asp.net-mvc entity-framework updates crud


【解决方案1】:

这并不难。使用 EF 有两种主要的工作方式:附加实体和分离实体。

假设我们有 2 个实体:

public class Foo
{
     public int FooId { get; set; }

     public string Description { get; set; }

     public ICollection<Bar> Bars { get; set; }
 }

public class Bar
{
    public int BarId { get; set; }

    public string Description { get; set; }
}

插入

var foo = new Foo()
{
    FooId = 1,
    Description = "as",
    Bars = new List<Bar>()
    {
        new Bar
        {
            BarId = 1,
            Description = "as"
        },
        new Bar
        {
            BarId = 2,
            Description = "as"
        },
        new Bar
        {
            BarId = 2,
            Description = "as"
        }
    }
};

ctx.Foos.Add(foo);
ctx.SaveChanges();

在上面的示例中,EF 将识别新项目,并将其全部插入。

更新(附件)

var foo = ctx.Foos.Include("Bars").Where(i => i.FooId == 1).FirstOrDefault();
foreach (var bar in foo.Bars)
{
    bar.Description = "changed";
}

ctx.SaveChanges();

这里我们从上下文中加载了 foo 和它的 bar。它们已经附加到上下文中。因此,我们需要做的就是更改值并调用SaveChanges()。一切都会好起来的。

更新(分离)

var foo = new Foo
{
    FooId = 1,
    Description = "changed3",
    Bars = new List<Bar>
    {
        new Bar
        {
            BarId = 1,
            Description = "changed3"
        },
        new Bar
        {
            BarId = 2,
            Description = "changed3"
        }
    }
};

ctx.Entry(foo).State = EntityState.Modified;

foreach (var bar in foo.Bars)
{
    ctx.Entry(bar).State = EntityState.Modified;
}

ctx.SaveChanges();

在这里,我们正在处理数据库中已经存在的项目。但是,它们不是从 EF 加载的(它们没有附加)。 EF 对他们一无所知。我们需要手动附加它们并告诉 EF 它们已被修改。

删除(附加)

var foo = ctx.Foos.Include("Bars").Where(i => i.FooId == 1).FirstOrDefault();
var bar = foo.Bars.First();
foo.Bars.Remove(bar);
ctx.SaveChanges();

从 EF 加载条形图,然后将它们从集合中移除。

移除(分离)

var bar = new Bar
{
    BarId = 1
};

ctx.Entry(bar).State = EntityState.Deleted;
ctx.SaveChanges();

这里,Bar 不是从上下文中加载的。所以,我们必须告诉 EF 它已被删除。


在您的情况下,您将更新的对象发送到 MVC 控制器;所以,你必须告诉 EF StoredProcedureReportStoredProcedureParameters 被修改了。

如果所有属性都被修改,你可以使用:

ctx.Entry(foo).State = EntityState.Modified;
//remember to do the same in all children objects

它将所有属性标记为已修改。请注意,如果某些属性未在视图中设置,它将被更新为空值。

如果不是所有属性都被修改,您必须指定哪些属性是。像这样:

context.Entry(foo).Property("Description").IsModified = true; 
context.Entry(foo).Property("AnotherProperty").IsModified = true; 

希望对你有帮助!

【讨论】:

  • 所以换句话说,它那么难。如果不循环孩子并标记它们Modified,您将无法做到这一点。将 EF 用于 Web 应用程序似乎很愚蠢。
  • 您可以创建一个方法来为您完成“循环工作”。
  • 是的,我明白这一点。我只是觉得我应该从一开始就这样做是丑陋的。像 EF 这样的东西的全部意义在于您的对象仍然附加到上下文,但这与 Web 请求的性质不一致,其中对象将以分离状态引入。这种循环是另一种表明不匹配的气味。
  • 我建议研究一下 GraphDiff,它为你做了很多烦人的工作。 github.com/refactorthis/GraphDiff
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-26
  • 2023-03-24
  • 1970-01-01
  • 2013-04-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多