EF7中的导航属性更新未保存

时间:2015-09-12 20:35:45

标签: asp.net-core entity-framework-core

我有简单的模型:

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 时出现问题。 通过替换,我的意思是选择一个已经存在的资源并将其分配给发布。

var resource2 = Database.Resources.First(r=>r.Url == "xyz");

我试过了:

  1. post.Resource = resource2; Database.Entry(post).State = EntityState.Modified;

  2. Database.Entry(post).Property(p => p.Resource).CurrentValue = resource2;

  3. post.Resource = null;

  4. 它们的不同组合,但它们都不起作用。在调用await SaveChangesAsync();并查找数据库后 - 没有任何更改。如何正确执行替换(更换外键)?

    // 2015年9月14日更新 问题是由额外选择引起的,为更新一对多关系而执行。完整代码:

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

1 个答案:

答案 0 :(得分:1)

我认为这可能与 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

编辑问题代码中的问题是因为您在检索Database后实例化了postpost的{​​{1}}实例未附加Database,因此当您将Resource附加到SaveChangesAsync并致电post时,它无效,因为当时Database已经注意到你正在拯救的post。这就是为什么你再次选择post post(在实例化之后)导致修复它的原因 - 因为然后附加了post的实例。如果您不想再次选择DbContext,则应使用相同的post实例执行上述最初检索{{1}}的工作。