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?
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With