InvalidOperationException "Value of member 'X' of an object of type 'Y' changed." when saving data via WCF Data Services and LINQ

StackOverflow https://stackoverflow.com/questions/22160636

I have a system that uses WCF Data Services on top of a .dbml (System.Data.Linq.DataContext) data model. The client retrieves data that it needs into a System.Data.Services.Client.DataServiceContext, makes changes, and saves back via WCF Data Services, which in turn saves to the SQL Server database. The DataContext class has an IUpdateable implementation very similar to this example.

I find this slightly weird and it was built a few years back so there's probably much better ways to do it now, but generally it's all been working well. However, recently it was deployed to a new environment and started having problems along the lines of

An exception of type 'System.Data.Services.Client.DataServiceRequestException' occurred and was caught.
... 
Value of member 'BehaviorAsString' of an object of type 'profile' changed.
A member that is computed or generated by the database cannot be changed.

The problematic column BehaviorAsString is calculated in the view that is the Source for the entity profile. In the dbml it's correctly set to ReadOnly=True, since it should never be updated. The stack trace shows that the problem is happening on the server side (as I interpret it - see below).

So far so straightforward: the problem appears to be that my UI is for some reason updating this property, then my DataService is rightly complaining that that's not allowed.

However, I don't think that this is the case. That data model field is only used in one place, a .xaml file, where it's bound to a TextBlock (i.e. a readonly label). My WPF app isn't changing the field values anywhere. So I think the problem is the column change state gets lost when the objects are (de)serialized. The DataService on the server calls the setter on each field, thus all the fields are treated as dirty and would be saved back to the database. Then LinqToSql notices it's trying to save back a ReadOnly field and throws an exception.

This means I need to do something on the server side to make it not see those readonly fields as changed, or otherwise prevent it from trying to save those changes.

As an attempted hack at a solution I simply commented out the contents of the BehaviorAsString setter, so it would not be marked as changed. This fixed the immediate exception only for the same problem to occur on another ReadOnly calculated field on a different entity. It's also a terrible solution as it involves modifying an autogenerated file.

It's a client environment so I have limited scope to install different versions and try out different things, so I'd like to have some good options before going back to them again.

The really strange thing though is that this is only happening for this environment and I can't replicate it. It's a different build from one deployed at other clients, but only a couple of small changes that aren't in files remotely related - I've triple checked this (NB: see update 3 below - could be because of cached DataContext). It's possible that there's a different .NET version installed on the server - the Application Pool is running in the .NET v2.0 runtime, with the app built using .NET 3.5 SP1 - or some other dependency that is different. No other referenced dlls are different.

So my question is twofold:

  1. Given how I'm using WCF Data Services, what's the right way to deal with calculated fields and ensure LINQ doesn't try to save them to the database?

  2. Is there anything environmental that could have caused this problem to arise where it didn't before?

And any other suggestions very welcome!


Stack trace of the exception as it was logged on the client side:

Message: An exception of type 'System.Data.Services.Client.DataServiceRequestException' occurred and was caught.
02/13/2014 16:05:20
Type: System.Data.Services.Client.DataServiceRequestException, System.Data.Services.Client, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message: An error occurred while processing this request.
InnerException(s):
  System.Data.Services.Client.DataServiceClientException: <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code></code>
  <message xml:lang="en-US">An error occurred while processing this request.</message>
  <innererror>
    <message>Value of member 'BehaviorAsString' of an object of type 'profile' changed.&#xD;
A member that is computed or generated by the database cannot be changed.</message>
    <type>System.InvalidOperationException</type>
    <stacktrace>   at System.Data.Linq.ChangeProcessor.CheckForInvalidChanges(TrackedObject tracked)&#xD;
   at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)&#xD;
   at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)&#xD;
   at System.Data.Linq.DataContext.SubmitChanges()&#xD;
   at RPServices.RPDataModel.RPDataModelDataContext.SaveChanges()&#xD;
   at System.Data.Services.DataService`1.BatchDataService.HandleBatchContent(Stream responseStream)</stacktrace>
  </innererror>
</error>

   at System.Data.Services.Client.DataServiceContext.SaveResult.<HandleBatchResponse>d__1e.MoveNext()
Source: System.Data.Services.Client
TargetSite: Void HandleBatchResponse()
StackTrace:    at System.Data.Services.Client.DataServiceContext.SaveResult.HandleBatchResponse()
   at System.Data.Services.Client.DataServiceContext.SaveResult.EndRequest()
   at System.Data.Services.Client.DataServiceContext.SaveChanges(SaveChangesOptions options)
   at RPAdminTool.ViewModel.ViewModelBase.SaveProcess()
   at RPAdminTool.ViewModel.ViewModelBase.<Save>b__2(Object _, DoWorkEventArgs __)

UPDATE 1:

Possibly this is related (?) which is referred to as a bug but with no reference or further explanation.

UPDATE 2:

I've looked into IUpdateable a little ... could I implement IUpdatable.SaveChanges() to clear changes on all fields marked as IsDbGenerated=true?

UPDATE 3:

I did find a code change that I missed previously :-/ which meant that the client-side (WPF) code was re-using a static DataServiceChannel object (my RPDataModelDataContext object) instead of getting a separate one. This was causing another oddity I noticed where despite calling DataServiceQuery.Expand() some recently-created related entities weren't geting loaded. It turns out the re-use of the DataServiceChannel object meant it wasn't hitting the server/database to get the recently-created entities.

I'm thinking this re-used DataServiceChannel object is probably the cause of this whole issue. I'm still not clear why it would have had this effect, but will update this question with the results of my testing. I'd be very interested in anyone able to answer why it would have this effect, as well as a general way to fix it on the server.

没有正确的解决方案

其他提示

Are you sending through WCF the same entity objects Linq2Sql generated?

If I was to modify the existing system with a non Code First approach, I would introduce Data Transfer Objects (DTOs). This means that you need to convert them to entity objects and that is where you will make sure that read only fields are not touched.

If you are using Code First, you may set attributes on your model class read only fields to tell serialiser keep them unchanged.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top