持续改变EF 4.3 - 更改跟踪问题

时间:2012-06-20 11:20:58

标签: entity-framework entity-framework-4.3

我有一个类ReportConfigurationManager,它管理针对UserReport实体的CRUD操作。感兴趣的两个操作是“Get”和“SaveUpdate”。在这两种情况下,我将操作包装在using语句中,以便将DbContext放置在查询的末尾。

现在最终这些方法将构成WCF服务的一部分,但也可以在服务内部调用它们。我目前的困难是让一组单元测试工作,直接调用ReportConfigurationManager。

我可以创建一个新的UserReport并保存它(由于实体有几个已经存在于数据库中的嵌套对象,这需要我一段时间才能解决 - 我需要在调用之前将这些对象依次“附加”到上下文中添加UserReport以使其正确保存。

我现在的问题是更新。

尽管有

    context.Configuration.ProxyCreationEnabled = false;
    context.Configuration.AutoDetectChangesEnabled = false;

在使用ReportConfigurationManager的所有方法上,当我附加一个UserReport时,它失败了经典的“ObjectStateManager中已经存在一个具有相同键的对象”(我认为禁用更改跟踪是为了处理这个?)

所以现在我已经切换到使用以下代码here

 public UserReport SaveUpdateUserReport(UserReport userReport)
    {
        using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            if (userReport.Id > 0)
            {
                {
                    UserReport oldReport = context.UserReports.Where(ur => ur.Id == userReport.Id).FirstOrDefault();
                    context.Entry(oldReport).CurrentValues.SetValues(userReport);
                }                  
            }
            else
            {
                //Need to attach everything to prevent EF trying to create duplicates in the database
                context.ReportTopTypes.Attach(userReport.ReportTopType);
                context.ReportWindows.Attach(userReport.ReportWindow);
                context.ReportSortOptions.Attach(userReport.ReportSortOption);

                foreach (var col in userReport.ReportColumnGroups)
                {
                    context.ReportColumnGroups.Attach(col);
                }

                context.ReportTemplates.Attach(userReport.ReportTemplate);

                //just add the new data
                context.UserReports.Add(userReport);
            }

            context.SaveChanges();
        }

        return userReport;
    }

我担心的是我的代码看起来很费力 - 在保存更新的副本之前,我需要获取旧对象的副本吗?而且我也不相信我的Save New逻辑。

这种方法是正确的,还是有更好的方法来编写上述内容?

其他事情的进一步细节:

因为我将通过WCF发送对象图。我实施了Eager Loading:

    public static DbQuery<ReportTemplate> IncludeAll(this DbQuery<ReportTemplate> self)
    {
        return self
            .Include("ReportColumnGroups.ReportColumns.ReportColumnType")
            .Include("ReportColumnGroups.ReportColumnType")
            .Include("ReportSortOptions.ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortOptions.ReportSortColumns.ReportSortType");
    }

    public static DbQuery<UserReport> IncludeAll(this DbQuery<UserReport> self)
    {
        return self
            .Include("ReportTemplate")
            .Include("ReportTopType")
            .Include("ReportWindow")
            .Include("ReportSortOption.ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortOption.ReportSortColumns.ReportSortType")
            .Include("ReportColumnGroups.ReportColumns.ReportColumnType")
            .Include("ReportColumnGroups.ReportColumnType");

    }


    public static DbQuery<ReportSortOption> IncludeAll(this DbQuery<ReportSortOption> self)
    {
        return self
            .Include("ReportSortColumns.ReportColumn.ReportColumnType")
            .Include("ReportSortColumns.ReportSortType");
    }

    public static DbQuery<ReportColumnGroup> IncludeAll(this DbQuery<ReportColumnGroup> self)
    {
        return self
            .Include("ReportColumn.ReportColumnType")
            .Include("ReportColumnType");
    }

    public static DbQuery<ReportColumn> IncludeAll(this DbQuery<ReportColumn> self)
    {
        return self
            .Include("ReportColumnType");
    }

    public static DbQuery<ReportSortColumn> IncludeAll(this DbQuery<ReportSortColumn> self)
    {
        return self
            .Include("ReportColumn.ReportColumnType")
            .Include("ReportSortType");
    }

我有一组静态缓存数据,我获得如下:

using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            reportConfigurationData = new ReportingMetaData()
                                          {
                                              WatchTypes = context.WatchTypes.ToList(),
                                              ReportTemplates = context.ReportTemplates.IncludeAll().ToList(),
                                              ReportTopTypes = context.ReportTopTypes.ToList(),
                                              ReportWindows = context.ReportWindows.ToList(),
                                              ReportSortOptions =
                                                  context.ReportSortOptions.IncludeAll().ToList()
                                          };
        }

我按如下方式检索UserReports:

public UserReport GetUserReport(int userReportId)
    {
        using (var context = new ReportDataEF())
        {
            context.Configuration.ProxyCreationEnabled = false;
            context.Configuration.AutoDetectChangesEnabled = false;
            var visibleReports =
                context.UserReports.IncludeAll().Where(ur => ur.Id == userReportId).FirstOrDefault();
            return visibleReports;
        }
    }

我关注的测试从数据库中获取现有的UserReport,使用静态数据类中的对象更新其ReportTemplate和ReportColumnGroups属性,然后尝试保存更新的UserReport。

使用Ladislav的回答中的代码,当我尝试附加UserReport时失败,可能是因为我附加的对象之一已经存在于数据库中。

1 个答案:

答案 0 :(得分:1)

是的还有另一种方式。首先,您应该知道EF不支持部分附加的对象图,因此AttachAdd都有附加或添加图中尚未被上下文跟踪的所有实体的副作用。这将大大简化您的插入代码。

public UserReport SaveUpdateUserReport(UserReport userReport)
{
    using (var context = new ReportDataEF())
    {
        context.Configuration.ProxyCreationEnabled = false;
        context.Configuration.AutoDetectChangesEnabled = false;

        // Now all entities in the graph are attached in unchanged state
        context.ReportTopTypes.Attach(userReport);

        if (userReport.Id > 0 && 
            context.UserReports.Any(ur => ur.Id == userReport.Id))
        {
            context.Entry(userReport).State = EntityState.Modified;
        }
        else
        {
            context.Entry(userReport).State = EntityState.Added;
        }

        context.SaveChanges();
    }

    return userReport;
}

这相当于您的原始代码。您不再次加载用户报告 - 您只需检查其在DB中的存在。此代码存在很多问题 - 例如,如果您更改了任何其他相关对象,则它将不会持久保存到数据库,因为其当前状态为Unchanged。如果你需要改变关系,那就更复杂了。