Disabled lazy-loading and eager-loading entity references not working as expected
I've been working with WCF RIA Services and Silverlight and have had some success in exposing a service that serves data taken from an ADO.NET Entity Data Model modeled from an existing SQL Server 2008 Express database. The database defines many relationships between tables that I'm hoping to be able to use client-side for databinding.
Everything was progressing smoothly until I tried executing the following service method:
public IQueryable<Timeline> GetHighlights() {
var self = from x in Database.Timelines
where User.Id == x.UserId || User.Id == x.SenderId
select x;
var friends = from x in Database.FriendItems
where User.Id == x.UserId
from y in Database.Timelines
where x.FriendId == y.UserId || x.FriendId == y.SenderId
select y;
return self.Concat(friends).OrderByDescending(s => s.Id);
}
Note: 'User' is an internal property of the class that selects the currently authenticated user and Database merely wraps the ObjectContext property (for ease).
The 'Timeline' entity contains 2 navigation properties 'User' and 'Sender' which are associated with the 'SilverfishUser' entity. When I iterate over the results from the 'self' query I see that the previously mentioned properties have been filled with the current user (which is correct). However when I iterate the results from the 'friends' query, both the properties are null (before being serialized to the client).
I have tried setting:
this.ContextOptions.LazyLoadingEnabled = false;
//and
this.ContextOptions.ProxyCreationEnabled = false;
And I have also tried eager-loading the references using the Include query method (with lazy-loading both enabled AND disabled) to no avail.
The only way I have successfully filled the User and Sender properties of the Timeline entity was using the following statement:
friends.ForEach(s => {
if (!s.UserReference.IsLoaded) s.UserReference.Load();
if (!s.SenderReference.IsLoaded) s.SenderReference.Load();
});
From what I understand, the 'Load' operation results in a seperate query being executed on the database. As you can see, this presents a potentially inefficient situation when a user has many friends with many timeline posts. The exact situation I'm trying to avoid by disabling lazy-loading. I want to return to the client a fully loaded entity that can be bound to in the minimum amount of queries as possible.
I have already overcome one problem where relative properties were not serialized to the client by applying the [Include] attribute on the metadata property definitions generated by the Domain Service Wizard. This issue seems to be a little more complicated, the solutions I have tried have been widely stated by others and should solve my problem in theory, but they do not. Again, the only way I have been able to successfully fill the entity is to explicitly load the references using the generated EntityReference<> property created for the associated property.
Any help, experience, or information on this issue would be greatly appreciated.
[EDIT] An update on some of my research, when I perfo开发者_如何转开发rm a query like this:
var friends = Database.FriendItems
.Include("Friend.Timeline")
.Where(s => User.Id == s.UserId);
And access the navigation properties ("friends.First().Friend.Timeline.First().User") the value is not null. It's only when I select the timelines into a new collection by adding something like:
.SelectMany(s => s.Friend.Timeline);
That the navigation properties no longer have any value. Now this is only a guess but I can only assume it projects the property values into a new object instance, so it doesn't re-fill these properties in an attempt avoid cyclic references? Anyway, this is one heck of a pickle to solve. Hopefully there's somebody out there who knows more about this than I do.
Well I have managed to find a bit of a workaround, albeit not a very elegant one. I'll post it as an answer so people can have a look at it, but I'm going to leave the question open and credit a more appropriate solution, if possible.
Here's my fix:
public IQueryable<Timeline> GetHighlights() {
var self = from x in Database.Timelines
where User.Id == x.UserId || User.Id == x.SenderId
select x;
var friends = from x in Database.FriendItems.Include("Friend.Timeline")
where (User.Id == x.UserId)
select x.Friend.Timeline;
List<Timeline> highlights = new List<Timeline>();
highlights.AddRange(self);
friends.ForEach(x => x.ForEach(y => highlights.Add(y)));
return highlights.AsQueryable().OrderByDescending(s => s.Id);
}
I think the reason this works is by creating the new collection manually, I prevent the entities from being projected into new objects, thus preserving the loaded relational properties. Again, this is merely speculation but the assumption follows a pretty good pattern lol.
精彩评论