7
votes

I was trying trying to serialize a domain model and ran into an issue where I need to convert a dynamic proxy into a POCO. The issue I ran into was that circular references exist by way of virtual properties in the model. Although I attempted to use [ScriptIgnore] in order to have the serializer not parse those properties, it still does. I believe that this is because the objects are dynamic proxies and there is still some remnants in the properties which cause the parser to enter (which in turn causes a recursion error "circular reference" - I tried limiting the recursion to 3 steps but I got an error of "Recursive steps exceeded").

How can I convert an object from a dynamic proxy to a POCO so that it can be serialized?

Edit: Simple example

public class One : BaseViewModel
{
    public int OneId { get; set; }
    public virtual ICollection<Two> Two { get; set; }
}

public class Two
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
    [ScriptIgnore]
    public virtual One One { get; set; }
}

public abstract class BaseViewModel
{
    public string AsJson()
    {
        var serializer = new JavaScriptSerializer();
        return serializer.Serialize(this);
    }
}
3
Proxies are a subclass of the POCO they represent. Generally, you should be able to serialize them just fine. Can you post a small but complete example of a class you are unable to serialize?Eric J.
@EricJ. - When constructed normally, the class serializes. I can post an example, but I am not sure how much it will help because when ran it will run just fine. The main issue is when the class is instantiated with data from the ObjectContext. This is when there are still references inside of the virtual properties even though they should be empty because they were not all included in the query to the database.Travis J
Seeing the structure of the class that's giving you problems may shed some light.Eric J.
@EricJ. - Here is the circular reference.Travis J
The accepted answer solves the serialization problem, but here is the answer to your original question.Gert Arnold

3 Answers

4
votes

As an alternative to Jim's Automapper solution, I've had some success with the 'Mapping' (shallow copying) the POCO proxy to an instance of the same POCO. The simple way to do this is to modify the template file that generates the POCO to include a ToSerializable() method, so the POCO classes look like this:

public partial class cfgCountry
    {
        public cfgCountry()
        {
            this.People = new HashSet<Person>();
        }

        [Key]
        public int CountryID { get; set; }
        public string Name { get; set; }
        public int Ordinal { get; set; }

        public virtual ICollection<Person> People { get; set; }

        public cfgCountry ToSerializable()
        {
            return new cfgCountry()
            {
            CountryID = this.CountryID,
            Name = this.Name,
            Ordinal = this.Ordinal,
            };
        }
    }

Here's the function I've added to the POCO template (tt) file to create the ToSerializable function (Such an ugly syntax.):

<#+
void WriteToSerializableMethod (CodeGenerationTools code, IEnumerable<EdmProperty> primitiveProperties, EntityType entity)
{

#>
public <#=code.Escape(entity)#> ToSerializable()
{
    return new <#=code.Escape(entity)#>()
    {
<#+
    foreach(var edmProperty in primitiveProperties)
    {
#>
    <#=edmProperty.Name#> = this.<#=edmProperty.Name#>,
<#+
    }
#>
    };
}
<#+
}
#>

It's not perfect, since you need to remember to return foo.ToSerializable() rather than foo itself whenever you expect a result to be serialized, but I hope its useful to somebody.

3
votes

This is a known issue

We fixed an issue in ScriptIgnoreAttribute, which was not being propagated to derived classes. Since POCO proxy types are created by deriving from the POCO class provided by the user, the JavaScriptSerializer wasn't able to see the [ScriptIgnore] attributes you have in your repro.

The fix won't be included in the next preview release of .NET 4.5.

(so presumably you have to wait for a following preview release or the final release)

http://connect.microsoft.com/VisualStudio/feedback/details/723060/ef-4-2-code-first-property-attributes-not-honoured

that is fixed in .NET 4.5

From the comments on that issue, it looks like you can work around by using NonSerializedAttribute instead of ScriptIgnoreAttribute if you are using the current version of JSON.Net

3
votes

Travis, I know you have your accepted answer here, but wanted to pass on a little bit of lateral thinking on this. I was faced with a very similar issue recently and could not get anything to work for me, tried all the [scriptignore] attibutes etc, etc.

What finally worked for me was using Automapper and creating a map from the proxy object to a slimmed down poco object. This solved all my issues within 2 minutes. All this after a 36 hour siege mentallity having prevailed when trying to get the proxy to play ball -shish :-)

Another approach to think about in the interim.

[Edit] - using Automapper (this is a little test app referencing automapper)

ref: http://automapper.codeplex.com/

nuget: Install-Package AutoMapper

Classes:

public sealed class One : BaseViewModel
{
    // init collection in ctor as not using EF in test
    // no requirement in real app
    public One()
    {
        Two = new Collection<Two>();
    }
    public int OneId { get; set; }
    public ICollection<Two> Two { get; set; }
}

public class Two
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
    [ScriptIgnore]
    public virtual One One { get; set; }
}

public abstract class BaseViewModel
{
    public string AsJson()
    {
        var serializer = new JavaScriptSerializer();
        return serializer.Serialize(this);
    }
}

public class OnePoco  : BaseViewModel
{
    public int OneId { get; set; }
    public virtual ICollection<TwoPoco> Two { get; set; }
}

public class TwoPoco
{
    public int TwoId { get; set; }
    public int OneId { get; set; }
}

test controller code:

public ActionResult Index()
{
    // pretend this is your base proxy object
    One oneProxy = new One { OneId = 1 };
    // add a few collection items
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 1, One = oneProxy});
    oneProxy.Two.Add(new Two() { OneId = 1, TwoId = 2, One = oneProxy});

    // create a mapping (this should go in either global.asax 
    // or in an app_start class)
    AutoMapper.Mapper.CreateMap<One, OnePoco>();
    AutoMapper.Mapper.CreateMap<Two, TwoPoco>();

    // do the mapping- bingo, check out the asjson now
    // i.e. oneMapped.AsJson
    var oneMapped = AutoMapper.Mapper.Map<One, OnePoco>(oneProxy);

    return View(oneMapped);
}

give this a try and see how you get on, it certainly worked for me, the 'earth' moved :)