如何改进这个LINQ查询

时间:2014-08-05 11:54:20

标签: c# linq linq-to-entities

我很感激有关改进此LINQ查询的任何建议。我正在查询所有州的名单:1。已公布,2。任何已出版的城市和任何已出版的地方。

实体,视图模型和关系:

public class State {
    public long Id { get; set; }
    public bool Published { get; set; }
    public string Name { get; set; }
    public IList<City> Cities { get; set; }
}
public class StateModel {
    public long Id { get; set; }
    public bool Published { get; set; }
    public string Name { get; set; }
    public IEnumerable<CityModel> Cities { get; set; }
}

public class City {
    public long Id { get; set; }
    public bool Published { get; set; }
    public string Name { get; set; }
    public State State { get; set; }
    public IList<Place> Places { get; set; }
}
public class CityModel {
    public long Id { get; set; }
    public bool Published { get; set; }
    public string Name { get; set; }
    public int PlacesCount { get; set; }
}

public class Place {
    public long Id { get; set; }
    public bool Published { get; set; }
    public string Name { get; set; }
    public City City { get; set; }
    public RecordStatus Status { get; set; }
}
// also I have a view-model named PlaceModel just like the Place itself.

// RecordStatus is an enum, nothing special 

我用这个片段查询州:

查看更新请!此代码段已过期。

Func<Place, Boolean> placeSelector = 
    p => p.Published && p.Status != RecordStatus.Banned;

Func<City, Boolean> citySelector = 
    c => c.Published && c.Places.Any(placeSelector);

var linq = from state in _context.States.AsNoTracking()
           where state.Published
           where state.Cities.Any(citySelector)
           select new StateModel {
               Id = state.Id,
               Name = state.Name,
               Cities = state.Cities
                             .Where(citySelector)
                             .Select(city => new CityModel {
                                 Id = city.Id,
                                 Name = city.Name,
                                 PlacesCount = city.Places.Count(placeSelector)
                             })
           };

正如您所见,我'在state.Cities上查询了3次:其中一个Any()的谓词为citySelector,另一个是state.Cities上的投影每个state,谓词和以前一样,最后一个用于计算每个城市的所有地点。我知道我可以在sql-server上执行谓词一次(不像我执行3次的代码片段),但我无法弄清楚如何?有什么建议吗?提前致谢。

更新

有一个错误(我的)我用Func<,>而不是Expression<Func<,>>将A的注意力推到了一个不是我的Q点的点。所以我编辑查询:

var linq = from state in _context.States.AsNoTracking()
           where state.Published
           where state.Cities.Any(c => c.Published && c.Places
                                 .Any(p => p.Published && p.Status != RecordStatus.Banned)
                             )
           select new StateModel {
               Id = state.Id,
               Name = state.Name,
               Cities = state.Cities
                             .Where(c => c.Published && c.Places
                                 .Any(p => p.Published && p.Status != RecordStatus.Banned))
                             .Select(city => new CityModel {
                                 Id = city.Id,
                                 Name = city.Name,
                                 PlacesCount = city.Places
                                                   .Count(p => p.Published && p.Status != RecordStatus.Banned)
                             })
           };

我正在寻找一些改进此查询的建议。

4 个答案:

答案 0 :(得分:2)

我认为你使用Func<X, Boolean>迫使LINQ使用Enumerable overload of e.g. Any,它发生在C#中,而不是Queryable overload,它可以在SQL中发生。

尝试将选择器声明为

Expression<Func<Place, Boolean>> placeSelector = 
    p => p.Published && p.Status != RecordStatus.Banned;

Expression<Func<City, Boolean>> citySelector = 
    c => c.Published && c.Places.Any(placeSelector);

需要using System.Linq.Expressions;

答案 1 :(得分:1)

也许您可以使用let关键字。像这样:

var linq = from state in _context.States.AsNoTracking()
                let cities=state.Cities.Where(citySelector)
                where state.Published
                where cities.Any()
                select new StateModel {
                   Id = state.Id,
                   Name = state.Name,
                   Cities = cities.Select(city => new CityModel {
                                     Id = city.Id,
                                     Name = city.Name,
                                     PlacesCount = city.Places.Count(placeSelector)
                                 })
               };

答案 2 :(得分:1)

Func<T1, T2, ...>是一段编译代码,LINQ-to-Entities / LINQ-to-SQL无法转换回SQL。这样写它会强制SQL运行一个可能很昂贵的查询,然后通过编译的.NET Func<>代码运行它的结果。

Expression<Func<T1, T2, ...>>是未编译的代码,LINQ-to-Entities / LINQ-to-SQL可以转换回SQL代码并在SQL上运行整个查询,而不是在SQL中运行它的一些部分,其他位和.NET中的碎片混合在一起。

作为一般规则,您应该始终将LINQ的谓词声明为Expression<Func<T1, T2, ...>> ,并在其上调用.Compile()以将其转换回Func<T1, T2, ...>只在需要的时候。

答案 3 :(得分:0)

如果你想避免重复的查询,你可以自下而上而不是自上而下你正在做的事情。例如:

var places = _context.Places.Where(x => x.Published && x.Status != RecordStatus.Banned)
                            .GroupBy(x => x.City.Id)
                            .Select(x => new
                             {
                                 CityId = x.Key,
                                 Count = x.Count()
                             })
                             .ToList(); // db query

var cityIds = places.Select(y => y.CityId).ToList(); //in memory

var cityModels = _context.Cities.Where(x => x.Published && cityIds.Contains(x.Id))
                                .Select(x => new CityModel
                                {
                                    Id = x.Id,
                                    Name = x.Name,
                                    Published = x.Published,
                                    StateId = x.State.Id, // added this to your model
                                    PlacesCount = 0
                                })
                                .ToList(); // db query

cityModels.ForEach(x => x.PlacesCount = places.FirstOrDefault(y => y.CityId == x.Id).Count); //in memory

var stateIds = cityModels.Select(x => x.StateId).Distinct().ToList(); //in memory

var stateModels = _context.States.Where(x => x.Published && stateIds.Contains(x.Id))
                                 .Select(x => new StateModel
                                 {
                                     Id = x.Id,
                                     Name = x.Name
                                 }).ToList(); // db query

stateModels.ForEach(x => x.Cities = cityModels.Where(y => y.StateId == x.Id)); //in memory