根据其他问题(here和here),可以通过捕获抛出的异常并检查它的InnerException
来捕获实体框架6中的唯一密钥违规。
使用重复的数据集调用DbContext.SaveChanges()
时,会抛出异常,但它是相当标准的InvalidOperationException
,而InnerException
是null
。
如何在实体框架核心中检测重复的密钥违规?
使用更多上下文进行更新(双关语)
我试图捕获/检测的特定违规是在两个实体(团队和用户)之间添加由多对多关系连接的链接。
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");
答案 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种操作方法:AddFirst
,AddSecond
,AddFirstAndSecond
和AddFirstAndSecondAgain
。
案例1(AddFirst
和AddSecond
):
假设首先调用AddFirst
。这将创建一个新用户,一个新团队和一个TeamUser。现在,如果之后调用AddSecond
,则会尝试添加重复的TeamUser并抛出重复的密钥违例异常。原因是插入重复TeamUser的第二次调用使用的是dbcontext的不同实例,而不是第一次调用插入TeamUser。
案例2(AddFirstAndSecond
):
假设您致电AddFirstAndSecond
。这将抛出无效的操作异常。 为什么?因为您使用单个dbcontext实例来添加第一个TeamUser和第二个重复的TeamUser。实体框架核心已经跟踪了第一个TeamUser,因此它无法跟踪第二个重复的TeamUser。
案例3(AddFirstAndSecondAgain
):
如果您确实需要在单个操作方法中添加重复的TeamUser,则在添加每个TeamUser时需要使用不同的dbcontext实例。看一下AddFirstAndSecondAgain
动作方法。这也会引发重复密钥违例异常,因为您使用不同的dbcontext实例来添加第一个和第二个重复的TeamUser。