Richard Moore
Richard Moore

Reputation: 1162

Entity Framework 6 - DataServiceContext Detect Has Changes

I have a WCF server application running Entity Framework 6.

My client application consumes OData from the server via a DataServiceContext, and in my client code I want to be able to call a HasChanges() method on the context to see if any data in it has changed.

I tried using the following extension method:

    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);
    }

But it always returns false, even if an entity it is tracking does have changes.

For instance, given that I have a tracked entity named Customer, the following code always returns before calling SaveChanges().

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

If I comment out the if not ctx.HasChanges() then return line of code, the changes are saved successfully so I'm happy that the entity has received the change and is able to save it.

It seems that the change is getting tracked by the context, just that I can't determine that fact from my code.

Can anyone tell me how to determine HasChanges on a DataServiceContext?

Upvotes: 4

Views: 3693

Answers (4)

Todd Sprang
Todd Sprang

Reputation: 2919

Far out. I just read through DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged), which is called directly from UpdateObject(entity) with a false argument.

The logic reads like:

  • If already modified, return; (short-circuit)
  • If not unchanged, throw if failIfNotUnchanged; (true only from ChangeState())
  • Else set state to modified. (no data checks happened)

So by the looks of it, UpdateObject doesn't care about/check the internal state of the entity, just the State enum. This makes updates feel a little inaccurate when there are no changes.

However, I think your problem is then in the OP 2nd block of code, you check your extension HasChanges before calling UpdateObject. The entities are only glorified POCOs (as you can read in your Reference.cs (Show Hidden Files, then under the Service Reference)). They have the obvious properties and a few On- operations to notify about changing. What they do not do internally is track state. In fact, there is an EntityDescriptor associated to the entity, which is responsible for state-tracking in EntityTracker.TryGetEntityDescriptor(entity).

Bottom line is operations actually work very simply, and I think you just need to make your code like

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

Though as we know now, this will always report HasChanges == true, so you may as well skip the check.

But don't despair! The partial classes provided by your service reference may be extended to do exactly what you want. It's totally boilerplate code, so you may want to write a .tt or some other codegen. Regardless, just tweak this to your entities:

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
}

But now this works great and is similarly expressive to the OP:

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

HTH!

Upvotes: 3

huoxudong125
huoxudong125

Reputation: 2056

I doubt that the datacontext in client is not the same one.so the changes is always is false.

You must be sure the Datacontext is the same one(instance), for every changes of the Datacontext. Then to detect the changes is meaningful.

Another way ,you must tracked the changes by yourself.simply using the Trackable Entities to help you tracking the changes of entities in the datacontext.

BTW. I Use the Code ' ctx.ChangeTracker.HasChanges()' to detect the changes of 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();
    }

Upvotes: 0

Jørgen Fogh
Jørgen Fogh

Reputation: 7656

In order for this to work, automatic change tracking must be enabled. You can find this setting in

ctx.Configuration.AutoDetectChangesEnabled

All the entity objects must also be tracked by the context ctx. This means that they must be returned by one of ctx's methods or explicitly added to the context.

It also means that they must be tracked by the same instance of DataServiceContext. Are you somehow creating more than one context?

The model must also be configured correctly. Perhaps Customer.Address1 is not mapped to a database column. In that case, EF will not detect changes to the column.

Upvotes: 0

Todd Sprang
Todd Sprang

Reputation: 2919

Is it possible you're not adding/editing your entities properly? MSDN states that you must use AddObject, UpdateObject, or DeleteObject to get change tracking to fire on the client (https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - see Managing Concurrency). Otherwise your extension method looks good.

Upvotes: 2

Related Questions