【问题标题】:Updating complex type with ef code first先用 ef 代码更新复杂类型
【发布时间】:2013-05-21 02:23:09
【问题描述】:

我有一个名为 account 的复杂类型,其中包含许可证列表。 许可证又包含一个域列表(域是一个简单的 id + url 字符串)。

在我的存储库中,我有这段代码

    public void SaveLicense(int accountId, License item)
    {

        Account account = GetById(accountId);
        if (account == null)
        {
            return;
        }


        if (item.Id == 0)
        {
            account.Licenses.Add(item);
        }
        else
        {
            ActiveContext.Entry(item).State = EntityState.Modified;
        }


        ActiveContext.SaveChanges();
    }

当我尝试保存更新的许可证(带有修改的域)时,会发生直接属于许可证的字符串得到更新的情况。

但是没有域得到更新。


我应该提到我所做的是允许用户在用户界面中添加和删除域。任何新域的 id=0 和任何已删除的域都不在列表中。

所以我想要的是

  1. 列表和数据库中的任何域且未更改 - 没有任何反应
  2. 列表和数据库中的任何域,但在列表中已更改 - 数据库已更新
  3. 应将 id=0 的任何域插入(添加)到数据库中
  4. 应删除不在列表中但在数据库中的所有域

我玩了一点,但没有成功,但我偷偷地怀疑我在更大的范围内做错了什么,所以如果我在设计方面误解了某些东西,或者只是错过了一些东西,我希望得到提示。

【问题讨论】:

    标签: asp.net-mvc-4 ef-code-first entity-framework-5 complextype


    【解决方案1】:

    不幸的是,更新对象图 - 具有其他相关实体的实体 - 是一项相当困难的任务,并且 Entity Framework 没有非常复杂的支持来使其变得容易。

    问题在于,将实体的状态设置为Modified(或通常设置为任何其他状态)只会影响您传递给DbContext.Entry 的实体,并且只会影响其标量属性。它对其导航属性和相关实体没有影响。

    您必须手动处理此对象图更新,方法是加载当前存储在数据库中的实体(包括相关实体),并将您在 UI 中所做的所有更改合并到该原始图中。您的else 案例可能如下所示:

    //...
    else
    {
        var licenseInDb = ActiveContext.Licenses.Include(l => l.Domains)
            .SingleOrDefault(l => l.Id == item.Id)
    
        if (licenseInDb != null)
        {
            // Update the license (only its scalar properties)
            ActiveContext.Entry(licenseInDb).CurrentValus.SetValues(item);
    
            // Delete domains from DB that have been deleted in UI
            foreach (var domainInDb in licenseInDb.Domains.ToList())
                if (!item.Domains.Any(d => d.Id == domainInDb.Id))
                    ActiveContext.Domains.Remove(domainInDb);
    
            foreach (var domain in item.Domains)
            {
                var domainInDb = licenseInDb.Domains
                    .SingleOrDefault(d => d.Id == domain.Id);
                if (domainInDb != null)
                    // Update existing domains
                    ActiveContext.Entry(domainInDb).CurrentValus.SetValues(domain);
                else
                    // Insert new domains
                    licenseInDb.Domains.Add(domain);
            }
        }
    }
    ActiveContext.SaveChanges();
    //...
    

    您也可以尝试this project called "GraphDiff",它打算以通用方式为任意分离的对象图完成这项工作。

    另一种方法是跟踪 UI 层中某些自定义字段中的所有更改,然后在数据回发时评估跟踪的状态更改以设置适当的实体状态。因为您在 Web 应用程序中,这基本上意味着您必须在用户更改值、添加新项目或删除项目时跟踪浏览器中的更改(很可能需要一些 Javascript)。在我看来,这个解决方案更加难以实施。

    【讨论】:

    • 谢谢,正如我所担心的那样,但至少我现在已经确认了,并且不会因为不得不这样做而感到如此沮丧......如果是这样的话:-)但是,如果他们可以在 EF 中实现一些不错的支持,那就太好了
    • @RickardLiljeberg:是的,您可以使用上面的代码,但它只是一个子集合。如果您有多个收藏和孙子孙孙......等等,这不再有趣了。顺便说一句:您可以为该功能投票:entityframework.codeplex.com/workitem/864。但它不会在 EF 7 之前发生(如果有的话),所以可能至少要等一年。
    • 我很高兴地投票了,我真的很惊讶地发现 EF 在这方面有多么有限。
    • 听起来像个笑话。这个答案确实让我在第一步时对实体框架与 nhibernate 的选择产生了疑问——我们还有合理的发布时间,我正在考虑切换。
    【解决方案2】:

    这应该足以完成您想做的事情。如果您对代码有更多疑问,请告诉我。

    public void SaveLicense(License item)
    {        
        if (account == null)
        {
            context.Licenses.Add(item);
        }
    
        else if (item.Id > 0)
               {
    
                   var currentItem = context.Licenses                        
                       .Single(t => t.Id == item.Id);
    
                   context.Entry(currentItem ).CurrentValues.SetValues(item);                    
                }
    
        ActiveContext.SaveChanges();
     }
    

    【讨论】:

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