Serializing EF4.1 Entities using JSON.Net
Asked Answered
P

2

6

I am building an application using MVC3, Razor view engine, Repository Pattern with Unit of Work and using EF4.1 Code First to define my data model.

Here is a bit of background (gloss over it if you want).

The application itself is just an Intranet 'Menu'.

The 2 main entities are MenuItem and Department of which:

  • MenuItem can have many Departments
  • Departments can have many MenuItems
  • MenuItem may have a MenuItem as a parent

This is how I have defined my Entities

public class MenuItem
{
   public int MenuItemId { get; set; }
   public string Name { get; set; }
   public string Url { get; set; }
   public virtual ICollection<Department> Departments { get; set; }
   public int? ParentId { get; set; }
   public virtual MenuItem ParentMenuItem { get; set; }
}

public class Department
{
   public int DepartmentId { get; set; }
   public string Name { get; set; }
   public virtual ICollection<MenuItem> MenuItems { get; set; }
}

I am using the FluentAPI to define the Self Reference Many-to-Many for the MenuItem.

The issue I am having is passing a MenuItem to the view via JSON. The central issues are that I have a circular reference between my entities that the built in JSON parser can't deal with and I have lazy loading and proxy generation still enabled.

I am using JSON.net library from Nuget as my JSON Serializer as this seems to be a nice way round the circular reference issue. I now am unsure how to 'fix' the proxy generation issue. Currently the serializer throws The RelationshipManager object could not be serialized. This type of object cannot be serialized when the RelationshipManager belongs to an entity object that does not implement IEntityWithRelationships.

Can anyone help me with this? If I turn off proxy generation, I am going to have a hell of a time loading all of the MenuItem children so I am keen leave this on. I have read a fair amount and there seems to be a variety of different answers including projecting the entities into another object and serialize that, etc, etc. Ideally there would be some way of configuring JSON.net to ignore the RelationshipManager object?

Update

Here is what I have used as a Custom ContractResolver for JSON.Net serializer. This seems to have sorted out my issue.

public class ContractResolver : DefaultContractResolver
{
    private static readonly IEnumerable<Type> Types = GetEntityTypes();
    private static IEnumerable<Type> GetEntityTypes()
    {
        var assembly = Assembly.GetAssembly(typeof (IEntity));
        var types = assembly.GetTypes().Where(t => String.Equals(t.Namespace, "Namespace", StringComparison.Ordinal));
        return types;
    }

    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        if (!AllowType(objectType))
            return new List<MemberInfo>();

        var members = base.GetSerializableMembers(objectType);
        members.RemoveAll(memberInfo => (IsMemberEntityWrapper(memberInfo)));
        return members;
    }

    private static bool AllowType(Type objectType)
    {
        return Types.Contains(objectType) || Types.Contains(objectType.BaseType);
    }

    private static bool IsMemberEntityWrapper(MemberInfo memberInfo)
    {
        return memberInfo.Name == "_entityWrapper";
    }
}

IEntity is an interface all my Code First entity objects implement.

Paraformaldehyde answered 9/8, 2011 at 5:7 Comment(0)
T
0

Well, you used powerful serialization API which serializes references and all members as well and now you complains that it serializes all members :)

I didn't test it but I believe this will bring you close to the solution.

JSON.NET is quite powerful tool and it should offer you the extensibility point to avoid this behavior but you will have to code it yourselves. You will need custom DataContractResolver where you define which members should be serialized. Here is the similar example for NHibernate.

You can implement some logic which will take only members present in the parent class of dynamic proxy. I hope this will not break lazy loading. To validate that current entity is proxy you can use this code to get all known proxy types:

IEnumerable<Type> types = ((IObjectContextAdapter)dbContext).ObjectContext.GetKnownProxyTypes();
Touchmenot answered 9/8, 2011 at 8:51 Comment(1)
Thanks Ladislav. From your referenced blog post I was able to write a custom ContractResolver to specify which members, and more specifically which object types as it turns out, to serialize. In some cases, a framework object was being passed through to be serialized so I had to check the object type. I will update my question with the code I have used. Feel free to comment on it :)Paraformaldehyde
A
2

I realise this question has an accepted answer, but I thought I would post my EF Code First solution for future viewers. I was able to get around the error message with the contract resolver below:

 class ContractResolver : DefaultContractResolver
 {
      protected override List<System.Reflection.MemberInfo> GetSerializableMembers(Type objectType)
      {
           if (objectType.Namespace.StartsWith("System.Data.Entity.Dynamic"))
           {
                return base.GetSerializableMembers(objectType.BaseType);
           }

           return base.GetSerializableMembers(objectType);
      }
 }

This works because EF Code First classes inherit from the POCO class that you actually want serialized, so if we can identify when we are looking at an EF generated class (by checking the namespace) we are able to just serialize using the properties from the base class, and therefore only serialize the POCO properties that we were really after in the first place.

Asphalt answered 10/3, 2012 at 19:44 Comment(2)
I found that you need to test objectType != null && objectType.Namespace != nullParada
@TonyOHagan absolutely - you should always null-check parameters. I left them out here for brevityAsphalt
T
0

Well, you used powerful serialization API which serializes references and all members as well and now you complains that it serializes all members :)

I didn't test it but I believe this will bring you close to the solution.

JSON.NET is quite powerful tool and it should offer you the extensibility point to avoid this behavior but you will have to code it yourselves. You will need custom DataContractResolver where you define which members should be serialized. Here is the similar example for NHibernate.

You can implement some logic which will take only members present in the parent class of dynamic proxy. I hope this will not break lazy loading. To validate that current entity is proxy you can use this code to get all known proxy types:

IEnumerable<Type> types = ((IObjectContextAdapter)dbContext).ObjectContext.GetKnownProxyTypes();
Touchmenot answered 9/8, 2011 at 8:51 Comment(1)
Thanks Ladislav. From your referenced blog post I was able to write a custom ContractResolver to specify which members, and more specifically which object types as it turns out, to serialize. In some cases, a framework object was being passed through to be serialized so I had to check the object type. I will update my question with the code I have used. Feel free to comment on it :)Paraformaldehyde

© 2022 - 2024 — McMap. All rights reserved.