我们说我有一个模型足球Team
。它与其他团队在一个小组中播放Matches
。现在我想从列表中选择前2名球队。得分按常规计算:胜利为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
,但我觉得这是一种非常不专业的方法。
正如我所说,这些方法中的每一种都是解决方案,可以让我解决任务,但是......我有第六感觉,我错过了一些完全明显的东西,因为我能用最少的努力做到这一点。任何人都可以建议我缺少什么吗?
P.S。如果它影响了答案,那么我们假设我使用的是最新版本的所有内容。
答案 0 :(得分:1)
当数据冗余出现时,通常标准化就是解决方案。我认为在你的情况下你需要一点点,规范化和一些冗余。
Match
中重复的属性是"臭"。他们似乎要求正常化。仔细观察就会发现,并非所有这些都是如此。一场比赛总是由两支球队组成。所以两个TeamId
都可以(以及随附的参考文献)。但你可以用不同的方式存储分数。
看看这个可能的模型:
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
是一个实体,用于在1场比赛中存储1支球队的成绩。 Scored
属性是HomeTeamScored
和AwayTeamScored
的归一化结果。优点是:属性不可为空:当结果是事实时,会创建MatchTeam
条目。
冗余位于RankingScore
属性中。这必须在输入或修改匹配时确定,并且取决于(并且应该与分数一致)。与冗余一样,存在数据不一致的危险。但这是一个很大的危险吗?如果只有一种服务方法可以输入或修改MatchTeam
数据,则危险就足够了。
优点是,现在可以在运行时收集每个团队的总分数:
var topTeams = context.Teams
.OrderByDescending(t => t.MatchTeams.Sum(mt => mt.RankingScore))
.Take(2);
答案 1 :(得分:0)
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
必须为零。那个null没有任何意义。这样可以避免使用trobule来检查空值。
4)此选项意味着什么?