3

I am getting started with Entity Framework using POCO in a model-first configuration. I have a slightly non-standard model - generated by customising the .tt file to respond to custom properties in the .edmx enabling me to trigger a NotifyPropertyChanged event for logging updates to certain properties - which results in a class that effectively looks a little like this:

public partial class MyClass: INotifyPropertyChanged
{
    /// <summary>
    /// Catches events to be added to the UserLog
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies any event listeners attached to the PropertyChanged event that a loggable field
    /// update has occurred.
    /// </summary>
    /// <param name="eventType">The type of the field.</param>
    /// <param name="message">The message to record in the logs</param>
    private void NotifyFieldUpdate(string eventType, string message)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new LogEventArgs(eventType, message));
        }
    }


  private  string _myField;
  public virtual string MyField
    {
      get
      {
        return _myField;
      }
      set 
      {
        if ( _myField != value )
          {
        _myField = value;
        NotifyFieldUpdate( "FIELDCHANGE", String.Format("MyField changed to {0}", value) );
          }
        }
      }
  }

The event handler is then configured in another part of the partial class, to avoid unnecessary overwrites.

The problem I have is that if that gets updated twice, it falls over:

  public void TestBehaviour(ObjectContext currentContext)
  {
    MyClass testMe = FetchFromObjectContext(currentContext);
    testMe.MyField = "Hello";
    currentContext.SaveChanges();
    testMe.MyField = "Goodbye";
  }

The moment I call the second of those methods, I hit the following error:

EntityMemberChanged or EntityComplexMemberChanged was called without first calling EntityMemberChanging or EntityComplexMemberChanging on the same change tracker with the same property name. For information about properly reporting changes, see the Entity Framework documentation.

I have tried using currentContext.DetectChanges() and currentContext.Refresh( ... ) but that is really monkey coding as I don't know what exactly is going on.

My first question is: What is causing the problem and what do I have to do with the ObjectContext in order to avoid this type of error? It seems quite plausible to me that fields could be updated from time to time and I would hate for my system to fall over if it happened twice.

My second, perhaps more in-depth question is: Am I approaching this all wrong by using the INotifyPropertyChanged interface when it appears that the class is firing these EntityMemberChanged events already? I assume this is because the ObjectContext is creating a proxy- does that mean in some cases those methods are likely to be unavailable if I don't have my own notifications?

glenatron
  • 11,018
  • 13
  • 64
  • 112

4 Answers4

1

If you can't disable proxies and have a virtual Timestamp column, do not call RaisePropertyChanged for this property. http://softwaremechanik.wordpress.com/2013/07/09/ef-poco-invalidoperationexception-entitymemberchanged-in-savechanges-timestamp-column/

ajd
  • 423
  • 4
  • 11
1

I think the first time your event doesn't get called, only the second time.Maybe you should try to use Events from Entity Framework. Check this link

Community
  • 1
  • 1
radu florescu
  • 4,315
  • 10
  • 60
  • 92
  • I am using the PropertyChangedEventHandler, which is one of the standard Entity Framework event handlers. The problem seems to be caused by ObjectContext proxies, which cannot be updated more than once from what I can tell. – glenatron Nov 22 '11 at 15:52
  • Disable proxies then // Disable proxy object creation. context.ContextOptions.ProxyCreationEnabled = false; – radu florescu Nov 22 '11 at 17:15
  • Indeed, as I mention below that is the approach I have ended up going for. – glenatron Nov 23 '11 at 16:00
0

I believe it's because you're passing the ObjectContext to the UI thread and it is not thread safe.

I think you're right that you need to re-think how you're handling this (mainly the data access on the PropertyChanged events).

See the Remarks section here: http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.aspx

twaggs
  • 3,591
  • 1
  • 14
  • 8
  • Is that going to be the same in a unit test, which is where this is arising? I am wondering whether the event handler delegate is running in its own thread. But then it's also running within the `MyClass` object, so surely that should happen in the same thread? Surely... – glenatron Nov 15 '11 at 23:08
0

It turns out that the root cause of the problem was, as I suspected, to do with the creation of proxies by my ObjectContext.

To resolve the error and - in my case - consequently solve the problem, I just needed to add

currentContext.ContextOptions.ProxyCreationEnabled = false;

to my ObjectContext creation and it prevents it creating proxies. This may cost me a little convenience, but given that they seem somewhat fragile and opaque I think it saves me a lot of hassle for the time being.

Helpful resource: MSDN on working with POCO entities

glenatron
  • 11,018
  • 13
  • 64
  • 112
  • If you disable proxies you're skipping out on a lot, like lazy loading children. Also, it could possibly destroy his existing models. – Asti Nov 17 '11 at 10:15
  • Models don't seem to be affected by it- being able to lazy load children would certainly be convenient, but having my code crash out when I update my objects is highly inconvenient and judging by the answers here, nobody knows of a way to avoid that. – glenatron Nov 17 '11 at 15:38
  • I'm having this same problem after have spent the last few hours convincing Entity Framework to create proxies so that I could reap huge the performance benefits over DetectChanges(). This answer is far from the right solution! – Dan Bechard Oct 07 '14 at 15:25
  • It was the one that worked for me at the time. If you've got a correct solution that works, by all means add it! This is also EF4 based, which is a few versions back now so the technology may have changed. – glenatron Oct 07 '14 at 15:50