【问题标题】:Persisting changes in EF 4.3 - Change tracking issuesEF 4.3 中的持久更改 - 更改跟踪问题
【发布时间】:2012-06-20 11:20:58
【问题描述】:

我有一个类 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;
    }

我担心我的代码看起来很费力 - 我需要先获取旧对象的副本,然后才能保存更新的副本?我也不相信我的保存新逻辑。

那么这种方法是否正确,或者是否有更好的方法来编写上述方法?

其他事情的进一步细节:

因为我将通过 WCF 发送对象图。我已经实现了急切加载:

    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 时失败,可能是因为我附加到它的对象之一已经存在于数据库中。

【问题讨论】:

    标签: entity-framework entity-framework-4.3


    【解决方案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;
    }
    

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

    【讨论】:

    • 在对象图中,唯一改变的对象是 UserReport 本身,但这与 ReportColumnGroup 类有多对多的关系。数据库中的 ReportColumnGroups 集永远不会改变,但与 UserReports 的关系将作为报告可以从集合中添加或删除 ReportColumnGroups。
    • 我以为我已经尝试过按照您的方式进行操作,但是在 Attach 上失败了 - 我已经经历了很多次迭代,所以将尝试您在上面输入的内容,看看它是否有效。跨度>
    • 是的 - 它失败并出现错误:ObjectStateManager 中已存在具有相同键的对象。当我运行行 context.UserReports.Attach(userReport);我将在上面添加我的代码的更多细节。
    • 好的 - 如果我禁用 LazyLoading,并删除我的 EagerLoading 扩展,那么一切似乎都可以工作。
    • 更正 - 它主要工作 - 我现在遇到的问题是没有填充多对多关系。所以我的 UserReport 不再检索到任何 ReportColumnGroups。
    猜你喜欢
    • 2021-12-06
    • 2018-12-24
    • 1970-01-01
    • 2012-01-02
    • 2011-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多