The simple answer to your question is - use .Include()
.
As an example: imagine that you have a Customer
object which has an associated ReferredBy
object, which references another customer. In your application, you want to return a list of Customer
s as well as the associated Customer
who referred each one.
Your WebAPI method might look something like:
[HttpGet]
public IQueryable<Customer> Customers() {
return db.Customers.OrderBy(c => c.LastName).Top(10);
}
When the serializer gets ahold of this, you are liable to run into various errors, which you can read more about in this article. In essence, though, it comes from 2 things:
- ProxyCreation / LazyLoading - which is EF-speak for "load the associated objects for my object graph on-demand, only when I ask for them", and
- Serialization of cycles - which means -
A
refers to B
, and B
refers to A
- so every time I serialize one, I serialize the other again as a child of it. This creates an infinite loop.
I won't get into all of the specifics or the additional problems - I already gave you an article which goes into it in some depth. Instead, here is the way I solve the issue in my applications:
- Use JSON.net as your serializer. You can refer to this link for instructions on how to set it up in Visual Studio as your project's default serializer (assuming it isn't already)
In your Global.asax
or one of the .cs
files which is loaded as part of your configuration, use the following settings:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
This tells JSON.net not to serialize reference loops.
Set the MergeOption
in your DataContext
and each of its Collection
properties to MergeOption.NoTracking
. In my applications, I do this by editing the .tt
file which creates the DataContext
:
First, find the line that creates the constructor, and change to:
MergeOption _defaultMergeOption = MergeOption.AppendOnly;
public <#=code.Escape(container)#>() : this("name=<#=container.Name#>") { }
public <#=code.Escape(container)#>(String connectionString) : base(connectionString) {
<# if (!loader.IsLazyLoadingEnabled(container)) { #>
this.Configuration.LazyLoadingEnabled = false;
<# } #>
this.Configuration.ProxyCreationEnabled = false;
_defaultMergeOption = MergeOption.NoTracking;
}
Find the line that starts with:
<# foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>()) { #>
Now edit the next few lines to say:
<# foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>()) { #>
<#= Accessibility.ForReadOnlyProperty(entitySet)#> ObjectQuery<<#=typeMapper.GetTypeName(entitySet.ElementType)#>> <#=code.Escape(entitySet)#> {
get {
var set = (((IObjectContextAdapter)this).ObjectContext).CreateObjectSet<<#=typeMapper.GetTypeName(entitySet.ElementType)#>>();
set.MergeOption = _defaultMergeOption;
return set;
}
}
<# }
What this does, is give you a constructor for your DataContext
which can default all of the collections to MergeOption.NoTracking
, and automatically disable ProxyCreation
and LazyLoading
. When you create an instance of your DataContext
to pull from, you can now simply say:
var db = new MyDataContext("ConnectionStringGoesHere");
Given the above, your new WebAPI method becomes as simple as:
[HttpGet]
public IQueryable<Customer> Customers() {
return db.Customers.Include("ReferredBy")
.OrderBy(c => c.LastName).Top(10);
}
The .Include()
will load the child-record as part of the initial SQL statement (one hit to the database in total), and the Serializer will ignore the back-references, allowing you to produce JSON similar to:
[{
Id: 1,
FirstName: 'Frank',
LastName: 'Abba',
ReferredBy: {
Id: 4,
FirstName: 'Bob',
LastName: 'Jones',
ReferredBy: null
}
}, {
Id: 4,
FirstName: 'Bob',
LastName: 'Jones',
ReferredBy: null
}
}]