Update of navigation property in EF7 does not get saved

删除回忆录丶 提交于 2019-12-23 18:01:06

问题


I have simple model:

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; }
}

and DbContext (I use ASP.NET identity in this project, if this is relevant):

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

After migration (SQL Server), database tables looks like expected - Post table has Foreign Key to ResourceId.

Creating Post's works fine, when I attach post.Resource (already created Resource).

My problem occurs when I want to replace post.Resource. By replace, I mean selecting one of already existing Resources and assigning it to post.

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

I have tried:

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

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

  3. post.Resource = null;

With different combinations of them also, but none of them works. After calling await SaveChangesAsync(); and looking up in database - there are no changes. How to perform the replace (update of foreign key) properly?

//Update 14.09.2015 Issue was caused by additional select, performed to update One-To-Many relationship. Full code:

        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:


I thought this may have something to do with Eager Loading, however I can't reproduce your issue with or without AspNet.Identity. Running the below code results in the Resource always being updated. Using EntityFramework 7.0.0-beta7.

Code

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

Result

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

Edit 16/9

The problem in the edited question's code is because you are instantiating Database after you have retrieved post. post is not attached to that instance of Database, so when you attach the Resource to it and call SaveChangesAsync it does nothing, because post at that time has noting to do with the Database you are saving against. That is why your workaround of selecting post post again (after the instantiation) causes it to be fixed - because then the instance of post is attached. If you don't want to select post again, you should use the same DbContext instance to do the work above that you used to originally retrieve post.



来源:https://stackoverflow.com/questions/32543448/update-of-navigation-property-in-ef7-does-not-get-saved

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!