dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.48k stars 3.13k forks source link

Different LazyLoad/Proxies behavior with AutoMapper between EF6 and EF Core #32708

Open phlogan opened 6 months ago

phlogan commented 6 months ago

Hello everyone.

Recently, me and my team decided to migrate our whole web system from .NET Framework to .NET 8, and upgrade/migrate all of our frameworks too, this lead us to migrate from EF6 to EF Core.

We use AutoMapper to convert Entities (which usually comes directly from Repository, with navigations "lazy loaded") to Application Models/ViewModels. And yes, we know that is not a good practice and we are replacing ViewModels to DTOs as we can, but it is definetelly all over so we still rely on this mapping, even though we face a little performance issue. We are using AutoMapper 9.0.0 in both .NET Framework and .NET 8, with the same configuration file (except from the line x.ShouldMapMethod = (m => false); in .NET 8 version, without it, the code just don't run: https://github.com/AutoMapper/AutoMapper/issues/3988.

We have both EF Core and EF6 configurated similar when it comes to LazyLoading and Proxies, as I show bellow: image

And the AutoMapper is configured, in both: automapper_efcore_ev4

With that being said, there is any reason why this same code takes 13.000 miliseconds to run in .NET Framework + EF6 and 800.000 miliseconds in .NET8 + EF Core 8? I may have missed something, but there is any difference of behavior when it comes to LazyLoading/Proxy in those versions? If it loads the data from the database on demand, it is completely comprehensible to be slow, but why this expressive difference? And just for the record, the entity being converted have lots of navigations and those are really large in terms of other navigations and fields itself. image

I even suspected from the .NET Framework package that we used to manage scopes, Medihme.Entity, I thought it could be improving the performance somehow, but just don't make sense and i couldn't find nothing relevant about.

Updating the AutoMapper to any version post 9 do not solved the problem though.

Anyway, I've implemented a workaround to "unproxy" the entities and some sub navigations, it is not perfect because I could not load dinamically sub collections navigations, but will fit for our case and, anyway, it is temporary.

Thank you!

AutoMapper: 9.0.0 AutoMapper.Extensions.ExtensionsMapping: 3.1.2

Medihme.Entity: 1.0.0

EF Core: 8.0.0 (running in .NET 8) EF: 6.4.4 (running in .NET Framework 4.6.1)

Database provider: SQL Server Operating system: Windows 10 IDE for .NET 8: VS 2022 17.8.3 IDE for .NET Framework 4.6.1: VS 2019 16.11.32

ajcvickers commented 6 months ago

This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

phlogan commented 5 months ago

Of course! I will post a runnable project as soon as I can. Thank you!

phlogan commented 5 months ago

Hello everyone.

I've just created a small repository to represent this issue (small as I could, as far as the mentioned issue needs a large data set for the difference be spotted).

Here is.

And it's important to note that in this given small project, with few entities and navigations, the difference may be not much relevant, but in production databases, it leads to unpracticable performance issues.

Here is too some of the results i had:

.NET Framework 4.6.1 + EF6: image

.NET 8 + EF Core 8: image

Thank you!

ajcvickers commented 5 months ago

Notes for triage: this is an extreme case of "n + 1". That is, the top level query is executed, and then 33,000 additional queries are executed to load each individual navigation. This is slightly slower in EF Core than it is in EF6:

EF6:

Method Mean Error StdDev
Map 7.917 s 0.0775 s 0.0725 s

EF Core 8:

Method Mean Error StdDev
Map 8.355 s 0.0989 s 0.0925 s

This is not unexpected, since we know the Castle-based proxies in EF Core are generally a bit slower than the EF6 custom implementation. However, it could also be due to change tracker perf or something else.