我遇到EF(6.1.3)
的问题我创建了下一个类(具有多对多关系):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
方法:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
如果我使用两个上下文,则会复制记录(添加到创建的标记中)。如果我删除标记的行 - 问题就会消失。
有没有办法在不使用相同的上下文的情况下修复此问题?
答案 0 :(得分:0)
如果可以,可以更好地重新加载实体或根本不分离它们。在应用程序中使用多个上下文实例总体上使事情变得更加复杂。
您遇到的问题来自实体框架实体更改跟踪器。当您从DbContext加载权限并处置该上下文时,实体将与实体更改跟踪器分离,并且实体框架不知道对其进行的任何更改。
在您通过附加实体引用分离实体后,它(分离实体)会立即进入实体更改跟踪器,并且它不知道之前已加载此实体。为了让实体框架知道这个分离的实体来自数据库,你必须重新附加它:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
这样您就可以使用记录在其他对象中引用,但如果您对这些记录进行了任何更改,那么所有这些更改都将消失。要对数据库进行更改,您必须将状态更改为已添加:
dbContext.Entry(record).State = EntityState.Modified;
实体框架使用您的映射来确定数据库中要应用更改的行,特别是使用您的主键设置。
几个例子:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
这是一个小实体结构,以便您可以看到它是如何工作的。你可以看到Bird和Tree有简单的Key - Id
。 BirdOnATree是鸟对树对的多对多表,附加列SittingSessionStartedAt
。
以下是多个上下文的代码:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
在这种情况下,鸟从DB上脱落而没有再次附着。实体框架会将此实体视为一个新实体,即使Id属性设置为指向现有行到数据库,它也不会存在于DB中。要更改此项,您只需将此行添加到开头的第二个DbContext:
context.Entry(bird).State = EntityState.Unchanged;
如果执行此代码,它将不会在DB中创建新的Bird实体,而是使用现有代码。
第二个例子:我们不是从数据库中获取鸟类,而是由我们自己创建:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
执行时,此代码也不会创建另一个鸟类实体,将在BirdOnATree表中引用Id = 1的鸟类,并且不会更新Id = 1的鸟类实体。事实上,您可以在此处放置任何数据,只需使用正确的ID。
如果我们在此更改代码以使此分离实体更新DB中的现有行:
context.Entry(bird).State = EntityState.Modified;
这样,正确的数据将被插入到表BirdOnATree
中,但是在表Bird中也会更新Id = 1的行,以适合您在应用程序中提供的数据。
您可以查看有关对象状态跟踪的文章:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
总的来说,如果可以避免这种情况,请不要使用对象状态跟踪和相关代码。它可能会出现难以找到源的不需要的更改 - 当您不期望它们时,字段会更新为实体,或者在您预期时不会更新。