Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Owned type property not persisting for a modified entity in EF Core

I'm trying to achieve something in EF Core that worked very well for me in EF 6.

I'm serializing the contents of a List<T> property as a Json string in the DB. <T> can be pretty much anything, since Json.Net takes care of serializing whatever we throw at it. My collection exposes a Json string property and takes care of serializing/deserializing itself.

This approach is convenient and efficient for structured nested data where a relational model would bring needless overhead and complexity.

In EF 6 I would do something like this:

[ComplexType]
public class SelfSerializingCollection<T> : Collection<T>
{
    public void AddRange(IEnumerable<T> collection)
    {
        foreach (var item in collection)
        {
            Add(item);
        }
    }

    protected string Json
    {
        get { return JsonConvert.SerializeObject(this); }
        private set
        {
            Clear();

            if (value == null)
            {
                return;
            }

            AddRange(JsonConvert.DeserializeObject<T[]>(value));
        }
    }
}

EF 6 doesn't support mapping generic types, so we'd provide a concrete implementation for each type of List we might want:

public class PhoneNumberCollection : SelfSerializingCollection<PhoneNumber> { }

And then use it like this:

public class Contact {
    public PhoneNumberCollection PhoneNumbers { get; set; }
}

And that was it. Now, I'm trying to achieve the same in EF Core 2.0.

Here's what I have so far. The SelfSerializingCollection<T> class is unchanged.

public class Contact {
    // EF Core supports generic types so we don't need a concrete implementation
    // for each collection type.
    public SelfSerializingCollection<PhoneNumber> PhoneNumbers { get; set; }
}

public class MyDbContext : DbContext
{
    // ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // EF Core does not support the [ComplexType] attribute
        // but we now have .OwnsOne() in EF Core 2
        modelBuilder.Entity<Contact>().OwnsOne(p => p.PhoneNumbers);
    }
}

So far so good. EF Core maps the PhoneNumbers_Json column in the DB. Reading and creating new entries works just fine.

But, unlike in EF 6, any changes to the PhoneNumbers collection on an existing entity are not being persisted back to the database. I am updating in a typical PUT method with _context.Entry(contactModelFromRequestBody).State = EntityState.Modified; - same as before. But for some reason, EF Core is not persisting my Json string property back to the DB for a Modified entity.

Any ideas?

like image 613
Matt Jenkins Avatar asked Oct 22 '17 09:10

Matt Jenkins


2 Answers

EF Core treats owned types like entity types w/o own identity, hence properties that point to owned type are treated as navigation properties. And setting the parent entity state to Modified does not cascade to navigation properties.

However the Update method of DbContext or DbSet does cascade, so instead of

_context.Entry(contactModelFromRequestBody).State = EntityState.Modified;

you should use

_context.Update(contactModelFromRequestBody);
like image 152
Ivan Stoev Avatar answered Sep 18 '22 12:09

Ivan Stoev


It seems necessary to manually set the modified entity state for my owned types:

_context.Entry(contact).Reference(p => p.PhoneNumbers)
    .TargetEntry.State = EntityState.Modified;

This does the trick, although I'm still hoping to find a more general solution that doesn't require me to mark all the owned properties like this on every update.

like image 21
Matt Jenkins Avatar answered Sep 19 '22 12:09

Matt Jenkins