處理EntityFramework Core中的重複鍵衝突

.net-core c# entity-framework entity-framework-core

根據其他問題( 此處此處 ),可以通過捕獲拋出的異常並檢查它的InnerException來捕獲Entity Framework 6中的唯一鍵衝突。

當使用重複的數據集調用DbContext.SaveChanges()時,會拋出異常,但它是一個相當標準的InvalidOperationException ,並且它的InnerExceptionnull

如何在Entity Framework Core中檢測重複的密鑰違規?

用更多上下文更新 (雙關語)

我試圖捕獲/檢測的特定違規是在兩個實體(團隊和用戶)之間添加由多對多關係連接的鏈接。

System.InvalidOperationException:無法跟踪實體類型“TeamUser”的實例,因為已經跟踪了具有相同鍵的此類型的另一個實例。添加新實體時,對於大多數鍵類型,如果未設置任何鍵,則將創建唯一的臨時鍵值(即,如果為鍵屬性指定了其類型的默認值)。如果要為新實體顯式設置鍵值,請確保它們不會與現有實體或為其他新實體生成的臨時值發生衝突。附加現有實體時,請確保只有一個具有給定鍵值的實體實例附加到上下文。

用戶實體類:

public class User
{
    [Key]
    public string Name { get; set; }

    public ICollection<TeamUser> TeamUsers { get; set; }
}

團隊實體類:

public class Team
{
    [Key]
    public string Id { get; set; }

    [Required]
    public string Name { get; set; }

    public ICollection<Template> Templates { get; set; }
    public ICollection<Checklist> Checklists { get; set; }

    public ICollection<TeamUser> TeamUsers { get; set; }
}

TeamUser實體類:

public class TeamUser
{
    public string TeamId { get; set; }
    public Team Team { get; set; }

    public string UserName { get; set; }
    public User User { get; set; }
}

我的DbContext子類配置團隊和用戶之間的多對多關係:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var teamUserEntity = modelBuilder.Entity<TeamUser>();

    teamUserEntity
        .HasKey(tu => new { tu.TeamId, tu.UserName });

    teamUserEntity
        .HasOne(tu => tu.Team)
        .WithMany(t => t.TeamUsers)
            .HasForeignKey(tu => tu.TeamId);

    teamUserEntity
        .HasOne(tu => tu.User)
        .WithMany(u => u.TeamUsers)
        .HasForeignKey(tu => tu.UserName);
}

EF Core生成了TeamUser表,如下所示:

CREATE TABLE "TeamUser" (
    "TeamId" TEXT NOT NULL,
    "UserName" TEXT NOT NULL,
    CONSTRAINT "PK_TeamUser" PRIMARY KEY ("TeamId", "UserName"),
    CONSTRAINT "FK_TeamUser_Teams_TeamId" FOREIGN KEY ("TeamId") REFERENCES "Teams" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_TeamUser_Users_UserName" FOREIGN KEY ("UserName") REFERENCES "Users" ("Name") ON DELETE CASCADE
);
CREATE INDEX "IX_TeamUser_UserName" ON "TeamUser" ("UserName");

熱門答案

您無法檢測重複鍵衝突的原因是因為您使用單個dbcontext實例來保存重複數據。讓我用一個示例控制器解釋:

MyController.cs

public class MyController : Controller
{
    private readonly MyDbContext _context;

    public MyController(MyDbContext context)
    {
        _context = context;
    }

    public IActionResult AddFirst()
    {
        var user = new User
        {
            Name = "Alice"
        };
        _context.Users.Add(user);

        var team = new Team
        {
            Id = "uniqueteamid",
            Name = "A Team"
        };
        _context.Teams.Add(team);

        var teamuser1 = new TeamUser()
        {
            User = user,
            Team = team
        };
        _context.TeamUsers.Add(teamuser1);

        _context.SaveChanges();

        return View();
    }

    public IActionResult AddSecond()
    {
        var teamuser2 = new TeamUser()
        {
            UserName = "Alice",
            TeamId = "uniqueteamid"
        };
        _context.TeamUsers.Add(teamuser2);

        _context.SaveChanges();

        return View();
    }

    public IActionResult AddFirstAndSecond()
    {
        var user = new User
        {
            Name = "Bob"
        };
        _context.Users.Add(user);

        var team = new Team
        {
            Id = "anotherteamid",
            Name = "B Team"
        };
        _context.Teams.Add(team);

        var teamuser1 = new TeamUser()
        {
            User = user,
            Team = team
        };
        _context.TeamUsers.Add(teamuser1);

        var teamuser2 = new TeamUser()
        {
            User = user,
            Team = team
        };
        _context.TeamUsers.Add(teamuser2);

        _context.SaveChanges();

        return View();
    }

    public IActionResult AddFirstAndSecondAgain()
    {
        var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1;Trusted_Connection=True;MultipleActiveResultSets=true");

        using (var context = new MyDbContext(optionsBuilder.Options))
        {
            var user = new User
            {
                Name = "Cat"
            };
            context.Users.Add(user);
            context.SaveChanges();
        }

        using (var context = new MyDbContext(optionsBuilder.Options))
        {
            var team = new Team
            {
                Id = "andanotherteamid",
                Name = "C Team"
            };
            context.Teams.Add(team);
            context.SaveChanges();
        }

        using (var context = new MyDbContext(optionsBuilder.Options))
        {
            var teamuser1 = new TeamUser()
            {
                UserName = "Cat",
                TeamId = "andanotherteamid"
            };
            context.TeamUsers.Add(teamuser1);
            context.SaveChanges();
        }

        using (var context = new MyDbContext(optionsBuilder.Options))
        {
            var teamuser2 = new TeamUser()
            {
                UserName = "Cat",
                TeamId = "andanotherteamid"
            };
            context.TeamUsers.Add(teamuser2);
            context.SaveChanges();
        }

        return View();
    }
}

在此控制器中,有4種操作方法: AddFirstAddSecondAddFirstAndSecondAddFirstAndSecondAgain

案例1( AddFirstAddSecond ):

假設首先調用AddFirst 。這將創建一個新用戶,一個新團隊和一個TeamUser。現在,如果之後調用AddSecond ,則會嘗試添加重複的TeamUser並拋出重複的密鑰違例異常。原因是插入重複TeamUser的第二次調用使用的是dbcontext的不同實例,而不是第一次調用插入TeamUser。

案例2( AddFirstAndSecond ):

假設您調用AddFirstAndSecond 。這將拋出無效的操作異常。 為什麼?因為您使用單個dbcontext實例來添加第一個TeamUser和第二個重複的TeamUser。實體框架核心已經跟踪了第一個TeamUser,因此它無法跟踪第二個重複的TeamUser。

案例3( AddFirstAndSecondAgain ):

如果您確實需要在單個操作方法中添加重複的TeamUser,則在添加每個TeamUser時需要使用不同的dbcontext實例。看看AddFirstAndSecondAgain動作方法。這也會引發重複的密鑰違例異常,因為您使用不同的dbcontext實例來添加第一個和第二個重複的TeamUser。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow