- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
EntityCollections and WebServices
Joined: 18-Aug-2003
I'm attempting to create a webservice that returns an EntityCollection. The function is simple, thus:
<WebMethod()> _
Public Function getFreeStuff(ByVal m_churchid As Integer) As ChumFreeStuffCollection
Dim m_result As ChumFreeStuffCollection
Dim m_church As New ChumChurchesEntity(m_churchid)
m_result = m_church.GetMultiChumFreeStuff(True)
getFreeStuff = m_result
End Function
but the function barfs this error message:
Cannot serialize member SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.Validator of type SD.LLBLGen.Pro.ORMSupportClasses.IValidator because it is an interface.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NotSupportedException: Cannot serialize member SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.Validator of type SD.LLBLGen.Pro.ORMSupportClasses.IValidator because it is an interface.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[NotSupportedException: Cannot serialize member SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.Validator of type SD.LLBLGen.Pro.ORMSupportClasses.IValidator because it is an interface.]
System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, Boolean canBePrimitive, MemberInfo memberInfo)
System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference)
System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source)
System.Xml.Serialization.StructModel.GetPropertyModel(PropertyInfo propertyInfo)
System.Xml.Serialization.StructModel.GetFieldModel(MemberInfo memberInfo)
System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns)
System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns)
System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns)
System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, Boolean repeats)
[InvalidOperationException: There was an error reflecting type 'chumdata.EntityClasses.ChumFreeStuffEntity'.]
System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, Boolean repeats)
System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType)
System.Xml.Serialization.XmlReflectionImporter.CreateArrayElementsFromAttributes(ArrayMapping arrayMapping, XmlArrayItemAttributes attributes, Type arrayElementType, String arrayElementNs)
System.Xml.Serialization.XmlReflectionImporter.ImportArrayLikeMapping(ArrayModel model, String ns)
System.Xml.Serialization.XmlReflectionImporter.ImportAccessorMapping(MemberMapping accessor, FieldModel model, XmlAttributes a, String ns, Type choiceIdentifierType)
System.Xml.Serialization.XmlReflectionImporter.ImportMemberMapping(XmlReflectionMember xmlReflectionMember, String ns, XmlReflectionMember[] xmlReflectionMembers)
System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement)
[InvalidOperationException: There was an error reflecting 'getFreeStuffResult'.]
System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement)
System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(String elementName, String ns, XmlReflectionMember[] members, Boolean hasWrapperElement)
System.Web.Services.Protocols.SoapReflector.ImportMembersMapping(XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, Boolean serviceDefaultIsEncoded, Boolean rpc, SoapBindingUse use, SoapParameterStyle paramStyle, String elementName, String elementNamespace, Boolean nsIsDefault, XmlReflectionMember[] members, Boolean validate)
System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs)
[InvalidOperationException: Method wsFreeStuff.getFreeStuff can not be reflected.]
System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs)
System.Web.Services.Description.SoapProtocolReflector.ReflectMethod()
System.Web.Services.Description.ProtocolReflector.ReflectBinding(ReflectedBinding reflectedBinding)
System.Web.Services.Description.ProtocolReflector.Reflect()
System.Web.Services.Description.ServiceDescriptionReflector.ReflectInternal(ProtocolReflector[] reflectors)
System.Web.Services.Description.ServiceDescriptionReflector.Reflect(Type type, String url)
System.Web.Services.Protocols.DocumentationServerType..ctor(Type type, String uri)
System.Web.Services.Protocols.DocumentationServerProtocol.Initialize()
System.Web.Services.Protocols.ServerProtocol.SetContext(Type type, HttpContext context, HttpRequest request, HttpResponse response)
System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing)
[InvalidOperationException: Unable to handle request.]
System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing)
System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response)
[InvalidOperationException: Failed to handle request.]
System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response)
System.Web.Services.Protocols.WebServiceHandlerFactory.GetHandler(HttpContext context, String verb, String url, String filePath)
System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, String path, String pathTranslated, Boolean useAppConfig) +699
System.Web.MapHandlerExecutionStep.System.Web.HttpApplication+IExecutionStep.Execute() +96
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +173
I suppose I could send the thing through an XmlWriter to a string and return that. I guess I thought collections and their decendents were serializable and could be returned through webservices. I may be mistaken. Anyone know?
Nice huh, those webservices
You've fallen in the same trap as I have: webservices is about XML, not about serialized stuff. The reason for this is that webservices are used to transfer data in a hierarchical way (XML) to other systems which might be running another platform. When you are using .NET as the client platform, you'll see that the client is re-defining all types you return from your webservice, because in theory it doesn't know these types. Webservices in .NET work like this: everything is first transformed to Xml and that Xml is then transfered to the client, and on the client consumed (interpreted, or transformed back to objects)
.NET uses one of the most useless and crappiest designed classes ever (sorry) to create the XML: XmlSerializer. This class is very dumb, it can't work with interfaces (however the SoapFormatter can... go figure). Interfaces are required for a generic framework (MS even uses them a lot). It also requires empty constructors and other things that pollute your code. It is also badly designed because it has a hardcoded codepath for the Dataset class. Why I don't know but it signals me, something fishy is going on behind the scenes because if you need to make an exception for the 'Dataset', more classes are probably not able to be transformed to Xml by this class and cause trouble and probably need to be treated as special classes but MS didn't implement functionality for this: you can implement ISerializable (I have) but that doesn't matter... Also, don't think that when an object is serializable with Soap, it is serializable by a webservices. Apparently the Soap formatter can transform objects to Xml without a problem and can work with interfaces, the XmlSerializer cannot.
Nevertheless, some time ago I wanted to see if I could rewrite the runtime libraries to make it work with XmlSerializer. I couldn't get it to work and till today I don't know what was wrong. I rewrote all classes to work with base classes instead of interfaces. This caused some trouble for the generated code (which was unsolvable) but it was a test to use the XmlSerializer, so I ignored that. When I had rewritten it and wanted to use the objects returned from my webservice I couldn't get it to work because of the re-definition of all my objects. I had to manually change (searched for it on google and MS said in a newsgroup you should do this! ) the generated class file on the client. I also had to define all the types I was returning as 'known types' on the webservice method. This also wasn't very nice. But when I tried to run it, it just 'hang'. The XmlSerializer got caught in an infinite loop and the server ate 10MB of memory away per second.
After 2 days of fighting with that crappy class I gave up. "Webservices is not for easy transportation of object hierarchies between .NET applications" was the conclusion. But now what to do?
----------------------------------------------------------
Solution 1: Remoting.
You should use remoting to communicate objects between .NET applications. Below I've pasted 3 blocks of code. Each block is in its own assembly: client, server and interfaces. Interfaces is necessary so server and client use the same interface to communicate with. It's a modified example from the book Programming C# by Jesse Liberty
Interfaces:
using System;
using SD.NorthwindDAL.EntityClasses;
namespace Interfaces
{
public interface IRemoteTest
{
CustomerEntity GetCustomer(string customerID);
}
}
Server:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Interfaces;
using SD.NorthwindDAL.EntityClasses;
namespace Server
{
public class MyServer: MarshalByRefObject, IRemoteTest
{
public MyServer()
{
}
public CustomerEntity GetCustomer(string customerID)
{
Console.WriteLine("Customer request recieved for customer: {0}", customerID);
CustomerEntity c = new CustomerEntity(customerID);
return c;
}
}
/// <summary>
/// Summary description for Class1.
/// </summary>
public class ServerTest
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
HttpChannel chan = new HttpChannel(65100);
ChannelServices.RegisterChannel(chan);
Type serverType = Type.GetType("Server.MyServer");
RemotingConfiguration.RegisterWellKnownServiceType(serverType, "theEndPoint", WellKnownObjectMode.Singleton);
Console.WriteLine("Press Enter To Exit");
Console.ReadLine();
}
}
}
Client:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using Interfaces;
using SD.NorthwindDAL.EntityClasses;
namespace RemotingTester
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Startup
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// register http channel
HttpChannel chan = new HttpChannel(0);
ChannelServices.RegisterChannel(chan);
MarshalByRefObject o = (MarshalByRefObject)RemotingServices.Connect(typeof(Interfaces.IRemoteTest),"http://geek:65100/theEndPoint");
IRemoteTest server = o as IRemoteTest;
CustomerEntity c = server.GetCustomer("CHOPS");
PrintCustomer(c);
Console.ReadLine();
}
public static void PrintCustomer(CustomerEntity enCustomer)
{
// print the customer object read
Console.WriteLine("-----[Customer]--------");
Console.WriteLine("CustomerID: {0}", enCustomer.CustomerID);
Console.WriteLine("Address: {0}", enCustomer.Address);
Console.WriteLine("City: {0}", enCustomer.City);
Console.WriteLine("CompanyName: {0}", enCustomer.CompanyName);
Console.WriteLine("ContactTitle: {0}", enCustomer.ContactTitle);
Console.WriteLine("Country: {0}", enCustomer.Country);
Console.WriteLine("Fax: {0}", enCustomer.Fax);
Console.WriteLine("PostalCode: {0}", enCustomer.PostalCode);
Console.WriteLine("Region: {0}", enCustomer.Region);
Console.WriteLine("\n");
}
}
}
---------------------------------------------------------- Solution 2: Serialized text. There is another solution: if you still want to use webservices, you can use the code snippet below. The idea is this: you serialize the objects to a memory stream with Soap. The memory stream contains a string with the Soap data. You can then return the string in that memory stream as the return value of the WebMethod. On the client, create a memory stream from the string and deserialize the objects from the memory stream.
Server:
SoapFormatter formatter = new SoapFormatter();
MemoryStream writeStream = new MemoryStream();
CustomerEntity customer = new CustomerEntity("CHOPS");
formatter.Serialize(writeStream, customer);
string serializedResult = System.Text.Encoding.UTF8.GetString(writeStream.ToArray());
return serializedResult;
Client:
// serializedResult is the return value of the WebMethod called.
MemoryStream readStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(serializedResult));
CustomerEntity customerDeserialized = (CustomerEntity)formatter.Deserialize(readStream);
Remoting is definitely the right choice it's more efficient and It Just Works(tm)
Btw: ObjectSpaces, the O/R mapper layer from Microsoft which will be part of .NET 2.0 throws the same exceptions. Let's hope MS solves the XmlSerializer to fix this so we all benefit from this, but I fear the XmlSerializer will get just another exceptional code path, especially for ObjectSpaces...
swallace wrote:
What a pain in the patoot. WebServices are my only choice, so that's that route I'll have to go.
Just out of curiosity, why are webservices your only choice? Because when it comes to .NET <-> .NET, webservices are the worst choice of all, due to the fact that they're a developer burden and very slow. (when you're transfering XML in a given standard format, that's another story).
Joined: 18-Aug-2003
I'm unable to control the client. The product is an ASP.NET web application, and I want to create webservice APIs to allow users to publish the gathered data into their own web sites. Those wacky web developers come from all kinds of environments, many of them non- .NET and even non-Windows. I'm looking for the lingua franca, ie, webservices. I don't expect the end-users to actually understand what an EntityCollection is, or to use your product to consume it (sorry). At this point I want to publish out the XML in some standard format and let them work with it on their own site. I guess I thought that serializing the Entity would convert it to XML and let me bang it out the pipe that way.
I've used webservices under .NET extensively in the past, delivering datasets with ease. They are very standard and easily consumed by most non-standard clients. With a non-dataset style collection I have to document more, but that's not so bad.
If you just want to convert it to Xml, you can use the .ToXml() method of the entity collection objects
I think that will be even better than the soap trick, because the other platforms can't convert it back to an object anyway. I understand now why you picked webservices, and I think you can best return the Xml using the ToXml() method, which converts a complete collection including entities to Xml.
Entities also have a ToXml() method.
Joined: 01-Oct-2004
I am trying exactly what you suggested. However, this code:
// .... some code to fetch the collection ... //
XmlNode colXml = col.ToXml();
results in
System.NotSupportedException: Cannot serialize member SD.LLBLGen.Pro.ORMSupportClasses.EntityField.ExpressionToApply of type SD.LLBLGen.Pro.ORMSupportClasses.IExpression because it is an interface.
I'm using the latest release numbered 1.0.2004.1.
Thanks for your help!
Joined: 02-Jun-2004
I, too, am using webservices and because of the rule we have here to make the front end not coupled to LLBLGen, I've generated custom classes that are used on the client. I also generate a helper class for each llblgen entity, which creates the xml, which is returned to the client and then the custom class has a method that reads through the xml and populates its own properties. I feel I'm doing way too much work here just to get the data across the wire. The architecture decisions here are to use web services, and no llblgen classes on the client, so I'm going to great lengths to create the generic xml from the llblgen entity classes, and reading the xml to populate the llblgen entity classes when I need to write. I think there's a question in here somewhere. It is - is there an easier way to do this and still meet the requirements of 1. using web services and 2. no using LLBLGen on the client?
Thanks, Dave
IowaDave wrote:
I, too, am using webservices and because of the rule we have here to make the front end not coupled to LLBLGen, I've generated custom classes that are used on the client. I also generate a helper class for each llblgen entity, which creates the xml, which is returned to the client and then the custom class has a method that reads through the xml and populates its own properties. I feel I'm doing way too much work here just to get the data across the wire.
I think so too. What you should do is use the WriteXml() method build into the entity classes to produce XML. Then, you copy the code of ReadXml() from the runtime lib source and build your Xml reader from that. The Xml produced by WriteXml() is very straightforward and easy to understand. There is one tag which needs attention, the tag which is the placeholder for a cyclic reference. But that's it.
The architecture decisions here are to use web services, and no llblgen classes on the client, so I'm going to great lengths to create the generic xml from the llblgen entity classes, and reading the xml to populate the llblgen entity classes when I need to write. I think there's a question in here somewhere. It is - is there an easier way to do this and still meet the requirements of 1. using web services and 2. no using LLBLGen on the client?
Well, I think you just need a consumer routine on the client which understands the easy Xml produced by WriteXml(). That routine is already there, in the orm support classes, so you can base your routine on that code. You can probably skip a lot of code in that routine, as you probably only need the Fields data.
Joined: 25-May-2004
I also use webservice, and on solution design we decide to not use llblgen object on the client side. The webservice had to be consumed by different type of client (.NET SmartClient and web site in php) So I wrote a DataSetGenerator, a piece of code that transform an entity (or entitycollection) to a dataset. So we use typed dataset as return type of our webmethod. Of course I also wrote an EntityGenerator to transform a typed dataset into a (collection of) entity (and keep all related object, new/dirty/deleted state etc).
Reflection powaaaa
PS : I've not use the WriteXML() method, because it's the only point of llblgen I don't like (take too much time, and generated XML is very very larger than dataset xml.
The xml is verbose, but it contains true type information for each field. This is important when you store the data somewhere or you want to use it in whatever system, other than a dataset container.
Joined: 02-Jun-2004
Well, I think you just need a consumer routine on the client which understands the easy Xml produced by WriteXml(). That routine is already there, in the orm support classes, so you can base your routine on that code. You can probably skip a lot of code in that routine, as you probably only need the Fields data.
I agree I just only need the fields. I am curious why Fabrice is saving all the flags - is dirty etc. This being truly stateless, the data should be the only thing in the entity class of interest. If the web method is aware of whether it's an update or insert method, I should be able to create a new instance of the entity and populate the data, set the is new or not new depending on which method and do a save. Correct?
Joined: 25-May-2004
I don't put all llblgen properties in the dataset As I work by reflection, it's quite simple to do : I only take fields defined in the generated classes, not the fields declared in the class above (IEntity2). And if I want to add a field, It'll be directly present in the dataset In the other direction (dataset -> entity), with the RowState you know how to set the IsNew & isDirty flags (and I add an isDeleted flags)
Otis wrote:
The xml is verbose, but it contains true type information for each field. This is important when you store the data somewhere or you want to use it in whatever system, other than a dataset container.
Yes, I fully agree, but when it's not needed, it's quite useless to have the same data (field type for each entity) take a lot of place. The dream is to have the choice
Joined: 02-Jun-2004
Oh, I see what you are doing, which makes sense since you've decided to return/pass datasets. We have a rule here that we won't return any .NET-only data types (eg Datasets) This is based on the theory that the return value (the xml) should be generic so the front end can be a non-.NET client. This is based on the fact that we have products(applications) in our company that are java, etc and a desire to make everything as open as possible. These contraints create a lot of extra work, it's true.
Fabrice wrote:
I don't put all llblgen properties in the dataset As I work by reflection, it's quite simple to do : I only take fields defined in the generated classes, not the fields declared in the class above (IEntity2). And if I want to add a field, It'll be directly present in the dataset In the other direction (dataset -> entity), with the RowState you know how to set the IsNew & isDirty flags (and I add an isDeleted flags)
You can also just traverse the Fields object to grab the fields of an entity, no need for reflection
Otis wrote:
The xml is verbose, but it contains true type information for each field. This is important when you store the data somewhere or you want to use it in whatever system, other than a dataset container.
Yes, I fully agree, but when it's not needed, it's quite useless to have the same data (field type for each entity) take a lot of place. The dream is to have the choice
![]()
You'd opt for a more lightweight XML producing method overload?
Joined: 25-May-2004
Otis wrote:
You can also just traverse the Fields object to grab the fields of an entity, no need for reflection
![]()
I agree but reflection is needed - in case I add new property (computed property for example) to the entity (I work with derived entities). This property will not be in the fields list. - to add pk and fk in the generated dataset
Otis wrote:
You'd opt for a more lightweight XML producing method overload?
Exactly !
But .. for me it's allready too late, I can't change that so close to the release
Fabrice wrote:
Otis wrote:
You can also just traverse the Fields object to grab the fields of an entity, no need for reflection
![]()
I agree but reflection is needed - in case I add new property (computed property for example) to the entity (I work with derived entities). This property will not be in the fields list. - to add pk and fk in the generated dataset
Good points
Otis wrote:
You'd opt for a more lightweight XML producing method overload?
Exactly ! But .. for me it's allready too late, I can't change that so close to the release
![]()
I'll see what I can come up with in the future, I'll add it to the todo list. (although I really hate XSchema...)
Joined: 25-May-2004
IowaDave wrote:
Oh, I see what you are doing, which makes sense since you've decided to return/pass datasets. We have a rule here that we won't return any .NET-only data types (eg Datasets) This is based on the theory that the return value (the xml) should be generic so the front end can be a non-.NET client. This is based on the fact that we have products(applications) in our company that are java, etc and a desire to make everything as open as possible. These contraints create a lot of extra work, it's true.
We are consuming the webservice with a .NET client yes, but also with a php website (and more external client in the future). DataSet are maybe a .NET element, but it's firstly XML so it's not difficult to make a XML parser for dataset, as you will do for the "normal" XML. But I understand your point of view. We have discussed a lot before choosing (typed) dataset.