efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Regression going from .NET/EF 8 to 9 with CosmosDb, will no longer save/load Ulid and Custom struct types

Open mip1983 opened this issue 1 year ago • 0 comments

Creating this as a separate issue from #31376 , described below with repro (https://github.com/mip1983/CosmosDbEF)

Hi @roji, thanks for the input, I'm a bit confused by what your saying about the complex type support on CosmosDb, as mapping my struct as a complex type (where it's not nullable) seems to stopped one of the errors I was getting after updating to .NET 9. Just to explain the steps I've gone through:

Existing .NET 8 solution, I have a particular entity that includes properties:

  • Ulid type for ID (not guid, https://github.com/Cysharp/Ulid)
  • Two properties that are of a custom 'DateTimeRange' type (struct). One is nullable.

With no special mapping or configuration applied in 'OnModelCreating', this entity would save and load from CosmosDb. This is an example from the Azure UI of this property on a record: Image

Without changing anything else, just running the .NET 9 upgrade tool on the project in visual studio (which updates 'Aspire.Microsoft.EntityFrameworkCore.Cosmos' to 9.0.0-rc.1.24511.1), I get mapping errors on startup about the Ulid and DateTimeRange properties as not a known primitive type.

The Ulid property would just save/load as a string by itself somehow. I've now needed to add a Ulid converter in 'ConfigureConventions'. Not sure why it worked before and not now?

By adding this to the mapping for the non-nullable 'DateTimeRange' property:

modelBuilder.Entity<Enquiry>().ComplexProperty(o => o.DateRange);

The error goes away and my solution loads. The records seem to load ok, I haven't tried saving/updating yet. I don't know why I need this now and didn't before (edit: have tried on my repro, see link on the end, it works with this mapping).

With the nullable 'DateTimeRange', that worked before, I have some objects with null on this property and others with it populated. I'm not really sure how to map this now, I've got it set to ignore for the moment just to get it running.

For me, one of the key things about CosmosDb is that you're not bound by a flat 2d table structure, records are just objects as JSON so can have this complexity, as long as it serializes/deserializes. At least that's how it was with the CosmosDb client. I'm finding it to be one of the challenges going from the client to EF is how it handles these complex objects, it feels like it really 'wants' to be relational. I wish it would treat one object as an entity to be tracked, it doesn't matter what type or complexity a property is as long as it can be turned to/from JSON. If you attach a 'complex' object to another, it can stop tracking that complex object as it's own thing and just treat it as part of the thing it's attached to (ideally without needing to tell it that it's an owned or complex type, isn't that implicit with a record in Cosmos?). That's how it's going the get saved to the DB. Anyway, I'm digressing.

Update @roji

Repro project here: https://github.com/mip1983/CosmosDbEF

Originally posted by @mip1983 in #31376

mip1983 avatar Oct 18 '24 11:10 mip1983