Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop WCF Deserializing Empty ICollection into Zero Capacity Array

I'm having a problem using WCF and Entity Framework 4.1 POCO objects (generated using T4 templates). My basic problem is that when sending a POCO object from my client to the service, WCF is deserializing a member variable of type ICollection as a fixed size array.

On the client side I can tell visual studio to use IList instead of T[] - but I cant see any option like this on the server end.

This causes no end of problems with several things, such as persisting these objects back to the database.

Is there any way to tell WCF what object type to deserialize ICollection (or any array) as?

like image 711
Matt Whetton Avatar asked Apr 20 '12 10:04

Matt Whetton


2 Answers

I'm surprised that more folks haven't run into this issue, as it hits you smack in the face when you try to use the EF T4-generated POCO objects over WCF. Specifically, the error I was getting said something like:

Exception: "Unable to set field/property Orders on entity type Datalayer.Customers. See InnerException for details."

InnerException: "An item cannot be added to a fixed size Array of type 'Datalayer.Order[]'."

At any rate, the only solution I've been able to come up with is the one that you mention, namely, modifying the T4 templates to use HashSet instead of ICollection. Doesn't strike me as the cleanest, but it seems to work.

like image 162
Ken Smith Avatar answered Nov 20 '22 05:11

Ken Smith


I'm using Entity Framework 6, and I was able to solve this issue by making the following changes in my T4 template.

I changed the following line where it creates the navigation properties to use a List instead of a collection from

navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,

to

navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("List<" + endType + ">") : endType,

Then I changed the code that sets the navigation property in the constructor to convert the default hashset to a list by adding a call to .ToList(). This line

this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();

was changed to

this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>().ToList();

The HashSet<>.ToList() method is an extension, so in order to make that extension method available, I added a using System.Linq statement by modifying the UsingDirectives method:

    public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
    return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
        ? string.Format(
            CultureInfo.InvariantCulture,
            "{0}using System;{1}" + Environment.NewLine +
            "{0}using System.Linq;" + 
            "{2}",
            inHeader ? Environment.NewLine : "",
            includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
            inHeader ? "" : Environment.NewLine,
            Environment.NewLine)
        : "";
}
like image 1
Kevin Peter Avatar answered Nov 20 '22 06:11

Kevin Peter