DataSet return type - EndOfStreamException
rvecchio opened this issue · 13 comments
Hello
I’m exploring the possibility to substitute the .NET Remoting in a range of legacy applications that have to be ported on .NET Core.
I’ve simply setup an hello word project, by following your useful documentation and basically everything works great, apart from an issue regarding the DataSet object when it’s used as a return type.
Every time I try to invoke remote methods that return System.Data.DataSet objects, I'm getting a System.IO.EndOfStreamException.
Strangely, by encapsulating the DataSet in a class like the following and by using this new type as return type, no error occurs:
`[Serializable]
public class DataSetObject : DataSet
{
public DataSetObject() : base() { }
public DataSetObject(string dataSetName) : base(dataSetName) { }
public DataSetObject(SerializationInfo info, StreamingContext context) : base(info, context) { }
public DataSetObject(SerializationInfo info, StreamingContext context, bool ConstructSchema) : base(info, context, ConstructSchema) { }
}`
This solution does not fit very well to me, because it implies a lot of changes in the existing server side code.
From my testing, the DataSet can even be successfully used as a byref parameter in a remote method. Honestly I would not expect this to work :-) so I don’t understand why it should not be transported as it is as a return type.
Could you please tell me if this is a bug, or possibly provide me with a workaround?
I’m available to send the prototype code if it’s useful.
Regards
Ruggero
You cannot serialize a stream. The DataSet supports a stream which means you require local context, the environment in which the DataSet was created. Transform your DataSet to a POCO and move that across the wire. ServiceWire is not a replacement for .NET Remoting which allowed context to be shared. It is an RPC library for .NET to .NET endpoints that are disconnected and share no context.
Even if DataSet could be moved across the wire, it would be a poor choice because of its weight. A stripped down POCO transformed prior to sending it across the wire would be much lighter weight.
Hello
There's no question about how efficient is an entity class compared to a DataSet in terms of weight. But it's not the same thing and furthermore my problem is related to legacy code.
I have no issue regarding the different contexts in this case, as far as I can see, because we can ensure that the two processes (client and server) are running either on the same machine or on two different machines on our end, so we can control the OS version, the platform, everything.
I still cannot understand why I'm able to transport a DataSet which is a property of a class like the following:
[Serializable]
public struct DataResponse
{
public Guid Id { get; set; }
public DataSet Data { get; set; }
}
Or a DataSet which is encapsulated in a class such as the one in my previous post.
But I'm receiving an error when I'm trying to transport the exact same DataSet if it is directly the return type of the remote method.
I will live with this mistery :-)
Regards
Ruggero
Ruggero, DataSet should serialize. Are both host and client the same .NET framework or same .NET Core? Regardless of whether it is on the same machine, context is not preserved as it is in .NET Remoting and MarshalByRef objects that are exposed remotely but still live on the host. Hence, if you are calling something in the DataSet that requires reading from a stream, that may be why you're getting the issue. Can you zip and drop me your code to tyler@tsjensen.com and I'll have a look?
Hi Tyler
I sent the code, as requested.
To your question about the platform: yes in my case both client and server will have the same version of the .NET Framework or .NET Core on board.
If you already had the chance to have a look to the code I sent to your email account, I think you noticed that this error occurs regardless of the kind of DataSet or its structure. Even returning an empty DataSet will cause the error...
In any case, thank you for your availability.
Ruggero
Ruggero,
It went to my spam folder. I'll check it out today.
Tyler
I still cannot understand why I'm able to transport a DataSet which is a property of a class like the following:
I've tried to extend the original DemoCommon.IComplexDataContract
class with the
public interface IComplexDataContract
{
...
DataSetContent GetDSContent(int rows);
DataSet GetDS(int rows);
}
and added DataSetContent
to the DemoCommon
[Serializable]
public class DataSetContent
{
public DataSet Content { get; set; }
}
The DemoHost was able to serve the DemoClient calls like below:
using (var client = new TcpClient<IComplexDataContract>(zkEndpoint))
{
client.InjectLoggerStats(logger, stats);
var dsc = client.Proxy.GetDSContent(15);
var ds = client.Proxy.GetDS(25);
}
Regards
It has something to do with the binary formatter. It may take me some time to diagnose the problem. You're welcome to step through the code as well. With two other jobs taking precedence, it may be a while before I can get to the bottom of it.
I do not mind to check the error origin
However I'd like to get a code that reproduces the error.
I've tried to add a method that returns System.Data.DataSet
object and no error is thrown. It works fine
Please find adjusted Demo
project attached with sample GetDS
and GetDSContent
methods were added to the IComplexDataContract
Demo.zip
@oleksabor
Thank you for sharing your code.
The problem occurs if the build target of the server (and the client) is the .NET Framework. As you reported, it works on .NET Core
In my case I wanted to test a scenario in which a .NET Core client could consume remote objects exposed by a legacy .NET Framework server; or even a scenario in which both server and client are running on the same version of the .NET Framework. In both cases I noticed the DataSet issue.
Regards
Ruggero
We're heading into .NET 5 and Framework is eventually going away. Given you can wrap it in a DTO style POCO and Framework is able to do the serialization, I would not be inclined to spend much time on solving this. If you find a solution and want to submit a PR, I'm happy to take a look at it.
I've made some investigations. And the problem is not only with the DataSet
actually
The problem occurs if there are two (or more) assembly versions are available (in the GAC).
I have two System.Data
assemblies installed in the GAC:
gacutil /l System.Data
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
The Global Assembly Cache contains the following assemblies:
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=AMD64
System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86
System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=AMD64
System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=x86
It means that NetExtensions.ToConfigName
returns System.Data
assembly name for the DataSet
type.
But NetExtensions.ToType
(Type.GetType
actually) fails to get the assembly by short name.
The third line fails but first two works on my computer.
assembly = Assembly.Load("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
assembly = Assembly.Load("System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
assembly = Assembly.Load("System.Data");
So I suppose that this may happen for any .Net Framework assembly if there are several assembly versions loaded to the GAC
First solution is to remove 2.x assembly versions from the GAC (uninstall 2.x 3.5 .Net Framework)
Another one is to adjust the NetExtensions
source code.
I suppose it can be made using cached type to assembly name
list.
NetExtensions.ToConfigName
can load assembly by short name and return AssemblyQualifiedName
if load has failed.
Static dictionary can be used to speed up the assembly name calculation.
So assembly load check is performed once if the type has not been processed before.
Cached instance is used otherwise.
Regards
Unfortunately if we go with the full version, we break those who are communicating from .NET Framework to .NET Core or between two different versions of the same. The ultimate answer here is to either transform your DataSet to a POCO DTO of a type you control or to use a different serializer.