4

我有一个运行实体框架 6 的 WCF 服务器应用程序。

我的客户端应用程序通过 DataServiceContext 使用来自服务器的 OData,并且在我的客户端代码中,我希望能够在上下文中调用 HasChanges() 方法以查看其中的任何数据是否已更改。

我尝试使用以下扩展方法:

    public static bool HasChanges(this  DataServiceContext ctx)
    {
        // Return true if any Entities or links have changes
        return ctx.Entities.Any(ed => ed.State != EntityStates.Unchanged) || ctx.Links.Any(ld => ld.State != EntityStates.Unchanged);
    }

但它总是返回 false,即使它正在跟踪的实体确实发生了变化。

例如,假设我有一个名为 Customer 的跟踪实体,以下代码总是在调用 SaveChanges() 之前返回。

    Customer.Address1 = "Fred"
    if not ctx.HasChanges() then return
    ctx.UpdateObject(Customer)
    ctx.SaveChanges()

如果我注释掉if not ctx.HasChanges() 然后返回代码行,则更改已成功保存,因此我很高兴实体已收到更改并能够保存它。

似乎上下文正在跟踪更改只是我无法从我的代码中确定这一事实。

谁能告诉我如何确定 DataServiceContext 上的 HasChanges?

4

4 回答 4

3

远。我刚刚通读了DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged),它是直接从UpdateObject(entity)一个false参数调用的。

逻辑如下:

  • 如果已经修改,返回;(短路)
  • 如果不变,则抛出 if failIfNotUnchanged; (仅来自ChangeState()
  • 否则将状态设置为已修改。(没有进行数据检查)

所以从表面上看,UpdateObject不关心/检查实体的内部状态,只关心State枚举。当没有更改时,这会使更新感觉有点不准确。

但是,我认为您的问题出在 OP 2nd 代码块中,您HasChanges 调用UpdateObject. 这些实体只是美化的 POCO(您可以在Reference.cs(显示隐藏文件,然后在服务参考下)中阅读)。它们具有明显的属性和一些On-通知更改的操作。他们在内部不做的是跟踪状态。实际上,有一个EntityDescriptor关联到实体,它负责在 中进行状态跟踪EntityTracker.TryGetEntityDescriptor(entity)

底线是操作实际上非常简单,我认为你只需要让你的代码像

Customer.Address1 = "Fred";
ctx.UpdateObject(Customer);
if (!ctx.HasChanges()) return;
ctx.SaveChanges();

尽管我们现在知道,这将始终报告 HasChanges == true,因此您不妨跳过检查。

但不要绝望!您的服务参考提供的部分类可以扩展为您想要的。它完全是样板代码,因此您可能想要编写一个 .tt 或其他一些代码生成器。无论如何,只需将其调整为您的实体:

namespace ODataClient.ServiceReference1  // Match the namespace of the Reference.cs partial class
{
    public partial class Books  // Your entity
    {
        public bool HasChanges { get; set; } = false;  // Your new property!

        partial void OnIdChanging(int value)  // Boilerplate
        {
            if (Id.Equals(value)) return;
            HasChanges = true;
        }

        partial void OnBookNameChanging(string value)  // Boilerplate
        {
            if (BookName == null || BookName.Equals(value)) return;
            HasChanges = true;
        }
        // etc, ad nauseam
    }
    // etc, ad nauseam
}

但是现在这很好用,并且对 OP 具​​有类似的表现力:

var book = context.Books.Where(x => x.Id == 2).SingleOrDefault();
book.BookName = "W00t!";
Console.WriteLine(book.HasChanges);

于 2015-10-30T13:54:11.963 回答
2

您是否可能没有正确添加/编辑您的实体?MSDN 声明您必须使用AddObjectUpdateObjectDeleteObject来获取更改跟踪以在客户端上触发(https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - 请参阅管理并发) . 否则你的扩展方法看起来不错。

于 2015-10-27T12:59:19.883 回答
0

我怀疑客户端中的数据上下文不是同一个。所以变化总是false

对于. Datacontext_ Datacontext那么检测变化是有意义的。

另一种方式,您必须自己跟踪更改。只需使用可跟踪实体来帮助您跟踪数据上下文中实体的更改。

顺便提一句。我使用代码'ctx.ChangeTracker.HasChanges()'来检测DataContext的变化。

    public bool IsContextDirty(DataServiceContext ctx)
    {
#if DEBUG
        var changed = ctx.ChangeTracker.Entries().Where(t => t.State != EntityState.Unchanged).ToList();
        changed.ForEach(
            (t) => Debug.WriteLine("entity Type:{0}", t.Entity.GetType()));
#endif
        return ctx != null && ctx.ChangeTracker.HasChanges();
    }
于 2015-10-29T05:49:35.163 回答
0

为了使其工作,必须启用自动更改跟踪。您可以在以下位置找到此设置

ctx.Configuration.AutoDetectChangesEnabled

所有实体对象也必须由上下文跟踪ctx。这意味着它们必须由ctx's 方法之一返回或显式添加到上下文中。

这也意味着它们必须由同一实例跟踪DataServiceContext。您是否以某种方式创建了多个上下文?

该模型还必须正确配置。也许Customer.Address1没有映射到数据库列。在这种情况下,EF 不会检测到对列的更改。

于 2015-10-27T13:14:29.560 回答