由 linq 查询引起的 StackOverflowException
-
04-07-2019 - |
题
编辑#2: 问题解决了一半。往下看
作为后续问题,有谁知道一种非侵入式的方法来解决我下面尝试做的事情(即,将对象相互链接而不触发无限循环)?
我尝试创建一个 asp.net-mvc Web 应用程序,并收到 StackOverFlowException。控制器触发以下命令:
public ActionResult ShowCountry(int id)
{
Country country = _gameService.GetCountry(id);
return View(country);
}
GameService 像这样处理它(WithCountryId 是一个扩展):
public Country GetCountry(int id)
{
return _gameRepository.GetCountries().WithCountryId(id).SingleOrDefault();
}
GameRepository 是这样处理的:
public IQueryable<Country> GetCountries()
{
var countries = from c in _db.Countries
select new Country
{
Id = c.Id,
Name = c.Name,
ShortDescription = c.ShortDescription,
FlagImage = c.FlagImage,
Game = GetGames().Where(g => g.Id == c.GameId).SingleOrDefault(),
SubRegion = GetSubRegions().Where(sr => sr.Id == c.SubRegionId).SingleOrDefault(),
};
return countries;
}
GetGames() 方法导致 StackOverflowException:
public IQueryable<Game> GetGames()
{
var games = from g in _db.Games
select new Game
{
Id = g.Id,
Name = g.Name
};
return games;
}
我的业务对象与 linq2sql 类不同,这就是我用 select new 填充它们的原因。
mscorlib.dll 中发生“System.StackOverflowException”类型的未处理异常
编辑#1: 我找到了罪魁祸首,它是以下方法,它触发 GetCountries() 方法,而该方法又再次触发 GetSubRegions() ,令人恶心:
public IQueryable<SubRegion> GetSubRegions()
{
return from sr in _db.SubRegions
select new SubRegion
{
Id = sr.Id,
Name = sr.Name,
ShortDescription = sr.ShortDescription,
Game = GetGames().Where(g => g.Id == sr.GameId).SingleOrDefault(),
Region = GetRegions().Where(r => r.Id == sr.RegionId).SingleOrDefault(),
Countries = new LazyList<Country>(GetCountries().Where(c => c.SubRegion.Id == sr.Id))
};
}
这里可能需要考虑一些其他的事情:)这就是当你因为喝了太多咖啡而以面向对象的思维方式思考时会发生的情况
解决方案
问题可能是:国家有次区域,次区域有国家。我不知道你是如何实现惰性列表的,但是可能会继续调用GetCountries,然后调用GetSubRegions等等。为了找到它,我将在GetCountries和GetSubRegions方法头上启动调试器设置断点。
我尝试使用LinqToSql的类似模式,但很难在不影响性能的情况下进行双向导航。这就是我现在正在使用NHibernate的原因之一。
其他提示
海!我认为你的模型是无意中递归调用一个方法,这会导致堆栈溢出。例如,您的Subregion对象正在尝试获取Country对象,而这些对象又必须获取Subregions。
无论如何,在StackOverflow异常中检查堆栈总是有帮助的。如果你看到一次又一次地访问一个属性,很可能是因为你正在做这样的事情:
公共对象MyProperty {set {MyProperty = value; }}
更容易发现像你这样的情况,方法A调用方法B调用方法A,因为你可以看到在调用堆栈中出现两次或多次相同的方法。
要回答您编辑的问题,即:“将对象相互链接而不触发无限循环”:
假设你们有某种关系,双方都需要了解对方......掌握两侧的所有相关实体,然后将它们链接在一起,而不是试图使一侧的获取自动获取另一侧。或者只是让 一 一边取另一个,然后修复剩下的一个。所以在你的情况下,选项是:
选项1:
- 获取所有国家/地区(将次区域留空)
- 获取所有次区域(将国家/地区留空)
- 对于每个次区域,查看国家/地区列表并将次区域添加到国家/地区并将国家/地区添加到次区域
选项2:
- 获取所有国家/地区(将次区域留空)
- 获取所有子区域,通过上面获取的国家/地区列表设置 Subregion.Countries
- 对于每个次区域,遍历其所有国家并将其添加到该国家
(或颠倒国家和次区域)
它们基本上是相同的答案,只是当您进行一些链接时它会发生变化。