7

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);
    }
}
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • 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. Jul 05 '12 at 17:55
  • @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 Jul 05 '12 at 17:58
  • 1
    Seeing the structure of the class that's giving you problems may shed some light. – Eric J. Jul 05 '12 at 17:58
  • @EricJ. - Here is the circular reference. – Travis J Jul 05 '12 at 18:02
  • To be clear... you are using the JavaScriptSerializer? – Eric J. Jul 05 '12 at 18:12
  • 1
    The accepted answer solves the serialization problem, but [here](http://stackoverflow.com/a/25774651/861716) is the answer to your original question. – Gert Arnold Sep 11 '14 at 12:10

3 Answers3

4

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.

daveharnett
  • 340
  • 2
  • 12
  • Thank you for your response @daveharnett. However, what I ended up doing was writing my own custom version of JavaScriptSerializer. – Travis J Dec 20 '12 at 20:33
3

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

Community
  • 1
  • 1
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • "While with this the ScriptIgnore or JsonIgnore attributes are finally honoured, this seriously cripples Entity Framework and forces you to do more plumbing than you normally would - and in doing so goes against the principles of EF IMHO." From the workaround. I appreciate the link and have seen suggestions to disable this feature, but it means explicitly including fields in every query I run against the database and I will just have to find a different way because of that. – Travis J Jul 05 '12 at 18:19
  • Yes but the workaround I think was written before Microsoft further responded to the ticket. MSFT has in fact offered better solutions than indicated in the workaround. If you change `ScriptIgnore` to `NonSerialized` and have a recent version of JSON.Net, I believe this will work today. – Eric J. Jul 05 '12 at 18:53
  • NonSerialized doesn't work for virtual, sad face. However, huge happy face, triggering the workaround on a situational basis does seem to work. I am doing this when a flag is sent: `context.Configuration.ProxyCreationEnabled = false; context.Configuration.LazyLoadingEnabled = false;` I am marking this as the correct answer because it points to this workaround. – Travis J Jul 05 '12 at 18:56
3

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 :)

jim tollan
  • 22,305
  • 4
  • 49
  • 63
  • Can you expand on Automapper a little? Perhaps with a simple example? Also to note, changing the context's configuration as noted in the accepted answer will cause the `[ScriptIgnore]` tags to be handled properly. – Travis J Jul 05 '12 at 20:02
  • sure, automapper is 'the' silver bullet for many many problems, i can't overstate this fact. automapper works by allowing you to create a map between the complex intangible object and a simpler more db/serialisation friendly object. i'll update my answer with an example based on stuff i've worked on (usually complex viewModels->poco). gimme 30 mins or so. – jim tollan Jul 05 '12 at 20:05
  • This gives me runtime exception "Missing type map configuration or unsupported mapping. Mapping types: -> " with EF 5 and AutoMapper 2.2. – R. Schreurs Apr 26 '13 at 10:24
  • hello @R. Schreurs. unfortunately, this was tested at the time on EF4 and automapper v2.0. However, i think the issue is that you can't map to a DynamicProxy (at least, i suspect). start basic 1st and map to pocos and concrete types – jim tollan Apr 26 '13 at 13:20
  • Thanks @jimtollan, I understand. I mainly added my comment to warn other readers for a solution that is not future proof. I was puzzled by the fact that Microsoft chose not to make the Dynamic Proxies inherit from the original entity, but decided to skip AutoMapper in this case and copy the fields I need manually... – R. Schreurs Apr 26 '13 at 14:21