正确使用MVC EF中的高级属性
本文关键字:高级 属性 EF MVC | 更新日期: 2023-09-27 18:07:53
假设我有一个足球模型Team
。它和其他队伍在一个小组里玩Matches
。现在我想从名单中选出前两名。得分照常计算:赢3分,平1分,输0分。
Match
的模型如下:
[Key]
public int MatchId{get;set;}
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[ForeignKey("HomeTeamId")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
public int? HomeTeamScored { get; set; }
public int? AwayTeamScored { get; set; }
我已经测试了这5个解决方案:
1)有一个视图而不是表来获取分数列,但它使编程部分复杂化,因为我将不得不以某种方式告诉EF使用表进行插入,但视图用于显示数据
2)未映射Score
列,然后取所有队伍,按如下方式计算分数:
var list = db.Teams.ToList();
foreach(var team in list)
{
team.Score = db.Matches.Where(...).Sum();
}
则按Score
排序,取前2。
3)另一种方法是
var list = db.Teams.OrderByDesc(t => db.Matches.Where(...).Sum()).Take(2).ToList();
我将不得不做很多null检查,也检查哪支球队赢了还是平局,我正在寻找的球队是主场还是客场,等等。
4) 另一种选择是每次我添加/编辑比赛时都重述Score
,但我觉得这是一种非常不专业的方法。
正如我所说的,这些方法中的每一个都是解决问题的方法,但是……我有一种第六感,我错过了一些很明显的东西,比如我如何用最少的努力做到这一点。谁能告诉我我错过了什么?
注:如果它影响了答案,让我们假设我使用的是最新版本。
当数据冗余迫近时,通常规范化是解决方案。我认为在你的情况下,你需要两者兼而有之,规范和冗余。
Match
中的重复属性为"臭"。他们似乎在呼吁正常化。仔细观察就会发现,并非所有人都是如此。一场比赛总是由两支球队组成。所以两个TeamId
是OK的(以及随附的参考文献)。但是你可以用不同的方式存储分数。
看看这个可能的模型:
class Team
{
public int TeamId { get; set; }
// ...
public ICollection<MatchTeam> MatchTeams { get; set; }
}
class Match
{
public int MatchId { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
class MatchTeam
{
public int MatchId { get; set; }
public int TeamId { get; set; }
public int Scored { get; set; } // Number of goals/points whatever
public int RankingScore { get; set; } // 0, 1, or 3
}
MatchTeam
是存储一支队伍在一场比赛中的成就的实体。Scored
属性是HomeTeamScored
和AwayTeamScored
的归一化结果。优点是:该属性不可为空:当结果为事实时,将创建MatchTeam
条目。
冗余在RankingScore
属性中。这必须在输入或修改比赛时确定,这取决于(并且应该与)分数一致。与冗余一样,存在数据不一致的危险。但这是一个很大的危险吗?如果只有一种服务方法可以输入或修改MatchTeam
数据,则危险被充分限制。
优点是现在可以在运行时收集每个团队的总得分:
var topTeams = context.Teams
.OrderByDescending(t => t.MatchTeams.Sum(mt => mt.RankingScore))
.Take(2);
1)我不明白为什么实现视图会使编程变得复杂。这是一个很好的解决方案。插入比赛结果和获取顶级球队是两个完全独立的操作。请让我看一些代码来理解为什么它们之间有这样的强耦合,比如比赛分数和球队的总分。
2)这是一个糟糕的选择:您需要为所有团队创建一个查询,并为每个团队创建一个额外的查询。糟糕的性能!
3)这将是很好的看到你的代码,以显示如何可以提高您的查询。例如,进行空检查并不是那么糟糕。它就像使用??
运算符一样简单,即mt => mt.HomeTeamSocred ?? 0
将非常容易地将null转换为0。如果你展示了所使用的表达式,就有可能看到它是否可以改进和简化。但是,我可以推荐这个,它并不那么复杂:
ctx.Match.Select(m => new
{ // Score for HomeTeam
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 3 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 0 : 1,
TeamId = m.HomeTeamId,
})
.Concat(
ctx.Match.Select(m => new
{ // Score for away Team
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 0 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 3 : 1,
TeamId = m.AwayTeamId,
})
).GroupBy(mr => mr.TeamId) // Group match scores by TeamId's
.Select(mrs=> new
{
TeamId = mrs.Key,
TotalScore = mrs.Sum(m => m.TeamScore)
})
.OrderByDescending(ts => ts.TotalScore)
.Take(2);
然而,有些事情我不明白。为什么Home/AwayTeamScored
可以为空?因为在比赛开始之前,两队的Score
必须为零。这个空没有任何意义。这将避免检查空值的麻烦。
4)这个选项是什么意思?