Pushing modified DataSets through Web Services#

There seems to be a bug in the .NET Soap serialization stack when passing a DataSet with an expression column in it that uses a relation. Allow me to elaborate :-) But don't run off just yet if you don't care about the serialization stack, there's some useful stuff in there as well ;-)

Imagine a typed DataSet "TestDataSet" that you're passing from your Service layer to your client UI layer through an ASP.NET Web Service. Say you have a table Employee and a table Company in it, and you want to show the employees in a DataGrid (e.g. WinForms but that doesn't really matter here) with the name of the company in a separate column. The service backend doesn't care about your needs to display the company name inline with the rest of the data, so all you have is the relation CompanyEmployee between the two tables.

For display purposes, you can add a column to the Employee table with its Expression set to "Parent(CompanyEmployee).Name" and it will display just fine: it calculates the new column by navigating to the parent row through the CompanyEmployee relationship and then takes the Name column from that row. Bingo!

TestDataSet testData = TestServiceAgent.GetTestData();
testData.Employee.Columns["ID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns["CompanyID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns.Add( "CompanyName", typeof( string ), "Parent(CompanyEmployee).Name" );
employeeGrid.DataSource = testData.Employee;

Extra advantages: this value is automatically updated if the parent changes and since it's a calculated column, you can't edit the company name in the grid. Also note that you can hide columns by setting their mapping type to Hidden. That's just a simple trick you can use on the client side.

The problem arises when you use this modified DataSet to update your entity in the backend again: you probably have some SaveTestData method on your service that accepts a TestDataSet to be saved and you just want to pass the changes of the modified DataSet along to it.

TestServiceAgent.SaveTestData( testData.GetChanges() );

Too bad, it will blow up in your face:

Exception: System.Web.Services.Protocols.SoapException: Server was unable to read request. --->
System.InvalidOperationException: There is an error in XML document (1, 12311). --->
System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Data.LookupNode.Bind(DataTable table, ArrayList list)
   at System.Data.DataExpression.Bind(DataTable table)
   at System.Data.DataExpression..ctor(String expression, DataTable table, Type type)
   at System.Data.DataColumn.set_Expression(String value)
   at System.Data.Merger.MergeSchema(DataTable table)
   at System.Data.Merger.MergeTableData(DataTable src)
   at System.Data.Merger.MergeDataSet(DataSet source)
   at System.Data.DataSet.Merge(DataSet dataSet, Boolean preserveChanges, MissingSchemaAction missingSchemaAction)
   at TestProject.TestDataSet.ReadXmlSerializable(XmlReader reader) in C:\TestProject\TestDataSet.cs:line 166
   at System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.ReadXml(XmlReader reader)
   at System.Xml.Serialization.XmlSerializationReader.ReadSerializable(IXmlSerializable serializable)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read14_SaveTestData()
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   --- End of inner exception stack trace ---
   at System.Web.Services.Protocols.SoapServerProtocol.ReadParameters()
   at System.Web.Services.Protocols.WebServiceHandler.Invoke()
   at System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message,
WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at TestProject.TestServiceProxy.SaveTestData(TestDataSet testData) in c:\testproject\testserviceagent.cs:line 291
   at TestProject.TestServiceAgent.SaveTestData(TestDataSet testData) in c:\testproject\testserviceagent.cs:line 162

As you can see, the Soap/XML deserialization stack has some problems with the expression column. And this isn't just because the parent row could be missing in case you only sent the changed rows (which you should do using the GetChanges() method on the DataSet to get the diffgram): it happens even if you send the full DataSet back. If there's an expression column that doesn't use a relation, then there's no problem.

There's a post on .NET 247 called "Serialize DataSet with ExpressionColumn" that shows somebody else running into this issue, and if you kept your foreign languages polished you can also check it out on the Russian GotDotNet site. But that's all I found on it so far, and none offer an explanation or solution.

A possible workaround would be to remove all expression columns again before submitting the DataSet back to the Service layer, but that's pretty harsh, not too transparent, and it can mess up your UI if you're still using the DataSet on-screen.

What I consider to be much better, is to create a new instance of the typed DataSet and merge the changes of the DataSet you're working with into it - making sure you ignore anything not in the DataSet's schema by using MissingSchemaAction.Ignore. This removes all the added clutter you don't need on the backend anyway and it makes sure you get a clean and valid copy of your typed DataSet out of the door.

TestDataSet changes = new TestDataSet();
changes.Merge( testData.GetChanges(), true, MissingSchemaAction.Ignore );
TestServiceAgent.SaveTestData( changes );

This way, everything works and the universe is happy again.

And if you're worried about the cost of the extra DataSet you just created: it's on the client side so I'd consider the performance hit negligible as long as you don't constantly send this type of changes to the backend. Then again, if you are constantly doing this, then there's probably something wrong with the way you're using your service :-p

Saturday, January 15, 2005 12:15:04 PM (Romance Standard Time, UTC+01:00)
I think it's bad practice to send datatsets over web-services.
Dataset are to .NET-isch. You assume that all you web-service clients will be .NET-clients. Go for the Schema first approach, and keep in the datasets internal
Peter
Saturday, January 15, 2005 1:09:13 PM (Romance Standard Time, UTC+01:00)
I was expecting this remark but I deliberately steered clear of the "why" question concerning DataSets over Web Services.

The fact is that we *are* using them on a project, since they fit very well in the overall architecture of the company. Furthermore, it's a much better experience for the developers if they can used the RAD features of the DataSet and don't have to worry about explicitly creating yet another schema. Finally, I don't think DataSets are .NET-only since they are in fact "just a schema" (diffgrams are .NET only but they're not required here, it's an optional optimalization) so it's up to the client to choose the best way to access the service. Since we have total control over both the server and the client in this project, we're using DataSets on both sides.

The whole Web Service interface might change if other types of clients would someday depend on the services but that's not likely to happen anytime soon, so it was a deliberate choice not to invest in that kind of scenario. This might not be the most "sexy" design in the current SOA hype but it's well supported and it works absolutely fine. Shipping is a feature you know :-)
Saturday, January 15, 2005 1:26:46 PM (Romance Standard Time, UTC+01:00)
I agree that if you have both the client and the server under control you can take advantage of it. ofcourse datasets have a schema, but I think you get the point
Peter
Saturday, April 07, 2007 9:35:04 AM (Romance Standard Time, UTC+01:00)
Don't want to be scammed? Please visit:http://www.viccol.com

Welcome to RSGoldCenter.
Unlike other virtual item shops,RSGoldCenter focuses solely on
Runescape! Our specialization in Runescape ensures YOU unbeatable
customer service, and the most importantly, extremely cheap, guaranteed
RuneScape-gold! We offer 100% legitimate Gold in all worlds. All we do
is just for your best experience in games. ... ^_^
Our promise for RS2 Gold Money sending is 30 mins-24 hours.After you
pay, we will message you in game ASAP, then to make a fixed time to
complete the trade. Since the trade should be face to face, so we hope
you can feel free to contact with our MSN:izegame01-customer-service@hotmail.com,
when you have any problem.
http://www.viccol.com
http://www.viccol.com
http://www.viccol.com GO!GO!GO!
Monday, July 30, 2007 3:58:03 AM (Romance Standard Time, UTC+01:00)
I've found the same problem, but with a windows application. I am using an expression to get output data for a datagridview.

I fixed it by doing:



// Make a copy of the expression
string colExp = myDataSet.Table1.MyExpressionColumn.Expression;
myDataSet.Table1.MyExpressionColumn.Expression = "";

// Won't throw exception now, as there is no link to my parent column
myDataSet.Table1.GetChanges();

// Revert the expression
myDataSet.Table1.MyExpressionColumn.Expression = colExp ;



No noticible effects in GUI as the expression is restored before any GUI events get fired.
Humphrey
Comments are closed.
All content © 2008, Jelle Druyts
On this page

Recent Photos
www.flickr.com
This is a Flickr badge showing public photos from Jelle Druyts. Make your own badge here.
Advertising
Top Picks
Statistics
Total Posts: 344
This Year: 7
This Month: 0
This Week: 0
Comments: 522
Archives
Sitemap
Disclaimer
This is my personal website, not my boss', not my mother's, and certainly not the pope's. My personal opinions may be irrelevant, inaccurate, boring or even plain wrong, I'm sorry if that makes you feel uncomfortable. But then again, you don't have to read them, I just hope you'll find something interesting here now and then. I'll certainly do my best. But if you don't like it, go read the pope's blog. I'm sure it's fascinating.

Powered by:
newtelligence dasBlog 2.0.7226.0

Sign In