efcore: Replacing a property with owned type doesn't work if there is owned types nesting in v2.1 RC1

I’m trying to use replacing properties with owned types since it was promised to fix in 2.1. This feature seems to be working ok with 1 level of owned types nesting. However, if you have 2 nested owned types, EF discards changes on the entity property. Its better to provide an example:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace TestEfCtorInjectionWithOwnedTypes
{
    public class MyEntity
    {
        public int Id { get; set; }
        public OwnedEntity Owned { get; set; }
        private MyEntity() { }
        public MyEntity(OwnedEntity owned) => Owned = owned;
    }

    public class OwnedEntity
    {
        public OwnedOwnedEntity OwnedOwnedProperty { get; private set; }
        private OwnedEntity() { }
        public OwnedEntity(OwnedOwnedEntity ownedOwnedProperty)
            => OwnedOwnedProperty = ownedOwnedProperty;
    }

    public class OwnedOwnedEntity
    {
        public double MyProperty { get; private set; }
        private OwnedOwnedEntity() { }
        public OwnedOwnedEntity(double myProperty) => MyProperty = myProperty;
    }

    class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions options) : base(options) { }

        public DbSet<MyEntity> MyEntities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>(cb =>
            {
                cb.OwnsOne(seg => seg.Owned, b =>
                {
                    b.OwnsOne(e => e.OwnedOwnedProperty);
                });
            });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();

            services.AddDbContext<MyDbContext>(options =>
                options.UseInMemoryDatabase("MyDatabase"));

            using (var sp = services.BuildServiceProvider())
            {
                int entityId;

                using (var scope = sp.CreateScope())
                {
                    var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();

                    var entity = new MyEntity(new OwnedEntity(new OwnedOwnedEntity(4242)));

                    dbContext.MyEntities.Add(entity);

                    dbContext.SaveChanges();

                    entityId = entity.Id;
                }

                using (var scope = sp.CreateScope())
                {
                    var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();

                    var entity = dbContext.MyEntities.Find(entityId);

                    System.Console.WriteLine("MyProperty before changing: " +
                        entity.Owned.OwnedOwnedProperty.MyProperty);

                    entity.Owned = new OwnedEntity(new OwnedOwnedEntity(4848));

                    System.Console.WriteLine("MyProperty before saving changes: " + 
                        entity.Owned.OwnedOwnedProperty.MyProperty);

                    dbContext.SaveChanges();

                    System.Console.WriteLine("MyProperty after saving changes: " +
                        entity.Owned.OwnedOwnedProperty.MyProperty);
                }
            }
        }
    }
}


The output I’m getting:

MyProperty before changing: 4242
MyProperty before saving changes: 4848
MyProperty after saving changes: 4242

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 7
  • Comments: 24 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@virzak That’s by design, you need to traverse navigations by calling entityEntry.Reference("OwnedReference").EntityEntry

@Alantoo Does it fix the nested own types?

I solved using a quick workaround ( We have only one entity with own type so this approach is simpler for us, without using reflexion)

Example for detached context:

context.Entry(entityExisting).Reference(p => p.OwnTypeA).TargetEntry.CurrentValues.SetValues(entityUpdate.OwnTypeA);
context.Entry(entityExisting).Reference(p => p.OwnTypeB).TargetEntry.CurrentValues.SetValues(entityUpdate.OwnTypeB);
context.Entry(entityExisting.OwnTypeB).Reference(p => p.OwnTypeB_SubOwnType).TargetEntry.CurrentValues.SetValues(entityUpdate.OwnTypeB.OwnTypeB_SubOwnType);
context.Entry(entityExisting).CurrentValues.SetValues(entityUpdate);

context.Entry(entityExisting).Reference(p => p.OwnTypeA).TargetEntry.State = EntityState.Modified;
context.Entry(entityExisting).Reference(p => p.OwnTypeB).TargetEntry.State = EntityState.Modified;
context.Entry(entityExisting.OwnTypeB).Reference(p => p.OwnTypeB_SubOwnType).TargetEntry.State = EntityState.Modified;
context.Entry(entityExisting).State = EntityState.Modified;

I`m waiting for a live so I can remove redundant code

@vertonghenb The fix will be shipped in 3.0 preview 4

None of the workarounds work when using UserManager<TUser> instead of DbContext.

Does someone have this use case, and has a small snippet of working code to share?

e.g.

// User is entity // User.Token is owned type // User.Token.Hash is owned-owned type user.Token = someNewToken; await userManager.UpdateAsync(user); // user.Token and user.Token.Hash are not updated I posted by use case to SO here.

Try something like that:

// `User` is entity
// `User.Token` is owned type
// `User.Token.Hash` is owned-owned type
user.Token = someNewToken;

var hash = user.Token.Hash
context.ChangeTracker.DetectChanges();
context.Entry(user).Reference(x => x.Token).TargetEntry.Reference(x => x.Hash).CurrentValue = hash;
context.Entry(user).State = EntityState.Modified;
await context.SaveChangesAsync();

I have this problem as well, although @smariussorin solution did not work for me but @AndriySvyryd solution works perfectly. Do we know when this problem will be resolved so I can remove my temporary work around. Thanks.

I can confirm that @smariussorin workaround works. =))

Tested with a owned type with 5 nested owned types