Current Limitations of Entity Framework Core 8

Update: updated on 31/07/2024 for EF Core 8

Introduction

Although EF Core seems to be the most popular ORM in the .NET world in these days – and I for sure won’t contradict it –, there is still some functionality missing, specially if we compare it with other ORMs that also exist, NHibernate coming obviously to my mind. This post is a reflection about that, it is in no way a comparison with other ORMs, in particular, NHibernate, it’s more a wish list for EF Core. I still follow and love NHibernate, and it’s still, in many ways, somewhat ahead of EF Core, but that’s not what I’ll be writing about. I had a list here, and I recently updated it.

Primary Key Generation

EF Core currently only supports the following primary key generation strategies:

  • Identity/autoincrement (on databases that support it)
  • GUID/uniqueidentifier (on databases that support it)
  • Sequence-based (on databases that support it)
  • HiLo (SQL Server only)
  • Manually assigned

For GUIDs, EF Core automatically generates, on the client-side, a new value before inserting, which is nice.

HiLo is a great pattern for generating primary keys, but, unfortunately, the way Microsoft implemented it, resorting to database sequences, makes it database-specific – currently, SQL Server only. If it were implemented with just standard DML commands, it could be made database-agnostic, for the benefit of all.

I dare say that EF Core is slightly biased towards SQL Server and, even more, that most SQL Server folks are used to having IDENTITY as their default primary key generation strategy. While this is fine, it does prevent batch insertions, because it requires obtaining the generated primary key after each inser (SELECT SCOPE_IDENTITY()). If we have a client-generated primary key, batch insertion is possible, which can be a great benefit.

I’d like to have a truly extensible primary key generation mechanism on EF Core. Maybe something for the future.

Collection Types

Currently, EF Core supports:

All can be lazy loaded, in which case, you need to declare them as ICollection<T> or IList<T>, but, you don’t have semantic collections such as sets or bags, or indexed collections such as lists or maps.

Table Inheritance Patterns

There are three canonical table inheritance patterns:

Currently, all of them are fully supported.

Mapping Entities to Views or SQL

In EF Core it is called keyless entity types mapping, and it is supported now.

Expression Columns

It is now possible to map a property to a SQL expression.

Expressions for CUD Operations

Another useful operation, the ability to use raw SQL – which could even be a stored procedure call – for Create, Update, and Delete (CUD) operations is already supported.

Strongly Typed DML Operations

Also known as bulk updates and deletes, they're here too.

Lifecycle Events

All of the typical lifecycle events are now implemented.

Interceptors

Here too.

Entity and Table Splitting

Both entity and table splitting are now supported as well.

Owned Types

For properties and collections, also here.

Value Converters

Value converters for types have been around for some time now.

Conclusion

In conclusion, we can see that pretty much anything that has been missing for a while is now implemented! Some features are still missing or partially implemented (I would place custom key generation on top), but the future looks bright for EF Core!

                             

12 Comments

  • In your example, I think you are missing a minus sign.

    var deletedPosts = ctx.Posts.Where(p => p.Timestamp.Date < DateTime.Today.AddDays(-365)).Delete();

  • @Al: totally, thanks! :-)

  • Regarding the ability of efcore to use stored procedure and raw sql I believe this ability already exists. Unsure how old this article is but assuming it’s fairly recent as it was linked in a code project email today. Source is several tutorials including an MS one detailing how to do it with EF core.

  • "While this is fine, it does prevent batch insertions, because it requires obtaining the generated primary key"

    So you're sure you can't lock the table fetch the next id, and wrap assigning the ids client-side inside a transaction, then unlock the table?

    Maybe less than ideal, but not sure why it couldn't be made to work? Have not tried.

  • For primary key generation, I'm curious as to what other client-generated algorithm you can use, aside from GUIDs or meaningful/manual constants. I can't picture any other way of doing it that would be safe and ensure two clients don't try to insert the same value.

  • @Chance:
    The difference is: if you know the ids beforehand, you can insert them all at once, e.g.:

    INSERT INTO mytable ... VALUES @p1 ...
    INSERT INTO mytable ... VALUES @p101 ...

    whereas with IDENTITY you need to do this:

    INSERT INTO mytable ... VALUE @p1 ...
    SELECT SCOPE_IDENTITY()
    -- hydrate object here with obtained identity
    INSERT INTO mytable ... VALUE @p1 ...
    SELECT SCOPE_IDENTITY()
    -- hydrate object here with obtained identity

    This is a known problem, for example, in NHibernate

  • @SyntaxisTaxsyn:
    please see https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/plan#map-cud-operations-to-stored-procedures and https://github.com/dotnet/efcore/issues/245.

  • @Joe Enos:
    just out of curiosity, NHibernate supports 19 strategies (or 18, because native just picks one of the others): https://nhibernate.info/doc/nhibernate-reference/mapping.html#mapping-declaration-id-generator.
    Some are variations of GUID, there's HiLo with and without sequences, and others.

  • Your "Strongly Typed DML Operations" example supposed a requirement to DELETE a collection of rows, sight unseen which imho is a dangerous scenario and should be the province of server-side DBA. Ok your 1yo rows intent is clear dough but I'd worry about less-defined cases (especially considering any concurrent activity which would be unknown to user1).
    The present EF all would have to pull all the matching rows then delete client-side and action that with a SaveChanges call thus 2 round-trips, but would at least offer possibility to reconsider such a mass draws (e.g. should preserve sticky posts?) so might assuage my DBA-brain.
    The DBA is likely to write a sproc to achieve such archiving (granting appropriate Execute permissions if should be invokable remotely.
    Roji's "operators would return the number of rows affected" on your Delete is of doubtful value, and reminds me of the case of DELETE of rows already deleted (should this be an error or silently ignored?)

  • On "Table Inheritance Patterns" obviously TPC is now live, but my complaint is that the scaffolding tooling cannot rev-entry such exotic inheritance schemas generated by EF code-first creation. Although ErikEJ's extensions stash a json file, this has no repeatable knowledge of TPx.

  • On "Expression Columns" I wonder if you refer to a computer-column (serverside) or local client-side [Ignore] that is not mapped to backend, and could even occur in some partial sourcecode file.

  • @Dick: regarding your question about strongly typed DML, right now, nothing prevents EF Core users from using SQL to do just that, eg, bypassing optimistic concurrency and the likes, or just deleting any records. NHibernate already had this for some time and there were some popular extensions for EF for doing this too.
    On TPC, of course you are right, but it's there just for completeness, NHibernate supports it for more than 10 years.
    Expression columns, I mean SQL that is mapped to a property. Serverside, yes, and it's already supported.
    Again, thanks for the feedback!

Add a Comment

As it will appear on the website

Not displayed

Your website