【问题标题】:How to handle a 2-to-1 relation in MVC?如何处理 MVC 中的 2 对 1 关系?
【发布时间】:2017-03-19 22:54:45
【问题描述】:

因此,在我的模型中,我有一个 Player 类和一个 Game 类,Game 有 2 个必需的 Player,如下所示:(以及我未在此处包含的其他一些字段)

public class Game
{
    public Player Player1 { get; set; }

    [Required]
    public int Player1Id { get; set; }

    public Player Player2 { get; set; }

    [Required]
    public int Player2Id { get; set; }
}

尝试将此模型迁移到数据库时,我收到以下错误:

在表“Games”上引入 FOREIGN KEY 约束“FK_dbo.Games_dbo.Players_Player2Id”可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

this stackoverflow question 的答案很好地解释了我为什么会收到此错误。)

为了避免这个错误,我添加了以下流畅的 API 代码:

modelBuilder.Entity<Game>()
        .HasRequired(c => c.Player1)
        .WithMany()
        .HasForeignKey(c => c.Player1Id)
        .WillCascadeOnDelete(false);

modelBuilder.Entity<Game>()
       .HasRequired(c => c.Player2)
       .WithMany()
       .HasForeignKey(c => c.Player2Id)
       .WillCascadeOnDelete(false);

然而,我现在遇到的问题是,当我尝试删除一个玩家时,其 ID 在一个或多个游戏中被引用。

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

如果我正确理解了这个错误,我得到这个错误是因为游戏失败的外键约束之一(级联删除是假的,所以游戏不会与玩家一起被删除。而是 Player1Id(或Player2Id) 设置为 null,这打破了约束,因为两者都是必需且不可为 null)

所以我尝试在 Seed() 方法中创建类似 this SO question 的触发器。

context.Database.ExecuteSqlCommand("CREATE TRIGGER Game_Player_Del on dbo.Players instead of delete as " +
            "set nocount on " +
            "delete from dbo.Games where Player1Id in (select Id from deleted) or " +
            "Player2Id in (Select Id from deleted) " + 
            "delete from dbo.Players where Id in (select Id from deleted)");

当我检查我的数据库时,确实添加了触发器,但它似乎不起作用,因为我仍然收到与添加触发器之前相同的错误。

那么,我应该怎么做才能使这项工作按预期进行? (删除玩家也会删除关联的游戏,删除游戏不会删除玩家)

编辑:我刚刚想到的一个可能的解决方案是在我的控制器中编辑删除方法,首先获取 Player1Id 或 Player2Id 等于 Player.Id 的所有游戏,删除这些然后删除播放器,但是(我认为)删除玩家时我不应该担心游戏,实体框架应该为我做这件事。

编辑2:(回应usr的评论)这是我的删除代码:

控制器:

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    repo.deletePlayer(id);
    return RedirectToAction("Index");
}

回购:

public void deletePlayer(int id)
{
    using (var context = new TennisContext())
    {
        player = context.Players.Find(id);
        context.Entry(player).State = EntityState.Deleted;
        context.SaveChanges();
    }
}

编辑 3:我设法通过在删除播放器之前查询和删除受影响的游戏来使其工作。

回购代码:

public void deletePlayer(int id)
{
    using (var context = new TennisContext())
    {
        player = context.Players.Find(id);
        List<Game> playerGames = context.Games.Where(g => g.Player1Id == id || g.Player2Id == id).ToList();
        foreach (Game g in playerGames)
        {
            context.Entry(g).State = EntityState.Deleted;
        }
        context.Entry(player).State = EntityState.Deleted;
        context.SaveChanges();
    }
}

【问题讨论】:

  • 触发器不是正确的解决方案。这可以在 EF 级别解决。 (我不知道如何。)发布您的删除代码。
  • @usr 我添加了我的删除代码
  • 有一个 Player 属性,它是 Player 对象的集合。验证该集合至少有两个。不确定是否有 ValidationAtttibute 可以为您完成,但您可以轻松创建一个。
  • 使外键可以为空。

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


【解决方案1】:

使用实体框架代码手动删除游戏。查询受影响的游戏并将它们传递给适当的删除方法(例如 Entry(player).State = EntityState.Deleted 事物或任何其他等效项)。

由于您不能使用自动级联手动进行。

【讨论】:

  • 我编辑了我的删除方法来查询游戏并在删除播放器之前删除它们。现在一切都按预期工作。谢谢!
【解决方案2】:

您的两个玩家 ID 都具有 [必需] 属性。如果您尝试删除一个播放器会发生什么?也许 Id 之一的玩家应该可以为空?同意触发的评论 - 不是你的解决方案。

【讨论】:

  • 删除任一玩家会在我的问题中间给出错误(“操作失败:关系.....”),删除游戏没有问题。
猜你喜欢
  • 1970-01-01
  • 2020-03-30
  • 1970-01-01
  • 2011-08-25
  • 2014-04-03
  • 1970-01-01
  • 2020-02-12
  • 1970-01-01
  • 2020-10-09
相关资源
最近更新 更多