views:

53

answers:

4

I have a .Net application split in client and server sides, and the server provides REST services (using WCF). I have services definitions like these:

[WebGet(UriTemplate = "/Customers/{id}")]
Customer GetCustomerById(string id);

[WebGet(UriTemplate = "/Customers")]
List<Customer> GetAllCustomers();

The Customer class and its friends are mapped to a database using Fluent NHibernate, with Lazy Loading. If I return from the service outside the Session-scope the service call will fail as it can't serialize the referenced lazy loaded Orders property (see class def at the end). The problem is that I need this to be lazy loaded as I don't want my GetAllCustomers-service to fetch all the referenced Orders. So what I want to do is to notify the serializer somehow such that it doesn't attempt to serialize Orders on GetAll. But please note that the same property must be serialized on GetCustomerById - so I must specify this on the service. Can this be done?!

Classes:

public class Customer
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Order> Orders { get; set; }
}

public class Order
{
    public virtual int Id { get; set; }
    // ++ 
}
A: 

Lazy loading works only inside the scope of a hibernate session. Not a chance doing this over the wire. You need to eagerly fetch the collection or use OOP inheritance and return from your web method a class which doesn't have this collection at all especially if the consumers won't need it.

Darin Dimitrov
Yes, I'm aware that lazy loading only works within the session scope. You're saying that the only solution is to have 2 different classes - one with and one without the Orders? Back when I used SOAP for the transfer it worked just fine to transfer objects with Lazy loaded references, but obviously I couldn't use the uninitialized objects on the client side. This is what I need for rest too. Can it be done? I don't understand what's causing the difference.. Does rest use a different serializer on default?
stiank81
wcf uses a different serializer by default - the fact that this is rest makes no odds.
cvista
you can revert to the old skool serializer by decorating the class and req props with [XmlSerializerFormat]
cvista
+2  A: 

If you are using WCF's default serialization - which I think you do - you would explicitly mark the properties you want to send across the wire and leave the rest. This is done using [DataMember] which I assume you do:

[DataContract]
public class Customer
{
    [DataMember]
    public virtual int Id { get; set; }

    [DataMember]
    public virtual string Name { get; set; }

    // not decorate 
    public virtual IList<Order> Orders { get; set; }
} 

UPDATE OK, you need to send sometimes and not send other times. Obviously you can have CustomerBase class (with no orders) and then Customer (Orders not decorated) and CustomerWithOrders (decorated Orders). Send each from each of your operations.

If that does not suit you, have a look at custom serialization using DataContractSerializerOperationBehavior and IDataContractSurrogate here:

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.idatacontractsurrogate.aspx

http://msdn.microsoft.com/en-us/library/system.servicemodel.description.datacontractserializeroperationbehavior_members.aspx

Aliostad
Yes, sure - you are absolutely right. But my problem is that this does not depend on the class, but on the service. Orders should not be serialized when using GetAllCustomers, but they should be when using the GetCustomerById. So I'm looking for a way to specify this on the service - or tell the serializer to not attempt to load lazy-loaded objects. I'm sorry if this was unclear - I updated the question to make it more clear.
stiank81
@stiank81: Perhaps you should return differing DTOs in that case? You could apply your serialization rules to the DTOs and serialize those instead of your entity. You could have them implement a common interface if you need to treat them uniformly.
DanP
Please note my update.
Aliostad
Thx for your effort. Will look into your suggestions. +1.
stiank81
+2  A: 

Have a different DTO for different scenarios and then use Automapper to transfer your nhib objects to the DTOs - this way you have a sprcialised object graph for each case and there is no way you can then be itterating and thus hydrating the collections you dont want to.

cvista
This makes sense, and might be the best solution. Will consider if there is an easier way, or if I should go with this. Thx.
stiank81
+1  A: 

I'd either return a different data-contract for the GetAllCustomers service. e.g.,

[DataContract]
public class CustomerSummary
{
   // Have properties that represent the summary of the customer
}

WebGet(UriTemplate = "/Customers/{id}")]    
Customer GetCustomerById(string id);    

[WebGet(UriTemplate = "/Customers")]    
List<CustomerSummary> GetAllCustomers();

or, use EmitDefaultValue when applying the DataMember attribute to the Orders property:

[DataContract]       
public class Customer       
{       
   [DataMember]       
   public virtual int Id { get; set; }       

   [DataMember]
   public virtual string Name { get; set; }       

   [DataMember(IsRequired = false, EmitDefaultValue = false)]
   public virtual IList<Order> Orders { get; set; }       
}

and then just leave the Orders property as null when GetAllCustomers returns it's data.

Tim Roberts
Thx for valuable suggestions. First one is definitly a good option, but will consider if I can use the second too.
stiank81