本文介绍了EF7中导航属性的更新未保存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的模型:

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身份):

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

迁移(SQL Server)后,数据库表看起来像预期的一样-邮寄表具有指向ResourceId的外键.

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

当我附加post.Resource(已经创建的Resource)时,创建Post的工作正常.

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

我想替换post.Resource 时出现问题.通过替换,我的意思是选择一个现有资源并将其分配给发布.

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

我尝试过:

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

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

post.Resource = null;

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

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?

//更新14.09.2015该问题是由其他选择引起的,用于更新一对多关系.完整代码:

//Update 14.09.2015Issue 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();

推荐答案

我认为这可能与急切加载有关,但是无论是否有,我都无法重现您的问题AspNet.Identity .运行以下代码将导致资源始终被更新.使用 EntityFramework 7.0.0-beta7 .

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.

代码

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 的实例.如果您不想再次选择 post ,则应使用相同的 DbContext 实例来完成上面原始检索 post .

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.

这篇关于EF7中导航属性的更新未保存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-27 10:25