views:

488

answers:

3

Forgive me if this has been asked before; I couldn't find anything close after a few searches:

I'm trying to write an ActionFilter in MVC that will "intercept" an IQueryable and nullify all the parent-child relationships at runtime. I'm doing this because Linq does not serialize objects properly if they have parent-child relationships (it throws a circular reference error because the parent refers to the child, which refers back to the parent and so on), and I need the object serialized to Json for an Ajax call. I have tried marking the child relationship in the DBML file with a privacy status of internal, and while this fixes the serialization problem, it also hides the child members from the view engine when the page renders, causing another error to be thrown. So, by fixing one problem, I cause another.

The only thing that fixes both problems is to manually set the child members to null just before returning the serialization, but I'm trying to avoid doing that because it's cumbersome, not reusable, etc. I'd rather use an ActionFilter to inspect the IQueryable that is being serialized and nullify any members with a Type of EntitySet (how Foreign Keys/Associations are represented). However, I don't have much experience with Reflection and can't find any examples that illustrate how to do something like this. So... is this possible with Reflection? Is there a better way to accomplish the same thing? I'll post the relevant code tomorrow when I'm back at my work computer.

Thanks,

Daniel


As promised, the code:

[GridAction]  
public ActionResult _GetGrid()  
{  
  IQueryable<Form> result = formRepository.GetAll();  
  foreach (Form f in result)  
    {
      f.LineItems = null;
      f.Notes = null;
    }

  return View(new GridModel<Form> { Data = result });
}

An added wrinkle is that I'm using the new Telerik MVC Extensions, so I'm not actually serializing the Json myself -- I'm just returning the IQueryable in an IGridModel, and the action filter [GridAction] does the rest.

A: 

There are two options, one is to ignore those properties during serialization using [XmlIgnore]. The other one is to nullify the properties using reflection.

Ignore in serialization, simple usage sample that shows how to use default value in serialization:

[Serializable]
public class MyClass
{
    [XmlIgnore]
    public int IgnoredVal { get; set; }

    public int Val { get; set; }
}

public void UsageSample()
{
    var xmlSerializer = new XmlSerializer(typeof(MyClass));
    var memoryStream = new MemoryStream();
    var toSerialize = new MyClass { IgnoredVal = 1, Val = 2 };
    xmlSerializer.Serialize(memoryStream, toSerialize);
    memoryStream.Position = 0;
    var deserialize = (MyClass)xmlSerializer.Deserialize(memoryStream);

    Assert.AreEqual(0, deserialize.IgnoredVal);
    Assert.AreEqual(2, deserialize.Val);
}

Nullify with reflection, code sample:

public void NullifyEntitySetProperties(object obj)
{
    var entitySetProperties = obj.GetType().GetProperties()
        .Where(property => property.PropertyType == typeof(EntitySet));

    foreach (var property in entitySetProperties)
    {
        property.SetValue(obj, null, null);
    }
}

In my opinion, if the first option can be done used in your code it's better. This option is more direct and economic.

Elisha
+1  A: 

So, just in case anyone's curious, here's how I finally solved this problem: I modified Damien Guard's T4 template to include the attribute [ScriptIgnore] above entities of type Association. This lets the JSON serializer know to not bother serializing these, thus preventing the circular reference problem I was getting. The generated code ends up looking like this:

private EntitySet<LineItem> _LineItems;
 [ScriptIgnore]
 [Association(Name=@"Form_LineItem", Storage=@"_LineItems", ThisKey=@"Id", OtherKey=@"FormId")]
 public EntitySet<LineItem> LineItems
 {
  get {
   return _LineItems;
  }
  set {
   _LineItems.Assign(value);
  }
 }

This fixes the serialization problem I was having without disabling the use of child tables through LINQ. The grid action on the controller ends up looking like this:

[GridAction]
public ActionResult _GetGrid()
{
  return View(new GridModel<Form> { Data = formRepository.GetAll() });
}
Daniel
A: 

Hi, Daniel

I'm having same problem as you.

But After adding [ScriptIgnore] I'm not able to use that association within the grid (telerik grid), it has disappeared. How did you solve this problem?

Thanks.

Azho KG
Could you post your code? I'm having trouble understanding the problem.Thanks,Daniel
Daniel