Passing EntityCollection with WCF (LINQ generated)

Posts   
 
    
Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 14-Mar-2009 23:51:44   

LLBLGen Pro 2.6 Final Builder 2.6 Final, October 6th, 2008 Adapter for .NET 3.5 ORMSupportClasses.NET20 version: 2.6.9.116 LinqSupportClasses.NET35 version: 2.6.9.116 DQE.SqlServer.NET20 version: 2.6.8.1114 Database: SQL Server 2005

This question has to do with the WCF example used in the documentation and the WCF example application posted by Walaa:

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

In LINQ, I use the following to execute the query so I can pass the collection back to the consuming class:

EntityCollection<MovieEntity> entities = ( (ILLBLGenProQuery)q ).Execute<EntityCollection<MovieEntity>>();

If I were trying to do the same thing as the example code posted in the documentation, except for using LINQ, how should the collection be cast so that it works properly after being remoted?

Thanks in advance!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 16-Mar-2009 08:38:02   

Therefore you've to expose non-generic entitycollections, e.g. EntityCollection the non-generic one. Try casting to the interface IEntityCollection2, which is the non-generic interface to the generic collection.

Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 16-Mar-2009 09:25:10   

Walaa wrote:

Therefore you've to expose non-generic entitycollections, e.g. EntityCollection the non-generic one. Try casting to the interface IEntityCollection2, which is the non-generic interface to the generic collection.

Hi Walaa,

I guess I should have mentioned I had tried that. While this works perfectly if it's not run remotely through WCF (I have a flag to turn remoting on/off), the collection throws an exception once it's remoted. The exception itself is of little use (the standard timeout exception that seems to occur when just about anything wrong happens).

Your example should throw the same exception. I'll try converting yours to a LINQ query later this morning, but my example followed yours conceptually.

You can probably just convert your example to a generic and then directly cast the generic to the IEntityCollection2 interface. It appears that for some reason:

EntityCollection(new CustomerEntityFactory())

is what makes your example work, hence the original casting question for an LINQ query since:

EntityCollection<MovieEntity> entities = ( (ILLBLGenProQuery)q ).Execute<EntityCollection<MovieEntity>>();

doesn't seem to be castable to anything that works when remoted.

Thanks!

Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 16-Mar-2009 13:25:31   

Hi Walaa,

I've just confirmed the same behavior with your example. If I change your GetAllCustomers method that returns an IEntityCollection2 like this (only change in example):

//EntityCollection toReturn = new EntityCollection( new CustomersEntityFactory()); EntityCollection<CustomersEntity> toReturn = new EntityCollection<CustomersEntity>();

The client gets the same exact timeout error I get when I use the LINQ example (which is just doing the same kind of conversion directly from a generic to an IEntityCollection2).

Any thoughts on how to get this (or more specifically the LINQ example) to work?

Thanks!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 16-Mar-2009 14:01:57   

Use he following collecion:

EntityCollection2 movies = new EntityCollection2(new MovieEntityFactory());

And either look on the generic one to fill this non-generic collection.

or the following:

movies  = (IEntityColection2) ( (ILLBLGenProQuery)q ).Execute<EntityCollection<MovieEntity>>();
Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 16-Mar-2009 15:23:02   

Walaa wrote:

Use he following collecion:

EntityCollection2 movies = new EntityCollection2(new MovieEntityFactory());

And either look on the generic one to fill this non-generic collection.

or the following:

movies  = (IEntityColection2) ( (ILLBLGenProQuery)q ).Execute<EntityCollection<MovieEntity>>();

Hi Walaa,

It really seems like something very different is happening between this:

EntityCollection toReturn = new EntityCollection( new CustomersEntityFactory()); using ( ...CODE returntoReturn;

and this:

EntityCollection<CustomersEntity> toReturn = new EntityCollection<CustomersEntity>(); using ( ...CODE return (IEntityCollection2)toReturn;

The reason I keep going back to the generic instantiation of the collection versus the factory version, is that I'm trying to keep your example as close to possible to what I'm doing with LINQ.

The generic version still throws the same exact timeout exception in your example and doing this in LINQ:

IEntityCollection2 collection = (IEntityCollection2)((ILLBLGenProQuery)q).Execute<EntityCollection<DateAccuracyTypeEntity>>(); ... return collection;

There was an earlier thread in which a bug in the serializer was causing some funkiness when remoting.

I think it's important to point out again that this works perfectly when I'm using the same code but not remoting. As soon as it accesses it remotely, it throws the generic timeout exception.

If you use the code I have above (using generics cast to IEntityCollection2) in your WCF remoting example, I think you should be able to duplicate this behavior. It seems something is going on in the marshalling of the data.

Thanks!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 17-Mar-2009 06:06:33   

my previous post was about passing a non-generic entityCollection

EntityCollection2 movies = new EntityCollection2(new MovieEntityFactory());

Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 17-Mar-2009 09:24:51   

Walaa wrote:

my previous post was about passing a non-generic entityCollection

EntityCollection2 movies = new EntityCollection2(new MovieEntityFactory());

Hi Walaa,

Is there an EntityCollection2 somewhere? Or is part of the above supposed to be IEntityCollection2? If the latter, that's the problem I'm having. If the former, where is EntityCollection2?

I truly don't know if I'm just missing something really, really obvious, like is my computer plugged in, or if we're just not on the same page.

Let me try to get back to my specific example with a specific question. Should the following work?:

IEntityCollection2 collection = (IEntityCollection2)((ILLBLGenProQuery)q).Execute<EntityCollection<DateAccuracyTypeEntity>>(); ... return collection;

Assuming the method signature has the return parameter as an IEntityCollection2 interface? It seemed to be what you were telling me to do in your earlier response? If so, while it works in a non-remoting situation, it breaks when remoting under WCF.

If the above isn't the cast you thought should work for a generic under LINQ, what is the specific example that will work?

Thanks for hanging in there!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 17-Mar-2009 14:41:40   

You are right, There is no EntityCollection2.... it looks like I was missing the morning coffee shot rage

Relevant threads: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=8796 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=9670

We'll look into this and get back to you.

Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 17-Mar-2009 17:03:07   

Walaa wrote:

You are right, There is no EntityCollection2.... it looks like I was missing the morning coffee shot rage

Relevant threads: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=8796 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=9670

We'll look into this and get back to you.

Hi again Walla,

Thanks for the threads. As stated earlier, I should have started the forum entry with what I'd already tried, which included the standard IEntityCollection2 conversion.

As a little background on what I'm doing, I'm formalizing how we'll be using LLBLGen in our standard architecture moving forward. I have a simple demo movie application as an example of the architecture. The architecture consists of the following four primary logical layers: Data, Business/Manager, Service, and Presentation (MS MVC/WPF, etc.) While the application is being used as a teaching device internally within our company, I will probably post it on the MSDN Code Gallery site in the near future.

The Data layer is the only layer that touches the DatabaseSpecific parts of LLBLGen. It's also designed to work as efficiently as possible, so when it passes back LLBL collections, it will pass back the type specific generics version.

The Business/Manager layer is the standard layer with all application business logic. It's the only layer that directly touches the Data layer. It also uses generic EntityCollections, since they're most performant. The website will almost always use the Business/Manager layer, unless security or other requirements force this layer to actually run in a separate physical layer, as an application server.

The Service layer is basically a facade over the Business/Manager layer, and any casting that's necessary to meet the requirements of the remoting technology is done here (such as passing back IEntityCollection2 collections as opposed to the generics version). The primary consumer of this layer would be the WPF application, or a web application that's forced to access data through a secured application server as mentioned further above.

The WPF applications have a configuration setting that allows them to access data through the remote service interface via the WCF ChannelFactory, or through the in-process service object itself. So if we have a WPF application that needs to run outside of the firewall, all service calls will be made through remote WCF interface. For most admin and data entry users that are working within the firewall, they'll have access to the in-process calls and won't be forced to be slowed down by the remote data marshalling. The only negative aspect for them will be the casting that happens in the Service facade, but it's a small price to pay for the ease of being able to configure the application as either remote or in-process.

The remote/in-process switching code is trivial and I did it with your example WCF application in a few minutes. It basically just branches based on a configuration setting to either instantiate a new ChannelFactory and assign it to the MainForm.DalService, or directly assigns the NorthwindService to MainForm.DalService.

This is where I discovered that while your example works perfectly in both the remoting/in-process scenarios (since it's using EntityFactory to instantiate the EntityCollection), the Generics version throws an exception when remoting, but works perfectly when in-process.

Sorry for the lengthy post, just thought I'd give this a little context.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 17-Mar-2009 18:10:36   

First of all, please keep the terminology correct otherwise things get confusing wink . If you talk about 'remoting' I presume you don't mean .NET Remoting, but you mean sending the objects over the wire using WCF. Is that correct? Because .NET remoting and WCF are two completely different things which have nothing in common other than that they're techniques to talk to services wink

In the example I've changed GetAllCustomers from being fetched into an EntityCollection into being fetched into an EntityCollection<CustomerEntity>

This fails. The problem is obvious though: the XML send over the wire is not deserializable to a type, as WCF doesn't understand the type: it has to be defined as a known type, OR WCF has to be given control over creating the type. As you want to use the same type on both sides from the generated code, namely EntityCollection<T>, you can't let WCF handle this and you have to define the type as a ServiceKnownType in the service specification.

If I add [ServiceKnownType(typeof(EntityCollection<CustomersEntity>))] to the INorthwindService interface definition in NorthwindService.cs, I can send an EntityCollection<CustomerEntity> over the wire: I changed CustomerServices.GetAllCustomers as: /// <summary> /// Gets all customer entities /// </summary> /// <returns>entity collection with all customers</returns> public IEntityCollection2 GetAllCustomers() { EntityCollection<CustomersEntity> toReturn = new EntityCollection<CustomersEntity>( new CustomersEntityFactory()); using (DataAccessAdapter adapter = new DataAccessAdapter()) { // simply fetch all customer entities into the collection. adapter.FetchEntityCollection(toReturn, null); } return toReturn; }

and in the client I of course changed the CustomersSelector's _allCustomers collection to be an EntityCollection<CustomersEntity> and also changed the cast in the ctor at line 44.

When I then start the service, and the client it works.

So I think if you want to use linq on the server you have two options: 1) execute the query and then copy over the data into an EntityCollection nongeneric variant. This is less performant of course 2) define for each EntityCollection<T> usage a ServiceKnownType, so for every entity you return a collection in the service, you define a ServiceKnownType attribute on the service interface.

Frans Bouma | Lead developer LLBLGen Pro
Gabbo
User
Posts: 56
Joined: 12-Jun-2006
# Posted on: 17-Mar-2009 19:03:08   

First, the bottom line is that you fixed the problem I was having (well, more specifically a gap in my newbie knowledge of WCF Service Contracts). Thank you, thank you, thank you!

Now...

Otis wrote:

First of all, please keep the terminology correct otherwise things get confusing wink . If you talk about 'remoting' I presume you don't mean .NET Remoting, but you mean sending the objects over the wire using WCF. Is that correct? Because .NET remoting and WCF are two completely different things which have nothing in common other than that they're techniques to talk to services wink

Yes, you are correct. Forgive my lazy terminology...there's no excuse. :-)

Otis wrote:

If I add [ServiceKnownType(typeof(EntityCollection<CustomersEntity>))]

This was the missing link for me. I only had the non-generic EntityCollection as a ServiceKnownType, not the generic version. Once I added the proper generic EntityCollection type, the example worked perfectly.

Otis wrote:

So I think if you want to use linq on the server you have two options: 1) execute the query and then copy over the data into an EntityCollection nongeneric variant. This is less performant of course 2) define for each EntityCollection<T> usage a ServiceKnownType, so for every entity you return a collection in the service, you define a ServiceKnownType attribute on the service interface.

My implementation works with option 2. So the Data and Business/Manager layers both use generic collections. Any client that accesses the Business/Manager layer directly will also work with the generic collections.

The Service facade implements an interface returning the non-generic collections as a return type of IEntityCollection2. These will be remote clients and the non-generic collections are cast back to the generic version as shown in Walaa's WCF example.

Exactly what I was looking for. Thank you!!!