Reputation: 31
In my current project, I am using WebAPI and EntityFramework model-first. After some investigation, it appears that I have to use LazyLoading
in order to load related entities.
I have read in many blogs that using LazyLoading
in a service can cause performance and serialization problems - so I'd like to avoid it if possible.
Any suggestions on how I can achieve this without creating POCO objects?
Upvotes: 3
Views: 449
Reputation: 27256
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:
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:
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
}
}]
Upvotes: 1