views:

1142

answers:

3

I have been trying out both Linq to Sql and EF in my ASP.NET MVC application. After switching to EF I realized my XML/JSON serialization output has extra cruft.

XML:

<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <EntityKey>
    <EntitySetName>Persons</EntitySetName>
    <EntityContainerName>PersonEntities</EntityContainerName>
    <EntityKeyValues>
      <EntityKeyMember>
        <Key>Id</Key>
        <Value xsi:type="xsd:int">1</Value>
      </EntityKeyMember>
    </EntityKeyValues>
  </EntityKey>
  <Id>1</Id>
  <Name>John</Name>
</Test>

JSON:

{"Id":1,"Name":"John","EntityState":2,"EntityKey"{"EntitySetName":"Persons","EntityContainerName":"PersonEntities","EntityKeyValues":[{"Key":"Id","Value":1}],"IsTemporary":false}}

Instead I would just like my output to be:

{"Id":1, "Name":"John"}

My EF query to retrieve the object is:

Tests.First(t => t.Id == testId);
+5  A: 

You can shape the JSON result in your controller like this:

public JsonResult Person(int id)
{
  var person = PersonRepository.FindByID(id);
  var result = new { Id = person.Id, Name = person.Name };
  return Json(result);
}

This will limit the DTO which is serialized to contain only the values you want.

Edit: As a paritial answer to your comment question; you can create a simpler PersonViewModel class (DTO) that you can map the properties to. As John Saunders mentioned in his answer Automapper is a nice way to simplify the copying of the property values out of the EF Person instance:

The modified Action method may look like this:

public JsonResult Person(int id)
{
  var person = PersonRepository.FindByID(id);
  var dto = Mapper.Map<Person, PersonViewModel>(person);
  return Json(dto);
}

The only other option I can think of is to use reflection to modify the DataMemberAttributes on the Person entity to suppress the EntityKey property.

Kris
Kris, I stumbled into this as well after posting the question, however, the issue here is that I have to recompose the Person object. If I add add a new property to the Person object Person.Age, I would have to touch this code again. Is there a more elegant approach?
aleemb
Thanks for the sample, it's a great starting point and though very cool, it would still require maintaining the mapped view. Hopefully with .NET 4.0 and T4 templates things should get easier.
aleemb
+1  A: 

So far, the best technique I've found involves code generation. I did this on one project that was using the Service Factory, but you could do the same "by hand" using T4 Text templates directly.

Also, keep an eye on AutoMapper. It's still new enough that I consider it to be emerging technology, but I'm hoping it will emerge soon!

John Saunders
AutoMapper is an interesting project though neither of these apply directly to me since I want to convert EF objects to POCO.
aleemb
You don't want POCO, per se. You want a DTO, and AutoMapper can do the mapping for it. I wasn't directly recommending Service Factory, though the code generation I did there actually produces the DTO and the translation classes.
John Saunders
I am beginning to uncover bigger issues with EF such as lack of focus on business objects and persistence ignorance (lack of which is manifesting itself in this problem). I found other ways, but may abandon ship and revert to L2S until V2 of EF.
aleemb
+2  A: 

Another approach to work around this is to use the JavascriptSerializer's ScriptIgnore attribute and create a partial class for the object in question, new'ing up the EntityKey, EntityState properties and adding a ScriptIgnore attribute to them:

public partial class Person
{
    [ScriptIgnore]
    public new System.Data.EntityKey EntityKey { get; set; }

    [ScriptIgnore]
    public new System.Data.EntityState EntityState { get; set; }
}

When the Person class is serialized via JavascriptSerializer, it will ignore those properties. However, this would not be practical because EF uses the state info to keep track of objects and this would wreak havoc.

If there were a way to dynamically add properties, it would eliminate the need to override those properties just to add a [ScriptIgnore] attribute. But, since there is no way to dynamically add attributes, this solution may not be that useful.

aleemb