It would be really valuable imho to see more complicated examples, not just for performance reasons but because I'm just very curious about how more complexity might change any of your approaches or design considerations in something that is closer to real world scenarios. Sometimes I come away from the videos and courses learning a lot, but not necessarily seeing how my design could really look like in a larger picture.
Agreed...him advocating for FirstOrDefault over SingleOrDefault because of the difference between looking up 2 records versus 1, is saying "don't worry about logical errors...we want another 1 microsecond of performance!"
I'm particularly interested if this approach is feasible in a filtered pagination scenario. If it's not, it would seem to have a limited use case for scenarios where performance would seem to matter more.
Yes, a follow-up video of more complex queries would be helpful, as it will paint a clearer picture for those looking to choose between Dapper & EF Core.
As compiled queries were readily available in LINQ to SQL, it's interesting to see developers on EF Core disregarding the use of compiled queries (nor even questioning whether it exists at all, leaving valuable CPU cycles on the floor). Basically, all it takes is curiosity and peeking at the mechanism that translates C# based LINQ queries to SQL, and the subsequent materialization of data to typed objects. That path should lead to questions on whether the continuous translation could be omitted (i.e. cached, parameterized), and thus end with the discovery of compiled queries. As a freelance consultant, absence of compiled queries on contracted projects are usually the norm, and it's usually a quick win to implement. Disclaimer: compiled queries are more heavy during application start-up, obviously, as the expression tree needs to be evaluated and translated, but they're clear winners for the majority of applications that are online for hours or days on end. Nick mentions this as well, but given the amount of memory available to app domains these days, compiled queries should be the default approach imo.
They could make it a setting too. So you could have runtime queries while developing, compiled queries in production (assuming no weird discrepancies between the two)
Or use dapper and its even faster. And simpler. And uses less memory. And quicker to startup. And easier to understand the queries that are used to call the DB, no surprises in the generated sql.
@@gbjbaanb I am a Junior developer and I can't understand how you even can able to use Dapper in a real project... For example, you have to update an entity with some many-to-many relationship. How should I implement the Update() method in my repository? For now I just delete all records in link-table and then create them all again. How it can be faster then EF Core? EF Core see that you for example delete one item from the collection property and add one item. And it generates the appropriate sql query. Or, even simpler example. Let's forget about many-to-many. Just a simple Update() for a small model. With Dapper you will write the SQL code that updates all the fields. EF Core will update only one field. I am sure that I am wrong somewhere and maybe my question is even some kind of stupid, but can you explain it to me please? Grats!
@@ЕгорФедоренко-с2щ EF Core translates to the same t-sql language you use in dapper when executing queries. So anything you do in ef core can be done in Dapper you just need to look at the output query EF core produces.
In 2013 I needed to used compiled queries for a Windows Phone application, something like you did to cache the queries to a local database. I'm glad to see them again in the wild.
Personal opinion on this kind of subject is that if you have the experience to know when something like compiled queries is necessary then go for it. Otherwise I tend to go with the "it isn't a performance issue until it is one." If something becomes a performance bottleneck for one reason or another then use a profiler to find where it is, diagnose the root cause, and then optimize for your needs. I just ran into this one in a project I'm helping with where they were rending ~150 (at most) extremely simple polygons (7 or 8 points at most). I wrote a conversion function that would give you the polygon when you asked for it. In areas of the program that needed a polygon I would ask for one and do with it what I needed. Performance was perceptively instantaneous and memory usage was well within acceptable parameters. The argument was around the idea of "we should cache it on the way in so it is calculated and created once at most. It'll perform better that way." I benchmarked it (with DotnetBenchmark of course) and doing the operation that produced a polygon 3x as complicated than needed an insane number of times (they would never get close to) the results were 7.4ms and 10MB of heap allocation. In some contexts that is insanely slow. In this particular context it is insanely fast. Unfortunately it was multiple days worth of discussion even after I shared my results. I'll never get that time back that everyone could've spent doing more productive things. TL;DR If your experience tells you this is the right time to do something more performant have at it. If you don't have the experience then focus more on how clear your intent is being expressed. Sometimes code that is easier to read and understand is worth more than code that executes faster. To steal a quote I read from someone somewhere on UA-cam "if your code doesn't work I don't care how fast it doesn't work."
Yup, you are absolutely right. In fact, the very reason people use Dapper is not query compilation time - it's that EF sometimes does bad job when translating LINQ to SQL for complicated queries. And I hear people say: "well, you can guide EF to do better job" - and that's true in most cases, I can spend couple hours or even days trying to "guide EF" or I can write SQL query in 15 minutes, but who would want to do that, right?
Dear Nick, i love your videos and just became a patreon because of this. I love these small videoes with ready to use stuff, 1 focus, which makes it easier to implement on an every day to day basis! Thanks for your content!
Nick, this is excellent content, thank you. This feature gives me the best of both worlds - the convenience and feature set of EF core, and the performance where it's needed. Also interested in your thoughts on the new EF Core 8 raw SQL queries for unmapped types, in the same context of opt-in for squeezing performance, and what the limitations or gotchas are. Thanks!
Would be interested in your take on options such as lazy instantiating of compiled queries so that you defer the hit on performance from startup to first use, and on using in-memory caching and cache expiration of queries in cases where they might be used in bursts but then not be required again for some time, to help control memory footprint in large / long lived apps.
Lazy loading ... eish, I always fall for that trap, then I remove it :) If you manage all the code, then Lazy is fine, but someone always iterates a list somewhere, and they love the fact that the nested references are loaded via lazy loading .... until they run it with real data. where there is more than two records :) But yeah, I've never tried Compiled Queries with LazyLoading. Might be wrong, but i think it will cause havoc if there are circular references in you poco classes
@Nick Chapsas , would it make a difference to use FromSql(), FromSqlRaw() from EF ? While using native SQL for Dapper, it would be nice to see, how FromSql() performs from EF, excluding all LINQ
@@PelFox Well thats really the question. If EF can do everything dapper can do and if when compared like for like its as performance or out performs it and is nearly as performant when you use it with a bunch of quality of life improvements then really, what is the point of Dapper?
top drawer video... A big YES for follow-up video of more complex queries like joins etc... Once again you're putting out awesome content Nick!!!! My fav UA-camr and trainer
I boldly assume the same rule of thumb applies to using prepare: if your query runs once each time your application runs (or in the case of prepare opens a connection), it's not worth the effort to compile it, if it runs more than once it is worth the effort to skip compiling the query on the fly each time it's invoked.
Hi Nick, thank you for the Benchmarks! Great information as always :) Please make a video on more complex queries. Take care and enjoy the Easter Holidays.
How do I pass the CancellationToken to the compiled query? Since the lambdas inside use synchronous methods I cannot simply pass the CT there. How can I pass one, and ensure the query is still cancellable?
Amazing video as always Nick. In past we moved project from EF to sql procedures (this required lot of business logic rework). Performance hit was huge from EF for us, but good to know of things keep improving from EF Core.
Test with even more and bigger advanced queries please. Love you and your content! also very intrigued about ef vs dapper in medium to large applications.
IT would be great to see this. Also Linq2Db can be used together with EF Core as a synergy where it gives you possibilities to use all the features from both ORMs, which is just fantastic.
Sounds like the expanded video about optimizing more complicated queries with lots of joints and counts and the like would be exactly what I have been looking for 😅
I just subscribed to your channel, really enjoying your videos because you seem to emphasize application performance, something I highly value. I'm new to the field and have a lot to learn. I don't want to be offensive, as English is not my first language, but if possible, could you speak a bit slower? It's not your speech, it's just me being a bit slow, hahahah. But your content is great!
Isn't precompiled queries only for speeding up the first time you do that query? When not using precompiled queries it is compiled and cached the first time and then subsequent queries will be fast. Or am I wrong?
The difference is that without compiled queries, even after EF caches the compilation result (i.e. the SQL), each time you send it the same query, it still has to traverse and examine the expression tree in order to determine whether or not a cache entry exists for that particular expression tree. With compiled queries, on the other hand, this step is eliminated.
I wonder if EF cache expressions? So if you call _context.Movies.FirstOrDefault... it will eat some memory & time for the first execution, but does EF need to do it every single call afterwards?
@@nickchapsas Interesting, Microsoft says "They're quicker to execute". Common sense would think so too. I am curious now why there are no performance gains.
Hello @Nick Chapsas I have one question public static class OneEntityCompiledQuery where TEntity : class { public static readonly Func FirstOrDefaultAsync = EF.CompileAsyncQuery((DbContext context, Expression predicate) => context .Set() .AsNoTracking() .FirstOrDefault(predicate) ); } Can I do something like this for more generic use of the CompiledQueries? To pass the where expression as a parameter ?
If you dont control the db structure, lets say different team will be incharge of db and how the db model evolve and you still need to access the db writing different complex query from moultiple tables inculding update and inserts based on those queries would you still go with EF/Dapper way or choose lower level such Oracle.ManagedDataAccess incase of oracke db?
If I understand Dapper correctly, I would point out that in Dapper you have the SQL as a magic string. Rarely would this be limited to one line. If it was more complex, it would then likely move to a stored procedures. Which would then likely bleed in business logic with a stored procedure...not a good practice anymore (IMO). In addition, if an update gets involved, we now need a multi table transaction. What would be interesting is a comparison of Dapper and efcore in terms of a more complex db structure. I respect your speed argument, but a few milliseconds here and there are not as costly as a developer with bad coding practices.
Not every big string is a magic string. Especially with IDEs like Rider, I can use Raw String Literals and I have full syntax highliting of the query with checks on per-engine syntax. This is the best case scenario for EF. It only gets worse from here.
Imho sprocs are the best practice. Put all your sql into sprocs and then treat the db as an API. It requires more discipline from coders though, which is why it's considered bad practice 😅. It's also easy now securable as you can prevent arbitrary access to tables so if a hacker ever got access, they can download your entire user table with a single query. Not so if you only use sprocs.
@@nickchapsas IMO - A string is within the code base that expresses a SQL statement is NOT a good place for a SQL statement. Its a string that contains a statement that a SQL processor has to interpret. Adding IDE assistance in the creation of the string (especially when its relational) means it has to understand the schema to be of assistance, which i feel adds additional coupling (it needs a connection string to do that). SQL statement as strings IMO is just not a good idea from a maintenance perspective. It leads to concatenation to create a SQL string that will select or filter the data as required. I feel that is not falling into the pit of success, but really poor code. Many developers simply want a tabular dataset, and throw the SQL at the engine. Millions and millions of dollars have been spent how to optimize a query. Using procedural logic to first limit the dataset , and deal with a subset, is an effective way to reduce the load on the data engine (regardless if its relational or otherwise). Using a stored procedure is also known to be an issue when it contains business logic. Data retrieval and storage need more thought, and IMO a SQL statement in code isn't something that cuts it for me.
@@gbjbaanb - If all you’re considering is performance then I agree with you. When you look at it from a maintenance, testing, CI/CD, and technical expertise standpoint then it quickly becomes untenable. I worked on a project that had over 500 stored procs and updating a stored proc was a nightmare. Changing the shape was nigh impossible because there was no real way to determine who was consuming it and whether they would be affected by your change. What you end up doing is making a new sproc with your slightly different requirements and so you end up maintaining multiple versions of essentially the same thing. Sooooo glad to be off that project 😅
I'm curious what Benchmark is doing. It's my understanding that EF Core has a query cache, and if they see the same query they will reuse it hence saving the compile time. I think they also have, or will have a way to compile the queries at build time and they are cached in a DLL. I've never seen the need for these optimizations. But it's good to know they are there.
Also you could show some samples of how to refactor classic existing scoped services from DAL (repositories etc) to support compiled queries. That would be great!
Great video but does it worth the effort ? In my projects I just combine them - EF Core is great for Save operations and simple queries and Dapper for Stored procedures (mainly complicated read opperations). I checked to see that this is a practice in many other projects and fiths well in the CQRS pattern. Even if you make EF Core faster - there are some restircitions how stored procedure can be executed, you don't really need to loose time with this.
yes plz, explain it, and how hard to said compiled query is translate linq like script to sql as same as you directly write it , then cache and reuse it ?
Informative. Dapper is not tracking the entity as per my knowledge and for EF core it is on by default. What would be the scenario like With Change Tracking , Without Change Tracking, Compiled query and Dapper comparison?
This is very cool feature and in my opinion, that allocated memory is nothing for achieving that performance. One of my question about this video is: How it fit in specification pattern and DDD? I think we should put our specifications in infrastructure layer. any idea about that?
.First is suspect, in the context used by Nick. If you know there will be one result, limit 2 will return the same number of records. If that is enforced by constraint, then the query will typically scan the same row count. If not enforced, then .Single is essential. The specifics are relevant, but don't go replacing all your method calls for performance.
I would personally not put async on methods that just return the Task. Eg the EF_Single_Compiled(). Just skips the whole state machine. Might change the performance slightly.
12:24 "they inflate the memory" by around 3 KB per single cached query? If you have 50 entites and 5 queries for each of those, you basically waste 750 KB of memory that is statically allocated and lives throughout the entire app? My only concern would be the startup time, but even that doesn't seem to hurt enough, it's matter of 2 us per compiled query, so you get 500 us in the startup, although that's a bit of a lie too because we're not seeing how slow the cold start of those query compilers is, before they get JITted and shown in the benchmarks.
Didn't understand how to use asynchronous with this feature. I Try to write something like EF.CompileAsyncQuery((context, id, cancelationToken) => ...ToList()). But cancellation doesn't work (logic - i didn't use it in my lambda). On the other hand i can do EF.CompileQuery((args) => ...ToListAsync(cancellationToken)). But i do not understand how to right
One item you didn't cover is adding "AsNoTracking()" on the EF call. [Pardon if covered in a prior video.] I've found this made a significant different in my EF routines when I know I don't need to do any updates on the returned data.
I didn't know Dapper, so in comparison. Does Dapper also use static queries like the pre-compile. For me it's make a big difference, if the queries stayed for ever. So for a web-application, not rly useful or better, use it with all respect .
@@nickchapsas This time it's not exactly true. Since you left "buffered" parameter of .QueryAsync(...) as true (it is a default value), the result you get after 'await' is actualy a List.
I have to wonder, but wouldn't a source generator be able to perform the compilation and inline the result at compile time, thus saving on the startup penalty? I would be interested in seeing if the delegate can be hidden behind an extension method on the DbContext, and what the performance penalty of that would be, and if it can be optimized? I personally feel it's more natural to start off with context.SingleMovieAsync(id) instead of passing the context as a parameter.
I love your videos and you have taught me tons, but this one isn't working for me. In my app, I'm using the unit of work/repository pattern. I created a benchmark app, that pulls in my unit of work to test my linq vs compiled query. My linq query is 2 to 3 times faster and it uses less memory. The query I tested is a simple select with a IN clause in the where. Any idea why?
Wow I didn't know you could do this. I'm thinking whether it's difficult to implement this for when you have a query that may apply certain filters or not depending on the request. Is there a way to chain this compiled queries like returning and IQueryable or it makes no sense?
Interesting. But still, its microseconds difference using raw sql spaghetti vs strongly typed LINQ. I would pick EF anyday and use raw sql only when it's absolute necessary.
Hey, this is not an AsNoTracking query, so it's not a fair comparison since Dapper doesn't have a tracking feature. EF Core should be used with AsNoTracking option enabled, as it can be resource-intensive and use reflection, which can have a heavy impact on performance.
It just doesn't make sense to create a compiled query for every query you want as fast as dapper. It just makes the code so much more complicated for other users. I've been a long time EF Core user, and a recent converter to dapper (3 or so years now) When I was using EF I used compiled queries, and when I really wanted to make things faster in EF I used stored procs. If I want to churn out an app fast I use EF Core for Insert, Update, Delete ... and dapper for Read. If I want a performant application with human readable code I use Dapper. ... and if I wanted to annoy everyone I used readers and writers :)
Nice video Nick 😀 Question for you: Based on my experience with EF Core, the main issue I had was to generate complex reports. When you have to join a lot of tables, do some aggregation, group by and related stuffs. Do you have any good practices to shared with use. Especially when you want to avoid as much as possible stored procedure or plain raw SQL
I found dapper when s complex join i was trying to implement in EF failed to generate correct sql. It was a bug and might have been fixed by now, but if EF fails like this, then you're SOL. The more complex your queries the greater the chance you'll have to write sql anyway. EF is great for the simple stuff.
As for the memory you carry around with the application in the case of the compiled query, shouldn't be considered. In essence you make compiled quries for actions that you perform often which means it is a lot memory you don't have to now garbage collect multiple times. In an API scenario the query will be called multiple times per second meaning the memory footprint is way better. If you call it once per day then bohoo right?
May you try to just add MemoryCache? If I remember well, if EF founds MemoryCache in DI, it will use it to cache the compiled query (so you shoudn't use pre-compiled functions).
It would be really valuable imho to see more complicated examples, not just for performance reasons but because I'm just very curious about how more complexity might change any of your approaches or design considerations in something that is closer to real world scenarios. Sometimes I come away from the videos and courses learning a lot, but not necessarily seeing how my design could really look like in a larger picture.
Agreed...him advocating for FirstOrDefault over SingleOrDefault because of the difference between looking up 2 records versus 1, is saying "don't worry about logical errors...we want another 1 microsecond of performance!"
I'm particularly interested if this approach is feasible in a filtered pagination scenario. If it's not, it would seem to have a limited use case for scenarios where performance would seem to matter more.
Yes, a follow-up video of more complex queries would be helpful, as it will paint a clearer picture for those looking to choose between Dapper & EF Core.
The fact that EF doesn't automatically do AOT compile for queries is really astonishing.
My exact thought. I just assumed this happened under the hood by default.
that would take more memory so idk why it needed to be done automatically
As compiled queries were readily available in LINQ to SQL, it's interesting to see developers on EF Core disregarding the use of compiled queries (nor even questioning whether it exists at all, leaving valuable CPU cycles on the floor). Basically, all it takes is curiosity and peeking at the mechanism that translates C# based LINQ queries to SQL, and the subsequent materialization of data to typed objects. That path should lead to questions on whether the continuous translation could be omitted (i.e. cached, parameterized), and thus end with the discovery of compiled queries.
As a freelance consultant, absence of compiled queries on contracted projects are usually the norm, and it's usually a quick win to implement.
Disclaimer: compiled queries are more heavy during application start-up, obviously, as the expression tree needs to be evaluated and translated, but they're clear winners for the majority of applications that are online for hours or days on end. Nick mentions this as well, but given the amount of memory available to app domains these days, compiled queries should be the default approach imo.
They could make it a setting too. So you could have runtime queries while developing, compiled queries in production (assuming no weird discrepancies between the two)
Or use dapper and its even faster. And simpler. And uses less memory. And quicker to startup. And easier to understand the queries that are used to call the DB, no surprises in the generated sql.
@@gbjbaanb I am a Junior developer and I can't understand how you even can able to use Dapper in a real project... For example, you have to update an entity with some many-to-many relationship. How should I implement the Update() method in my repository? For now I just delete all records in link-table and then create them all again. How it can be faster then EF Core? EF Core see that you for example delete one item from the collection property and add one item. And it generates the appropriate sql query.
Or, even simpler example. Let's forget about many-to-many. Just a simple Update() for a small model. With Dapper you will write the SQL code that updates all the fields. EF Core will update only one field.
I am sure that I am wrong somewhere and maybe my question is even some kind of stupid, but can you explain it to me please? Grats!
@@gbjbaanb Yep. Dapper is much better and you have complete control over optimisation
@@ЕгорФедоренко-с2щ EF Core translates to the same t-sql language you use in dapper when executing queries. So anything you do in ef core can be done in Dapper you just need to look at the output query EF core produces.
Hi, Can you make scenario where stored procedure from database side is integrated into Entity Framework
Ef core power tools visual studio extension
Are you asking how to do it? Or if there's a similar performance overhead to be fixed?
In 2013 I needed to used compiled queries for a Windows Phone application, something like you did to cache the queries to a local database. I'm glad to see them again in the wild.
Personal opinion on this kind of subject is that if you have the experience to know when something like compiled queries is necessary then go for it. Otherwise I tend to go with the "it isn't a performance issue until it is one." If something becomes a performance bottleneck for one reason or another then use a profiler to find where it is, diagnose the root cause, and then optimize for your needs. I just ran into this one in a project I'm helping with where they were rending ~150 (at most) extremely simple polygons (7 or 8 points at most). I wrote a conversion function that would give you the polygon when you asked for it. In areas of the program that needed a polygon I would ask for one and do with it what I needed. Performance was perceptively instantaneous and memory usage was well within acceptable parameters.
The argument was around the idea of "we should cache it on the way in so it is calculated and created once at most. It'll perform better that way." I benchmarked it (with DotnetBenchmark of course) and doing the operation that produced a polygon 3x as complicated than needed an insane number of times (they would never get close to) the results were 7.4ms and 10MB of heap allocation. In some contexts that is insanely slow. In this particular context it is insanely fast. Unfortunately it was multiple days worth of discussion even after I shared my results. I'll never get that time back that everyone could've spent doing more productive things.
TL;DR
If your experience tells you this is the right time to do something more performant have at it. If you don't have the experience then focus more on how clear your intent is being expressed. Sometimes code that is easier to read and understand is worth more than code that executes faster. To steal a quote I read from someone somewhere on UA-cam "if your code doesn't work I don't care how fast it doesn't work."
Yup, you are absolutely right. In fact, the very reason people use Dapper is not query compilation time - it's that EF sometimes does bad job when translating LINQ to SQL for complicated queries. And I hear people say: "well, you can guide EF to do better job" - and that's true in most cases, I can spend couple hours or even days trying to "guide EF" or I can write SQL query in 15 minutes, but who would want to do that, right?
EF core 8 now supports query execution and directly maps the results to VM or Dto such as dapper
Dear Nick, i love your videos and just became a patreon because of this. I love these small videoes with ready to use stuff, 1 focus, which makes it easier to implement on an every day to day basis! Thanks for your content!
Nick, this is excellent content, thank you. This feature gives me the best of both worlds - the convenience and feature set of EF core, and the performance where it's needed. Also interested in your thoughts on the new EF Core 8 raw SQL queries for unmapped types, in the same context of opt-in for squeezing performance, and what the limitations or gotchas are. Thanks!
Would be interested in your take on options such as lazy instantiating of compiled queries so that you defer the hit on performance from startup to first use, and on using in-memory caching and cache expiration of queries in cases where they might be used in bursts but then not be required again for some time, to help control memory footprint in large / long lived apps.
Lazy loading ... eish, I always fall for that trap, then I remove it :)
If you manage all the code, then Lazy is fine, but someone always iterates a list somewhere, and they love the fact that the nested references are loaded via lazy loading .... until they run it with real data. where there is more than two records :)
But yeah, I've never tried Compiled Queries with LazyLoading.
Might be wrong, but i think it will cause havoc if there are circular references in you poco classes
@@stoic7810 I don't think Mike Insch meant Lazy Loading of entities. I think he meant to use Lazy for compiled queries instead of Func
@@stoic7810 Yes, Lazy wont create the instance until it's called the first time, so it wont be as big impact on your startup time.
Very good, thanks Nick! Now waiting for AsNoTracking() explanation)
@Nick Chapsas , would it make a difference to use FromSql(), FromSqlRaw() from EF ? While using native SQL for Dapper, it would be nice to see, how FromSql() performs from EF, excluding all LINQ
Yep thats pretty much what I asked last time
Next EF video will cover this
EF core 8 preview has sql unmapped types, pretty much how Dapper does it. With that you won't even need Dapper.
@@PelFox Well thats really the question.
If EF can do everything dapper can do and if when compared like for like its as performance or out performs it and is nearly as performant when you use it with a bunch of quality of life improvements then really, what is the point of Dapper?
How about FromSql but also compiled?
Nick, continue with those videos.
They are super interesting. Thanks for debugging it.
You are a star!
top drawer video... A big YES for follow-up video of more complex queries like joins etc... Once again you're putting out awesome content Nick!!!! My fav UA-camr and trainer
I boldly assume the same rule of thumb applies to using prepare: if your query runs once each time your application runs (or in the case of prepare opens a connection), it's not worth the effort to compile it, if it runs more than once it is worth the effort to skip compiling the query on the fly each time it's invoked.
Hi Nick, thank you for the Benchmarks! Great information as always :) Please make a video on more complex queries. Take care and enjoy the Easter Holidays.
I'd love to see some examples of more complex expressions with benchmarks!
My team keeps track of the top slowest queries. Another great tool to have under our belt to optimize these.
How do I pass the CancellationToken to the compiled query? Since the lambdas inside use synchronous methods I cannot simply pass the CT there. How can I pass one, and ensure the query is still cancellable?
Why would u add cancellationToken if it's not async ?
Amazing video as always Nick.
In past we moved project from EF to sql procedures (this required lot of business logic rework). Performance hit was huge from EF for us, but good to know of things keep improving from EF Core.
Nice !!! Please keep continue with series of EF vs Dapper!
I use EF alot but still don't know all its perf aspects
thank you
Looks like an awesome addition to make to the EF repository wrapper
Test with even more and bigger advanced queries please. Love you and your content! also very intrigued about ef vs dapper in medium to large applications.
Please a video where you compare dapper and ef core with complext sql queries (joins and stuff). Nice video ! I love it.
please - more videos on this... brilliant
Could you also Test linq2db against both? It claims to be one of the fastest ORM Mapper.
IT would be great to see this. Also Linq2Db can be used together with EF Core as a synergy where it gives you possibilities to use all the features from both ORMs, which is just fantastic.
linq2db is great!
Sounds like the expanded video about optimizing more complicated queries with lots of joints and counts and the like would be exactly what I have been looking for 😅
I just subscribed to your channel, really enjoying your videos because you seem to emphasize application performance, something I highly value. I'm new to the field and have a lot to learn. I don't want to be offensive, as English is not my first language, but if possible, could you speak a bit slower? It's not your speech, it's just me being a bit slow, hahahah. But your content is great!
Super interesting and useful info - thanks for this and all your other content!
I want see more videos about that! Love your content Nick!
Isn't precompiled queries only for speeding up the first time you do that query? When not using precompiled queries it is compiled and cached the first time and then subsequent queries will be fast. Or am I wrong?
great point, I asked myself the same question
Wondering about this too
The difference is that without compiled queries, even after EF caches the compilation result (i.e. the SQL), each time you send it the same query, it still has to traverse and examine the expression tree in order to determine whether or not a cache entry exists for that particular expression tree. With compiled queries, on the other hand, this step is eliminated.
@@amirhosseinahmadi3706 thanks for the answer! So that is why we see the difference in the benchmarks.
I wonder if EF cache expressions? So if you call _context.Movies.FirstOrDefault... it will eat some memory & time for the first execution, but does EF need to do it every single call afterwards?
I would absolutely love to see examples with relations
Didn't know about compiled queries, please do the video about the more complex scenario's!
I'm probably missing something, but as per benchmarks you may benefit from compilation even when doing it in the same method, right before executing.
Would using .AsNoTracking improve performance even further?
In these tests it would actually make it worse. Check this video for more details: ua-cam.com/video/Q4LtKa_HTHU/v-deo.html
@@nickchapsas Interesting, Microsoft says "They're quicker to execute". Common sense would think so too. I am curious now why there are no performance gains.
@@nickchapsas does that video combine no tracking with compiled queries?
Wow Nick, I loved this video! Thanks a lot...
Hello @Nick Chapsas I have one question
public static class OneEntityCompiledQuery where TEntity : class
{
public static readonly Func FirstOrDefaultAsync =
EF.CompileAsyncQuery((DbContext context, Expression predicate) =>
context
.Set()
.AsNoTracking()
.FirstOrDefault(predicate)
);
}
Can I do something like this for more generic use of the CompiledQueries? To pass the where expression as a parameter ?
If you dont control the db structure, lets say different team will be incharge of db and how the db model evolve and you still need to access the db writing different complex query from moultiple tables inculding update and inserts based on those queries would you still go with EF/Dapper way or choose lower level such Oracle.ManagedDataAccess incase of oracke db?
Awesome video. We definitely need the more complex scenarios.
Also can you create a comparison calling a Stored Procedure?
I always learn something from these videos
If I understand Dapper correctly, I would point out that in Dapper you have the SQL as a magic string. Rarely would this be limited to one line. If it was more complex, it would then likely move to a stored procedures. Which would then likely bleed in business logic with a stored procedure...not a good practice anymore (IMO). In addition, if an update gets involved, we now need a multi table transaction. What would be interesting is a comparison of Dapper and efcore in terms of a more complex db structure. I respect your speed argument, but a few milliseconds here and there are not as costly as a developer with bad coding practices.
Not every big string is a magic string. Especially with IDEs like Rider, I can use Raw String Literals and I have full syntax highliting of the query with checks on per-engine syntax. This is the best case scenario for EF. It only gets worse from here.
Imho sprocs are the best practice. Put all your sql into sprocs and then treat the db as an API. It requires more discipline from coders though, which is why it's considered bad practice 😅.
It's also easy now securable as you can prevent arbitrary access to tables so if a hacker ever got access, they can download your entire user table with a single query. Not so if you only use sprocs.
@@nickchapsas IMO - A string is within the code base that expresses a SQL statement is NOT a good place for a SQL statement. Its a string that contains a statement that a SQL processor has to interpret. Adding IDE assistance in the creation of the string (especially when its relational) means it has to understand the schema to be of assistance, which i feel adds additional coupling (it needs a connection string to do that). SQL statement as strings IMO is just not a good idea from a maintenance perspective. It leads to concatenation to create a SQL string that will select or filter the data as required. I feel that is not falling into the pit of success, but really poor code. Many developers simply want a tabular dataset, and throw the SQL at the engine. Millions and millions of dollars have been spent how to optimize a query. Using procedural logic to first limit the dataset , and deal with a subset, is an effective way to reduce the load on the data engine (regardless if its relational or otherwise). Using a stored procedure is also known to be an issue when it contains business logic. Data retrieval and storage need more thought, and IMO a SQL statement in code isn't something that cuts it for me.
@@snapching I think a lot of your gripes seem to come from your own bad practices to be honest.
@@gbjbaanb - If all you’re considering is performance then I agree with you. When you look at it from a maintenance, testing, CI/CD, and technical expertise standpoint then it quickly becomes untenable. I worked on a project that had over 500 stored procs and updating a stored proc was a nightmare. Changing the shape was nigh impossible because there was no real way to determine who was consuming it and whether they would be affected by your change. What you end up doing is making a new sproc with your slightly different requirements and so you end up maintaining multiple versions of essentially the same thing. Sooooo glad to be off that project 😅
I'm curious what Benchmark is doing. It's my understanding that EF Core has a query cache, and if they see the same query they will reuse it hence saving the compile time.
I think they also have, or will have a way to compile the queries at build time and they are cached in a DLL.
I've never seen the need for these optimizations. But it's good to know they are there.
Also you could show some samples of how to refactor classic existing scoped services from DAL (repositories etc) to support compiled queries. That would be great!
Great video but does it worth the effort ? In my projects I just combine them - EF Core is great for Save operations and simple queries and Dapper for Stored procedures (mainly complicated read opperations). I checked to see that this is a practice in many other projects and fiths well in the CQRS pattern.
Even if you make EF Core faster - there are some restircitions how stored procedure can be executed, you don't really need to loose time with this.
yes plz, explain it, and how hard to said compiled query is translate linq like script to sql as same as you directly write it , then cache and reuse it ?
Informative. Dapper is not tracking the entity as per my knowledge and for EF core it is on by default. What would be the scenario like With Change Tracking , Without Change Tracking, Compiled query and Dapper comparison?
No tracking in EF in the current tests will make it slower. Check my previous vs dapper video for more details
This is very cool feature and in my opinion, that allocated memory is nothing for achieving that performance.
One of my question about this video is: How it fit in specification pattern and DDD?
I think we should put our specifications in infrastructure layer. any idea about that?
Hi Nick, Thanks for the video, can you please make a more complicated example video with joins...
.First is suspect, in the context used by Nick. If you know there will be one result, limit 2 will return the same number of records. If that is enforced by constraint, then the query will typically scan the same row count. If not enforced, then .Single is essential. The specifics are relevant, but don't go replacing all your method calls for performance.
Would be interesting to see Linq2DB performance comparison/walkthrough also.
why should i use Find() for querying with primary keys instead of First/Single?
Why don't you use the EF Core AsNoTracking extension method?
Is this implied since it is a ATO compiled query?
I would personally not put async on methods that just return the Task. Eg the EF_Single_Compiled(). Just skips the whole state machine. Might change the performance slightly.
Would it be sensible to use Lazy or AsyncLazy to defer initialization of the compiled queries until they’re requested the first time?
12:24 "they inflate the memory" by around 3 KB per single cached query? If you have 50 entites and 5 queries for each of those, you basically waste 750 KB of memory that is statically allocated and lives throughout the entire app? My only concern would be the startup time, but even that doesn't seem to hurt enough, it's matter of 2 us per compiled query, so you get 500 us in the startup, although that's a bit of a lie too because we're not seeing how slow the cold start of those query compilers is, before they get JITted and shown in the benchmarks.
Didn't understand how to use asynchronous with this feature. I Try to write something like EF.CompileAsyncQuery((context, id, cancelationToken) => ...ToList()). But cancellation doesn't work (logic - i didn't use it in my lambda). On the other hand i can do EF.CompileQuery((args) => ...ToListAsync(cancellationToken)). But i do not understand how to right
One item you didn't cover is adding "AsNoTracking()" on the EF call. [Pardon if covered in a prior video.] I've found this made a significant different in my EF routines when I know I don't need to do any updates on the returned data.
I covered it in a previous video. It would have made the operation slower here.
I didn't know Dapper, so in comparison. Does Dapper also use static queries like the pre-compile. For me it's make a big difference, if the queries stayed for ever. So for a web-application, not rly useful or better, use it with all respect .
Awesome tips, thanks!
nice, and we need more ef stuff
Would love to see a video on Expressions with EF Core
Is it possible and necessary to use CancellationToken in such requests(Compile Async request)?
Did you forget to set it default asnontracking? There is a good performance boost using that with EF
Asnotracking would make it slower. Check the previous EF vs Dapper video for more details
we can complie it on demond and cache it, even to save memory cache could have an expiration criteria 🙂
In dapper example, you use
result.ToList()
Why not cast?
(List) result
Will it differ performance or memory consumption ?
You can’t cast a type down to something that it is not. It needs the ToList to actually enumerate and build the result
@@nickchapsas This time it's not exactly true. Since you left "buffered" parameter of .QueryAsync(...) as true (it is a default value), the result you get after 'await' is actualy a List.
I have to wonder, but wouldn't a source generator be able to perform the compilation and inline the result at compile time, thus saving on the startup penalty?
I would be interested in seeing if the delegate can be hidden behind an extension method on the DbContext, and what the performance penalty of that would be, and if it can be optimized? I personally feel it's more natural to start off with context.SingleMovieAsync(id) instead of passing the context as a parameter.
My thoughts exactly
Can I implement order by with EF.CompileAsyncQuery ?
Awesome! I didn't know that.
hi Nick, this is the comment you told me to leave down below
I love your videos and you have taught me tons, but this one isn't working for me. In my app, I'm using the unit of work/repository pattern. I created a benchmark app, that pulls in my unit of work to test my linq vs compiled query. My linq query is 2 to 3 times faster and it uses less memory. The query I tested is a simple select with a IN clause in the where. Any idea why?
Can compile be used in cases where the query is built at runtime based on filters chosen by the user li,e in search forms? Thanks in advance!
Wow I didn't know you could do this. I'm thinking whether it's difficult to implement this for when you have a query that may apply certain filters or not depending on the request. Is there a way to chain this compiled queries like returning and IQueryable or it makes no sense?
Why ef just not store cache of compiled functions at background then? it will be much more usefull than write compile for each fync
Are there any drawbacks to using compiled queries besides the issues mentioned in the video?
In web api, would this compiled queries on ejected to the http methods ?
Does these make more sense in simple or complicated queries or does it depend on the frequency they are executed?
I would like to see speed test generic base repository with derived repository for more complex linq, vs pure dbcontext only
Interesting. But still, its microseconds difference using raw sql spaghetti vs strongly typed LINQ. I would pick EF anyday and use raw sql only when it's absolute necessary.
another reason why i wish methods has static fields or something similair
Does EF Team have a plausible solution, looks like yes. My impression is that they like to be always behind. Couldn't they make it behind the scenes?
Have you tried to benchmark FromSqlInterpolated vs Dapper?
Hey, this is not an AsNoTracking query, so it's not a fair comparison since Dapper doesn't have a tracking feature. EF Core should be used with AsNoTracking option enabled, as it can be resource-intensive and use reflection, which can have a heavy impact on performance.
AsNoTracking would have made EF slower. Check the last EF video for more details
It just doesn't make sense to create a compiled query for every query you want as fast as dapper. It just makes the code so much more complicated for other users.
I've been a long time EF Core user, and a recent converter to dapper (3 or so years now)
When I was using EF I used compiled queries, and when I really wanted to make things faster in EF I used stored procs.
If I want to churn out an app fast I use EF Core for Insert, Update, Delete ... and dapper for Read. If I want a performant application with human readable code I use Dapper. ... and if I wanted to annoy everyone I used readers and writers :)
Nice video Nick 😀
Question for you:
Based on my experience with EF Core, the main issue I had was to generate complex reports. When you have to join a lot of tables, do some aggregation, group by and related stuffs. Do you have any good practices to shared with use. Especially when you want to avoid as much as possible stored procedure or plain raw SQL
I found dapper when s complex join i was trying to implement in EF failed to generate correct sql. It was a bug and might have been fixed by now, but if EF fails like this, then you're SOL. The more complex your queries the greater the chance you'll have to write sql anyway. EF is great for the simple stuff.
does dappr not do compiled queries?
Great video Nick! Please keep the EF Core vids coming, they are sooo good!
Is it possible to use it with Add/Remove/Update ?
The one thing that is very unclear - do you use AsNoTracking mode for context or just forgot to add this into benchmark?
As for the memory you carry around with the application in the case of the compiled query, shouldn't be considered.
In essence you make compiled quries for actions that you perform often which means it is a lot memory you don't have to now garbage collect multiple times. In an API scenario the query will be called multiple times per second meaning the memory footprint is way better.
If you call it once per day then bohoo right?
Is there something similar for MongoDB?
Please make a video about more complicated scenarios, Nick
More Complex scenario please :)
Thanks a lot 🎉❤
Great video, thanks
So if you turn off tracking in EF, which is something Dapper hasn't even heard a rumour of, how does it compare?
EF would be slower with no tracking. Check the previous video for more details
May you try to just add MemoryCache? If I remember well, if EF founds MemoryCache in DI, it will use it to cache the compiled query (so you shoudn't use pre-compiled functions).
What’s the perf like if you do .AsNoTracking()?
Worse
GREATE VIDEO , PLEASE MAKE A VIDEO DEEP DIVE INTO COMPILED QUERY