Current Limitations of Entity Framework Core

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 are 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, way ahead of EF Core, but that’s not what I’ll be writing about.

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 only supports one type of collection. It can be lazy loaded, in which case, you need to declare it as ICollection<T> or IList<T>, or not, but that is it. You can’t have collections of primitive types without using a value converter. Plus, 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, TPH and TPT are already supported, but TPC is not. There are plans to introduce it in the future (https://github.com/dotnet/efcore/issues/3170)

Expression Columns

Currently there’s no way to map a column to a SQL expression, which could be very handly. True, we can have a whole entity coming as the result of a view, or a raw SQL, but this is a different thing. Of course, that property would not be persisted.

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. This is also currently scheduled by the team for a future release (https://github.com/dotnet/efcore/issues/245).

Strongly Typed DML Operations

One thing that would be nice and already exists on other ORMs is the ability to batch perform DML operations from LINQ queries, for example, UPDATEs and DELETEs. Take this hypothetical strongly typed delete on top of a LINQ query, for example:

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

Much better than writing SQL as hardcoded strings, don’t you think? Yes, I do know that there are third-party extensions out there that already do this, but it would be nice to have this built-in. It’s being tracked for future implementation (https://github.com/dotnet/efcore/issues/795).

Lifecycle Events

Some lifecycle events – such as EF 6’s ObjectMaterialized and SavingChanges - are conspicuously missing. This can be useful, for example, to do something on an entity after it is loaded (hydrated) from the database. Fortunately, there are plans to introduce them (https://github.com/dotnet/efcore/issues/15911).

Conclusion

As a I said, these are just some thoughts on nice-haves for EF Core. Some of them are already on the EF Core team’s list, and some are not. There are many more, see the whole plan for v7.0 here and do submit your own suggestions here. As usual, I’d love to hear your thoughts on this, so let’s have them!

                             

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