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

时间:2014-06-08 20:05:12

标签: c# asp.net-mvc entity-framework properties

我们说我有一个模型足球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。如果它影响了答案,那么我们假设我使用的是最新版本的所有内容。

2 个答案:

答案 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属性是HomeTeamScoredAwayTeamScored的归一化结果。优点是:属性不可为空:当结果是事实时,会创建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)此选项意味着什么?