我有简单的模型:
public class Post
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { set; get; }
//non-important properties stripped, to focus on problem
public virtual Resource Resource { set; get; }
public virtual ICollection<Tag> Tags { set; get; }
}
public class Resource
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { set; get; }
[Url]
public string Url { set; get; }
}
和DbContext(我在这个项目中使用ASP.NET标识,如果这是相关的):
public class DbContext : IdentityDbContext<User>
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var postEntity = modelBuilder.Entity<Post>();
postEntity.Reference(p => p.Resource).InverseCollection(); //no navigation property on Resource side
postEntity.Collection(p => p.Tags).InverseReference(tag => tag.Post);
postEntity.ToTable("Posts");
var resourceEntity = modelBuilder.Entity<Resource>();
resourceEntity.ToTable("Resources");
var tagEntity = modelBuilder.Entity<Tag>();
tagEntity.Reference(t => t.Post).InverseCollection(p => p.Tags).Required(false);
tagEntity.ToTable("Tags");
}
}
迁移(SQL Server)后,数据库表看起来像预期的 - Post表具有ResourceId的外键。
当我附上post.Resource(已经创建的资源)时,创建Post的工作正常。
当我想替换post.Resource时,我的问题就出现了 。 通过替换,我的意思是选择一个已经存在的资源并将其分配给post。
var resource2 = Database.Resources.First(r=>r.Url == "xyz");
我努力了:
post.Resource = resource2; Database.Entry(post).State = EntityState.Modified;
Database.Entry(post).Property(p => p.Resource).CurrentValue = resource2;
post.Resource = null;
它们的组合也不同 ,但它们都不起作用。在调用await SaveChangesAsync();
并查找数据库 - 没有变化。如何正确执行替换(更改外键)?
//更新14.09.2015 问题是由附加选择引起的,执行更新一对多关系。完整代码:
var Database new DbContext();
var resource2 = Database.Resources.First(r=>r.Url == "xyz");
var oldAssignedTags = Database.Posts.Include(p=>p.Tags).FirstOrDefault(p => p.Id == post.Id).Tags;
var tags = newTags as List<Tag> ?? newTags.ToList();
//TagComparer is irrelevant here
var toRemove = oldAssignedTags.Except(tags, TagComparer);
var toAdd = tags.Except(oldAssignedTags, TagComparer);
foreach (var tag in toRemove)
{
Database.Entry(tag).State = EntityState.Deleted; //Database.Tags.Remove(tag);
}
foreach (var tag in toAdd)
{
tag.Post = post;
post.Tags.Add(tag);
}
post.Resource = resource2;
await Database.SaveChangesAsync();
我认为这可能与Eager Loading有关 ,但无论有没有AspNet.Identity
,我都无法重现您的问题。运行以下代码会导致资源始终更新。使用EntityFramework 7.0.0-beta7
。
码
static void Main(string[] args)
{
Init();
WithEagerLoading();
CleanUp();
Init();
WithoutEagerLoading();
CleanUp();
}
private static void WithoutEagerLoading()
{
var db = new MyContext();
var post = db.Posts.First(); // no eager loading of child
post.Resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
db.SaveChanges();
Console.WriteLine($"2nd Resource.Id: {new MyContext().Posts.Include(p => p.Resource).First().Resource.Id}");
}
private static void WithEagerLoading()
{
var db = new MyContext();
var post = db.Posts
.Include(p => p.Resource) // eager loading
.First();
post.Resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
db.SaveChanges();
Console.WriteLine($"2nd Resource.Id: {new MyContext().Posts.Include(p => p.Resource).First().Resource.Id}");
}
private static void CleanUp()
{
var db = new MyContext();
db.Posts.RemoveRange(db.Posts);
db.Resources.RemoveRange(db.Resources);
db.SaveChanges();
}
private static void Init()
{
var db = new MyContext();
var resource = new Resource { Url = "http://atqu.in" };
var resource2 = new Resource { Url = "http://trend.atqu.in" };
var post = new Post { Resource = resource };
db.Add(post);
db.Add(resource);
db.Add(resource2);
db.SaveChanges();
db = new MyContext();
post = db.Posts.Include(p => p.Resource).First();
resource = db.Resources.First(p => p.Url == "http://trend.atqu.in");
Console.WriteLine($"1st Resource.Id: {post.Resource.Id}");
}
结果
1st Resource.Id: 0f4d222b-4184-4a4e-01d1-08d2bc9cea9b
2nd Resource.Id: 00ccae9c-f0da-43e6-01d2-08d2bc9cea9b
1st Resource.Id: 749f08f0-2426-4043-01d3-08d2bc9cea9b
2nd Resource.Id: 2e83b512-e8bd-4583-01d4-08d2bc9cea9b
编辑16/9
编辑问题代码中的问题是因为您在检索post
实例化Database
。 post
没有附加到该Database
实例,因此当您将Resource
附加到它并调用SaveChangesAsync
它什么都不做,因为那时的post
与您要保存的Database
有关。这就是为什么你再次选择post
(在实例化之后)的解决方法导致它被修复 - 因为然后附加了post
的实例。如果您不想再次选择post
,则应使用相同的DbContext
实例来完成DbContext
用于最初检索post
。