处理EntityFramework Core中的重复键冲突

时间:2017-05-28 16:39:50

标签: c# entity-framework .net-core entity-framework-core

根据其他问题(herehere),可以通过捕获抛出的异常并检查它的InnerException来捕获实体框架6中的唯一密钥违规。

使用重复的数据集调用DbContext.SaveChanges()时,会抛出异常,但它是相当标准的InvalidOperationException,而InnerExceptionnull

如何在实体框架核心中检测重复的密钥违规?

使用更多上下文进行更新(双关语)

我试图捕获/检测的特定违规是在两个实体(团队和用户)之间添加由多对多关系连接的链接。

  

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");

2 个答案:

答案 0 :(得分:3)

为处理此类情况,我编写了一个开放源代码库:EntityFramework.Exceptions它允许您捕获如下类型的异常:

Uri imageUri = new Uri(imageSource);
BitmapImage bitmapImage = new BitmapImage(imageUri);
if (bitmapImage.IsDownloading)
{
   bitmapImage.DownloadCompleted += (s, e) => _autoResetEvent.Set();
   var imageLoadingTimer = new Timer(10000);
   imageLoadingTimer.Elapsed += (s, e) => _autoResetEvent.Set();
   imageLoadingTimer.Start();
   _autoResetEvent.WaitOne();                
}

所有您需要做的就是从Nuget安装它,并使用您的using (var demoContext = new DemoContext()) { demoContext.Products.Add(new Product { Name = "a", Price = 1 }); demoContext.Products.Add(new Product { Name = "a", Price = 1 }); try { demoContext.SaveChanges(); } catch (UniqueConstraintException e) { //Handle exception here } } 方法调用它:

OnConfiguring

答案 1 :(得分:2)

您无法检测到重复密钥冲突的原因是因为您使用单个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。