passing EntityCollection with WCF

Posts   
1  /  2
 
    
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 07-Feb-2007 09:20:41   

Hi guys,

First some info: - Version: LLBLGen Pro 2.0.0.0 - RTL: 2.0.0.61205 - DB: SQL 2005 - Template: Adapter with .Net 2.0

My problem is pretty straightforward: I want to send an EntityCollection across a wire using WCF (using tcp-binding). I've already managed to succesfully sent IEntity2 objects.

Here's my wcf interface:


using DataAccessLayer.HelperClasses;
using SD.LLBLGen.Pro.ORMSupportClasses;
using System.ServiceModel;

namespace WcfInterfaces
{
    [ServiceContract]
    public interface IGenService
    {
        [OperationContract]     
        int SaveEntityCollection( EntityCollection collection );
    }
}

And my wcf implementation:


using System;
using WcfInterfaces;
using DataAccessLayer.DatabaseSpecific;
using DataAccessLayer.HelperClasses;
using SD.LLBLGen.Pro.ORMSupportClasses;

namespace WcfService
{
    public class GenService : IGenService
    {
        public int SaveEntityCollection( EntityCollection collection )
        {
            int persistedEntities = 0;
            try
            {
                using (DataAccessAdapter da = new DataAccessAdapter())
                {
                    persistedEntities = da.SaveEntityCollection( collection, true, true );
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine( "Error in calling SaveEntityCollection(EntityCollection): " + ex.Message );
            }
            return persistedEntities;
        }
    }
}

The first problem is immediately in generating the proxy using svcutil. With above interface & implementation I get the following (relevant) generated code:


using System.Data;

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IGenService")]
public interface IGenService
{
    
    // CODEGEN: Parameter 'collection' requires additional schema information that cannot be captured using the parameter mode. The specific attribute is 'System.Xml.Serialization.XmlElementAttribute'.
    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IGenService/SaveEntityCollection", ReplyAction="http://tempuri.org/IGenService/SaveEntityCollectionResponse")]
    [System.ServiceModel.XmlSerializerFormatAttribute()]
    SaveEntityCollectionResponse SaveEntityCollection(SaveEntityCollectionRequest request);
}
...

The codeGen seems to have difficulties with acquiring all necessary info. Suddenly "System.Data" is used, and the [XmlSerializerFormatAttribute] is added.

This is how my proxy code looks when it's generated correctly (another function):


[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IGenService")]
public interface IGenService
{   [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IGenService/FetchEntity", ReplyAction="http://tempuri.org/IGenService/FetchEntityResponse")]
    object FetchEntity(object entity);

I'm not sure where the problem lies, and what I can do about it.

I've also tried so change the parameter type to "IEntityCollection2". In this scenario, the proxy is generated fine, but the collection is empty when it arrives at the server (where it was correctly filled at the client).

Note: I'm not an WCF/Webservices/LLBL expert (yet), so the solution might be really simple simple_smile

edit: I've read in another post there were problems with the transformation of the brackets to < and >. I also see this in my message log, but this problem should be fixed already?!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 07-Feb-2007 20:51:20   

Could you enable the webservice related tasks in the adapter preset? As described in the webservice support section in the adapter manual?

I have no experience with WCF at the moment, so I'm not sure if these steps are necessary anymore, but it seems likely, unless you define a datacontract. As you're not defining a datacontract, a schema discovery has to be done like wsdl.exe did/does, so stub classes can be generated. These stub classes will use datasets etc, unless the schema is known. In the manual a step-by-step approach is described how to get this working for normal webservices.

If you select remoting instead, it works without these steps.

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 08-Feb-2007 14:34:21   

Ok, I followed the steps successfully.

I have a generated proxy with some extra information related to EntityCollection objects, which looks promising...

I can't test it at the moment. I will get back on this tomorrow

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 08-Feb-2007 15:21:04   

I don't know if you need the proxy classes though. I now have a simple client / server running with wcf, using xml serialization, but these don't use proxy classes.

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 08-Feb-2007 15:34:23   

My current methodology is: - create the public interface - create the service implementation - run the host - create the proxy using svcutil (which reads the meta-data from the running host) - use the proxy on the client to call specific methods...

Of course, it's also possible to code it directly on the client. I don't think that really matters in this case.

I'm curious what is (minimally) required to successfully transfer EntityCollections.

But have you sent an EntityCollection across the wire? If so, I would be pleased if you would share this solution wink

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 08-Feb-2007 16:11:53   

Jowen wrote:

My current methodology is: - create the public interface - create the service implementation - run the host - create the proxy using svcutil (which reads the meta-data from the running host) - use the proxy on the client to call specific methods...

Of course, it's also possible to code it directly on the client. I don't think that really matters in this case.

I think it does. the proxy classes creator requires a schema up front, which isn't there. So you need the extra code you now enabled.

I'm curious what is (minimally) required to successfully transfer EntityCollections. But have you sent an EntityCollection across the wire? If so, I would be pleased if you would share this solution wink

  • use non-generic entitycollections only.
  • if you create proxies, the schemaimporter project and hte webservice helper classes are necessary.
  • if you don't and use WCF, you can code against the service via an interface.

Yes I have passed an entity collection across webservices, I used proxies though and vanilla .net 2.0 webservices. The code is very simple: a webmethod which returns entitycollection and a client which calls that method simple_smile . What's key is to follow the steps in the manual otherwise you will get bad proxy classes.

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 10:11:34   

After enabling the schema importer, my proxy has extra code related to the EntityCollection:


[assembly: System.Runtime.Serialization.ContractNamespaceAttribute("http://DataAccessLayer/xml/serialization", ClrNamespace="dataaccesslayer.xml.serialization")]

namespace dataaccesslayer.xml.serialization
{
    using System.Runtime.Serialization;
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute()]
    public partial class EntityCollection : object, System.Runtime.Serialization.IExtensibleDataObject
    {
        
        private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
        
        public System.Runtime.Serialization.ExtensionDataObject ExtensionData
        {
            get
            {
                return this.extensionDataField;
            }
            set
            {
                this.extensionDataField = value;
            }
        }
    }
}


[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IGenService")]
public interface IGenService
{   
...

[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IGenService/SaveEntityCollection", ReplyAction="http://tempuri.org/IGenService/SaveEntityCollectionResponse")]
    int SaveEntityCollection(dataaccesslayer.xml.serialization.EntityCollection collection);

...
}

But I have no idea how to use it now confused The new dataaccesslayer.xml.serialization.EntityCollection totally differs from the original DataAccessLayer.HelperClasses.EntityCollection (it only has this ExtensionData property).

Should I refactor the existing code? For instance, now I have something like this:


EntityCollection collection = new EntityCollection(new FooEntityFactory());

but this is never going to work....

note: the original EntityCollection I use inherits from "EntityCollectionNonGeneric", so I also satisfied your first requirement.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 12:34:49   

Did you also enable the webservice helper class task in the adapter preset ?

Still odd that the proxy generator tool completely messes up, as it doesn't use the schema importer project at all, as it simply cooks up new types instead of using the types supplied by the schemaimporter (i.e. the types in the generated code).

Could you also try by simply compiling your code against the interface and not use the proxy classes ?

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 14:13:24   

Ah, I was assuming the schema importer worked correctly because it generated extra code... (I'm partly glad the generated code is a mess, because that explains a lot)

But do you have any idea what can be the reason of this not working? (I don't even know what the expected result should look like) I've ran through all the steps again, but get the same results.

I'm a bit opposed to NOT using the proxy. This because: 1) I think it's a good methodology (client and server are always in sync) 2) I doubt it if it has advantages over using a proxy. Will it solve my EntityCollection problems? 3) I don't have the knowledge to directly write the client code. Of course I can find out how, but this takes extra time...

Again, I'm not an expert in this, so I'm easy to persuade simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 14:36:03   

Why not use a [ServiceContract] ? Example: you write an interface, which is shared between server and client (a better way to keep things in sync than a proxy stub IMHO)

(code borrowed from a WCF example I used yesterday to track down a bug, don't know if it's fully working in your case)


[ServiceContract]
[ServiceKnownType(typeof(CustomerEntity))]
[ServiceKnownType(typeof(EntityCollection))]
public interface IWCFTest
{
    [OperationContract]
    IEntity2 GetCustomer(string customerID);

    [OperationContract]
    IEntityCollection2 GetCustomers();
}

Then, in the client you reference this interface and simply do:


ChannelFactory<IWCFTest> channelFactory =
        new ChannelFactory<IWCFTest>("WCFTestServer");
IWCFTest serverTest = channelFactory.CreateChannel();

IEntity2 c = serverTest.GetCustomer("CHOPS");
IEntityCollection2 customers = serverTest.GetCustomers();

Server:


// class to implement the service logic
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFTestServer : IWCFTest
{
    public IEntity2 GetCustomer(string customerID)
    {
        CustomerEntity toReturn = new CustomerEntity(customerID);
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntity(toReturn);
        }
        return toReturn;
    }

    public IEntityCollection2 GetCustomers()
    {
        EntityCollection toReturn = new EntityCollection(new CustomerEntityFactory());
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(toReturn, null);
        }
        return toReturn;
    }
}

// class to actually form the service:
public class WCFTestServerHost
{
    public WCFTestServerHost()
    {
        WCFTestServer server = new WCFTestServer();
        ServiceHost host = new ServiceHost(server);
        host.Open();
    }
}

NO Proxies generated, no webservice reference made. The client programs against the interface and the service does too. Then you start the service process and the client process.

The client is configured (also the service) through the config file.

I'm a newbie in WCF too, and soon I hope to know much more about this and be able to suggest what to do in WCF. I do know that the model above (with different code but should work the same) does work.

No schema-importer/proxies required.

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 14:44:06   

I already have that ServiceContract defined. Only now I create a proxy based on that contract, instead of sharing the interface itself on the clientside. Sharing the interface is not a bad alternative (as I can think of), so I will definitely give it a try. The method of creating the proxy is more promoted by Microsoft, but we should not follow them blindly!

I'll experiment with your suggestion, and get back to you on the outcome.

Thanks so far for your quick & clear responses...

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 14:58:08   

I'm currently creating the code I posted in a working example. I think the proxies are a good solution if a datacontract is defined, so you can have DTO classes defined on the client.

When the objects passed aren't known on teh client (i.e. no shared code between service and client) it's IMHO best to get stubs created and new classes generated, so the proxies generated aren't that bad, though you want to use entities on the client so you should use the interface method I think (similar to remoting, but then using xml under the hood).

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 15:12:58   

Hmm, the entity collection stays empty on the client side... rage

(edit). hmm. the factory is empty... odd. That's why it quits. the data itself does arrive on the client side.

(edit) the factory IS in the xml, though I didn't specify a namespace so the XPath stuff might give incorrect results... hmm.

(edit3): Arg... frowning ... the XML is placed as text in the node... all <> are < and >...

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 15:28:16   

Otis wrote:

Hmm, the entity collection stays empty on the client side... rage

(edit). hmm. the factory is empty... odd. That's why it quits. the data itself does arrive on the client side.

Damn... that's pretty annoying. rage

There's something fishy with that EntityCollection... I have no problems transferring IEntity2 objects.

But cool to see your finally gaining some experience with WCF wink

Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 15:38:07   

Otis wrote:

(edit3): Arg... frowning ... the XML is placed as text in the node... all <> are < and >...

I had the same problem. See the end note in my start post...

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 15:54:01   

Jowen wrote:

Otis wrote:

(edit3): Arg... frowning ... the XML is placed as text in the node... all <> are < and >...

I had the same problem. See the end note in my start post...

I think I found it. The XmlWriter.WriteRaw apparently )($#&(&$@(# up. Though! the entity.WriteXml(writer) does work, so peeking into that code, I found a different piece of code.

The writeraw works in normal webservices, though apparently not in .net 3.0 (where a different serializer is used.. )

Adding that... BINGO!

In a hierarchy: no problem: fetching customers + orders, it works.

I'll make sure the build of this new runtime gets released today. For your convenience I'll attach it to this post. (attached)

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 16:05:27   

Ok that gives me hope...

But just to be clear: should I still use the SchemaImporter?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 16:20:30   

Jowen wrote:

Ok that gives me hope...

But just to be clear: should I still use the SchemaImporter?

No, you don't have to. Use the interface approach, and the types are correctly converted for you. I've attached the latest build to the previous message.

Frans Bouma | Lead developer LLBLGen Pro
Jowen
User
Posts: 47
Joined: 06-Feb-2007
# Posted on: 09-Feb-2007 16:28:26   

(edit)

Ok, stubborn as I am, I first tried it WITH the proxy (less work to test)...

And it seems to work! smile You have no idea how relieved I am.

A bit annoying that there was a problem with the xml writer, but your quick and helpful response compensated that more than enough simple_smile

I guess this is a pretty important update, hope that other LLBL Gen users don't have to go through the same hassle I did.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 09-Feb-2007 17:19:43   

Thanks for the feedback! simple_smile

Still odd that the proxies were now created correctly though. Anyway, it works simple_smile

Frans Bouma | Lead developer LLBLGen Pro
llblPowa
User
Posts: 41
Joined: 19-Nov-2006
# Posted on: 01-Mar-2007 02:09:57   

Hello Otis,

Could you post a simple WCF working example (client+server) using a common db (ie Northwind or AdventureWorks)?

Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 01-Mar-2007 10:04:27   

llblPowa wrote:

Hello Otis,

Could you post a simple WCF working example (client+server) using a common db (ie Northwind or AdventureWorks)?

Thanks.

It's above this post in this same thread simple_smile

http://www.llblgen.com/TinyForum/GotoMessage.aspx?MessageID=49680&ThreadID=8927

Frans Bouma | Lead developer LLBLGen Pro
bakman
User
Posts: 14
Joined: 07-Mar-2007
# Posted on: 07-Mar-2007 15:14:01   

Hi Otis

Have you by any chance tried this for entites of type "TargetPerEntityHierarchy"?

In my test case I have 3 tables: Contact, Address and ContactAddressRelation. The Contact table is a TargetPerEntityHierarchy type (abstract) with 2 subtypes: Person and Location.

Serializing a single ContactEntity (the actual type is PersonEntity) works perfectly:


IRelationPredicateBucket filter = new RelationPredicateBucket(ContactFields.ContactId == id);
IPrefetchPath2 path = new PrefetchPath2((int)EntityType.ContactEntity);
path.Add(ContactEntity.PrefetchPathAddressCollection);

ContactEntity entity = adapter.FetchNewEntity(new ContactEntityFactory(), filter, path) as ContactEntity;

return entity;

but sending a collection of ContactEntities:


EntityCollection list = new EntityCollection(new ContactEntityFactory());

DataAccessAdapter adapter = new DataAccessAdapter();
adapter.FetchEntityCollection(list, null);

return list;

fails with a NullReferenceException: {"Object reference not set to an instance of an object."}:



Server stack trace: 
   at SD.LLBLGen.Pro.ORMSupportClasses.EntityFields2.ReadXml(XmlNode fieldsElement)
   at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
   at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.Xml2EntityCollection(XmlNode node, Dictionary`2 processedObjectIDs, List`1 nodeEntityReferences)
   at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.ReadXml(XmlNode node)
   at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.ReadXml(XmlReader reader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadIXmlSerializable(XmlSerializableReader xmlSerializableReader, XmlReaderDelegator xmlReader, XmlDataContract xmlDataContract, Boolean isMemberType)
   at System.Runtime.Serialization.XmlDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, DataContract& dataContract)
... removed ...

When using an AddressEntityFactory everything works perfectly.

I have an e2e trace of the entire flow if required.

Any help will be greatly appreciated.

Regards Lau

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 07-Mar-2007 16:02:27   

Which runtimeLibrary version are you using?

bakman
User
Posts: 14
Joined: 07-Mar-2007
# Posted on: 07-Mar-2007 16:07:11   

Hi Walaa

I am using SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll product version 2.0.07.0219

I Just downloaded and installed it today (after uninstalling the "old" one).

Regards Lau

1  /  2