La mise à jour de la propriété de navigation dans EF7 n'est pas enregistrée

asp.net-core entity-framework-core

Question

J'ai un modèle simple:

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

et DbContext (j'utilise l'identité ASP.NET dans ce projet, le cas échéant):

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

Après la migration (SQL Server), les tables de base de données ressemblent à ce qui était prévu - la table Post a une clé étrangère vers ResourceId

La création de publications fonctionne bien lorsque j'attache une ressource post.Resource (ressource déjà créée).

Mon problème survient lorsque je souhaite remplacer post.Resource . Par remplacer, je veux dire sélectionner l’une des ressources déjà existantes et l’affecter à l’affichage.

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

J'ai essayé:

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

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

  3. post.Resource = null;

Avec différentes combinaisons d'entre eux aussi , mais aucun d'entre eux ne fonctionne. Après avoir appelé, await SaveChangesAsync(); et en regardant dans la base de données - il n'y a pas de changements. Comment effectuer le remplacement (mise à jour de la clé étrangère) correctement?

// Mise à jour 14.09.2015 Le problème a été causé par une sélection supplémentaire effectuée pour mettre à jour la relation One-To-Many. Code complet:

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

Réponse acceptée

Je pensais que cela pouvait avoir un AspNet.Identity avec Eager Loading , mais je ne peux pas reproduire votre problème avec ou sans AspNet.Identity . L'exécution du code ci-dessous a pour résultat que la ressource est toujours mise à jour. Utiliser 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}");
}

Résultat

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

Éditer 16/9

Le problème dans le code de la question modifiée est dû au fait que vous instanciez la Database de Database après avoir récupéré le post . post n'est pas attaché à cette instance de Database de Resource SaveChangesAsync post Database Database , de sorte que lorsque vous connectez la Resource à appeler et SaveChangesAsync il ne fait rien, car post à cette époque a noter à voir avec la Database de Database que vous enregistrez contre. C’est pourquoi votre solution de contournement en sélectionnant post post à nouveau (après l’instanciation) l’a fixée - car l’instance de post est jointe. Si vous ne souhaitez pas sélectionner à nouveau le post , vous devez utiliser la même instance de DbContext pour effectuer le travail ci-dessus que vous avez utilisé pour extraire le post à l'origine.



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow