我很感激有关改进此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)
})
};
我正在寻找一些改进此查询的建议。
答案 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