正确使用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,但我觉得这是一种非常不专业的方法。

正如我所说的,这些方法中的每一个都是解决问题的方法,但是……我有一种第六感,我错过了一些很明显的东西,比如我如何用最少的努力做到这一点。谁能告诉我我错过了什么?

注:如果它影响了答案,让我们假设我使用的是最新版本。

正确使用MVC EF中的高级属性

当数据冗余迫近时,通常规范化是解决方案。我认为在你的情况下,你需要两者兼而有之,规范和冗余。

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属性是HomeTeamScoredAwayTeamScored的归一化结果。优点是:该属性不可为空:当结果为事实时,将创建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)这个选项是什么意思?