禁用延迟加载和急切加载实体引用无法按预期工作

发布于 2024-12-07 15:32:00 字数 2166 浏览 0 评论 0原文

我一直在使用 WCF RIA 服务和 Silverlight,并在公开服务数据的服务方面取得了一些成功,该服务提供从现有 SQL Server 2008 Express 数据库建模的 ADO.NET 实体数据模型中获取的数据。数据库定义了表之间的许多关系,我希望能够使用客户端进行数据绑定。

一切进展顺利,直到我尝试执行以下服务方法:

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);
}

注意:“User”是类的内部属性,用于选择当前经过身份验证的用户,而数据库仅包装 ObjectContext 属性(为了方便起见)。

“Timeline”实体包含 2 个导航属性“User”和“Sender”,它们与“SilverfishUser”实体关联。当我迭代“self”查询的结果时,我发现前面提到的属性已由当前用户填充(这是正确的)。但是,当我迭代“朋友”查询的结果时,这两个属性都为空(在序列化到客户端之前)。

我尝试过设置:

this.ContextOptions.LazyLoadingEnabled = false;
//and
this.ContextOptions.ProxyCreationEnabled = false;

并且我还尝试使用包含查询方法(启用和禁用延迟加载)来急切加载引用,但无济于事。

我成功填充时间轴实体的用户和发件人属性的唯一方法是使用以下语句:

friends.ForEach(s => { 
    if (!s.UserReference.IsLoaded) s.UserReference.Load();
    if (!s.SenderReference.IsLoaded) s.SenderReference.Load();
});

据我了解,“加载”操作会导致在数据库上执行单独的查询。正如您所看到的,当用户有很多朋友并且有很多时间线帖子时,这会带来潜在的低效情况。我试图通过禁用延迟加载来避免这种情况。我想向客户端返回一个完全加载的实体,该实体可以绑定到尽可能少的查询。

我已经通过在域服务向导生成的元数据属性定义上应用 [Include] 属性解决了相关属性未序列化到客户端的问题。这个问题似乎有点复杂,我尝试过的解决方案已经被其他人广泛阐述,理论上应该可以解决我的问题,但他们没有。同样,我能够成功填充实体的唯一方法是使用生成的 EntityReference<> 显式加载引用。为关联属性创建的属性。

任何有关此问题的帮助、经验或信息将不胜感激。

[编辑]我的一些研究的更新,当我执行这样的查询时:

    var friends = Database.FriendItems
                .Include("Friend.Timeline")
                .Where(s => User.Id == s.UserId);

并访问导航属性(“friends.First().Friend.Timeline.First().User”)该值不为空。仅当我通过添加以下内容将时间线选择到新集合中时:

.SelectMany(s => s.Friend.Timeline);

导航属性不再具有任何值。现在这只是一个猜测,但我只能假设它将属性值投影到一个新的对象实例中,因此它不会重新填充这些属性以避免循环引用?无论如何,这是一个很难解决的难题。希望有人比我更了解这一点。

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 perform 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浊酒尽余欢 2024-12-14 15:32:00

好吧,我设法找到了一些解决方法,尽管不是一个非常优雅的方法。我会将其作为答案发布,以便人们可以查看它,但我将保留该问题,并在可能的情况下提供更合适的解决方案。

这是我的修复:

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);
}

认为其工作原理是通过手动创建新集合,我防止实体被投影到新对象中,从而保留加载的关系属性。再说一次,这只是猜测,但假设遵循一个很好的模式,哈哈。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文