帮助重构实体框架4查询

时间:2010-12-08 00:09:36

标签: linq entity-framework entity-framework-4 linq-to-entities eager-loading

我有两个实体:

  • 位置
  • 发表

位置发布之间的 1 .. *。

位置是抽象的,我有很多派生实体,例如城市。我为我的模型使用Table-Per-Type继承。

我正在尝试编写以下查询:(简化)

  • 获得前20名城市,并包括“评分最高的职位”(最高评级)。

因此,位置有一个名为帖子的导航属性。

要记住的事情 - 我禁用延迟加载,因此我必须急切加载或执行两个查询

所以,这是我目前的查询。请注意,我需要从此方法返回ICollection<Location>

public ICollection<Location> FindTopTwentyLocations()
{
    var results = new List<Location>();

    var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList();

    foreach (var city in cities)
    {
       var aggregatedCity = city.Location;
       aggregatedCity.Posts = new List<Post> { aggregatedCity.TopPost };
       results.Add(city);
    }

    return results;
}

所以基本上,我抓住了前20个城市,投射到一个匿名类型,所以我可以抓住最高职位,然后循环通过匿名类型的集合,将帖子推回到“城市 “对象,以便添加到List<Location>的返回类型。

  • 我无法使用.Include,因为这将返回所有帖子
  • 我不想执行2次查询
  • 我必须使用匿名类型投影,否则会抛出EF错误(无法翻译查询)

考虑到这些要点,我们有更好的方法吗?我对var cities查询感到相当满意,但我并不喜欢将匿名类型属性循环/复制到我的模型实体。

有什么想法吗?

修改

我也注意到对匿名类型的投射正在失去我正在检索的急切加载的位置关联。

E.g

var query = locationRepository.Find().OfType<City>().Include("State").ToList();

工作 - 返回所有“州”协会。

但:

var query = locationRepository.Find().OfType<City>().Include("State").Select(x => new {
   Location = x,
   TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
}).ToList();

所有“州”协会的结果均为空。

奇妙!

2 个答案:

答案 0 :(得分:0)

很高兴保持这种开放一段时间并看到其他答案,但我现在坚持我所拥有的。

关于上面的编辑,我想提一点。

将急切加载的查询投影到匿名类型似乎会丢失包含的关联。我不明白为什么。

我使用的解决方法是在匿名类型中包含急切加载的关联:

var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       State = x.State, // include association in anon type
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList();

    foreach (var city in cities)
    {
       var aggregatedCity = city.Location;
       aggregatedCity.State = city.State; // copy anon type association over
       aggregatedCity.Posts = new List<Post> { aggregatedCity.TopPost };
       results.Add(city);
    }

这似乎有效。

在重构代码方面,我认为var cities查询不能被优化。我尝试将foreach从左到右的复制转移到另一个.Select投影中(例如,在查询生效后 - .ToList()之后),但为了做到这一点,我会必须从左到右复制所有属性,例如:

var cities = locationRepository
                   .Find()
                   .OfType<City>()
                   .Select(x => new
                    {
                       Location = x,
                       State = x.State, // include association in anon type
                       TopPost = x.Posts.OrderByDescending(r => x.Rating).FirstOrDefault()
                    }).Take(20).ToList().Select(x => new City
                    {
                       CityName = Location.Name,
                       State = State,
                       // etc etc
                    });

考虑到我的实体上有20多个属性,我不想这样做。让我回到Linq-To-Sql和POCO的痛苦世界。

正如我所说 - 接受其他建议。

修改

我最终在这里使用了存储过程。我的代码有效,但它有100多行。我宁愿将那些100多行抽象到存储过程中。

此外,这个结果基本上是只读的(我不需要图中的实体 - 我只是得到结果,显示它们并完成它)。

答案 1 :(得分:0)

我可能会遗漏一些东西,但你能不能做这样的事情吗?

var cities = locationRepository 
                   .Find() 
                   .OfType<City>() 
                   .Select(x => 
                    { 
                       x.Posts = x.Posts.OrderByDescending(r => x.Rating).Take(1);
                       return x;
                    }).Take(20).ToList(); 

或者如果EF抱怨改变Posts属性,那么创建一个新的Location对象&amp;在选择操作中添加帖子。