Entity Framework Core vs Dapper Performance in 2023

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody I'm Nick and in this video we're going to take a very close and detailed look between the performance of anti-famil core and Dapper there's a lot of chatter around EF core being super fast insanely memory efficient and as good as dupper however I want to take a very scientific approach and isolate all those claims and see if that is actually really the case because there's a lot of blogs and videos on the matter and the vast majority if not all of them try to get the answer but they do it in a wrong way so in this video I'm going to do my best to give you the most accurate answer in how does ef core compare to Dapper in terms of performance if you lack of content and you want to see more make sure you subscriber in the simplification bell and for more training check out nickjapses.com now before I move on I want to make something very clear I use both EF core and Dapper and I actually really like ef4 and upper arguably I use Dapper more because I'm leaning more towards the performance side of things however I'm totally on board with the value proposition of EF core and I really really like the product I want to make it very clear so let me show you what I have here in terms of Benchmark code and I actually have quite a lot of stuff now we are going to be using sqlite for the purposes of this Benchmark why well because we want to call anti framework directly in the database directly but we don't want to have to deal with an in-memory database because we want some query conversion to actually happen so the best next thing that is not a full-fledged database engine that can be a bottleneck is sqlite and the great thing about sqlite on my machine and the drive that this project is on is that I'm running a pcie Gen 4 SSD which can do up to 5 gigabytes per second so write and read speeds will not be a bottleneck for what we're going to be seeing here now the object we're going to be dealing with is this very simple movie Object so no nested stuff no nothing this is intentionally kept slim to see what is the impact of adding the library so I have a good as an ID which is a bit more complex in conversion I have the title and I have the year of release and then I have of this movies.db generated by these migrations over here which both Dapper and EF core will be using and as you can see this is a table generated I will be using this for my benchmarks and then I have my movies context the DB context which loads the file directly here from the path and it's using sqlite and then I also have a movie generator which is using bogus which allows me to generate many fake movies deterministically by passing a seed meaning that every time the benchmarks run for Dapper and EF core it's going to generate the exact same sequence of movies meaning that between every run the same data set will be generated so we're going to have a very consistent result then in my benchmarks class which is where my benchmarks will be I have the movie context at DB connection which is generated using this Factory over here very simple stuff we'll just get an sqlite connection to that sqlite database and then we have some setup we have a random class which is using a seed to make sure we get the same results over and over again per execution then we're generating 100 movies and we're stating the day database I will explain why this is not higher later and then we just generate a test movie which we will be updating on the update Benchmark then I'm just inserting it and I'm generating my movies context and that is it in the end I'm just cleaning up all the generated data and that single movie generated to be updated that's all there is to it now we're going to start this Benchmark by getting something by its ID or in this case the primary key and there's really three ways to do this in the F core you have a find where you can use find async and pass the key and that will actually match it with the primary key then you have single or default async and you just have the predicate and then you have first or default async and you have it pretty good now a few notes Here why do I have both and some people will say hey you should just use single or default here is the thing about that the query that find async will generate looks like this where it's going to have a limit one and once it finds one match it is going to return the item back this is exactly the same thing that the first will do because we said we just want the first however if you use single or default even though it makes more sense conceptually to use that you're going to have a limit too because single means one and default can be nothing or more than one so if you use this in a column that doesn't have an index and it's a large data set singular default can actually be slower so keep that in mind I'm going to show you both in this video but keep that in your mind when you're working on your own code you might want to use first or default not single or default even though conceptually singular default makes more sense and then in terms of dapper what you're gonna have is something like this where we have double Gateway ID we have a SQL query here much by ID and we pass the movie ID down now a very important point because the movie ID here is actually a property on the movie Object I could simply here just pass the movie however if I do that I kinda cheat and that will actually save me the new Anonymous object allocation making this a bit more memory efficient however I'm not going to do that because it's going to be very unlikely that you're going to have a whole object with the property ID when you write this query so for that reason I'm going to just allocate a new object to give it a fair shot everything is in release mode my program.cs has my benchmarks so I'm going to go ahead and see how all of these approaches compare alright so results are back and let's see what we have here there's actually quite a bit going on here let's start from the extremely obvious thing find is stupidly efficient both in memory and in speed this is because if you use find the value will actually be tracked and then return from memory so we can't really trust these results as a novel to Apple because this doesn't actually go to the database to get it it just keeps it in memory and it returns it over and over again so we can ignore this but if you want to get something by key you should be using fine that's a no-brainer now for every other single or get by ID let's see what we have below so single and first in EF virtually the same in this case because the data set is so small but quite a bit of memory 10.2 kilobytes of memory for that query in comparison doubled the same work at 7 microseconds while this took 48 and that is on the primary key that is quite a lot it is around five times the memory and many times slower to use EF core for that type of word of course this is nullified if you can use find but if you want to filter by any other property that is not the key that's what you realistically should expect so double clearly wins that now I want to point something out because we do have quite a bit of memory here and I was wondering in the beginning can we give EF core an even better shot to make it be a bit more efficient in terms of the database connection and I was thinking that even though technically this is not how you are going to use this in your own applications I want to actually reuse the same Connection in my DB context and not having to have a separate connection from the Dapper stuff and the EF core stuff again the reason I'm doing that is to isolate the workload so I can only compare the operation itself not in connection overhead I can do that by injecting an idb connection to the Constructor and then using it here instead of using the sqlite file path or database path so I can just pass that down here I'm going to have to cast to a DB connection it doesn't matter and now I can use that and to use it in my benchmarks all I'm going to do is pass down the DB connection that is it I'm going to copy these results over here and I'm going to paste them in this results dot markdown file which you can grab from the description if you want so get by ID own connection that's what we got now let's see what we're going to get by reusing that single connection for everything and what sort of overhead that has let's go ahead and just run this so let's see where we are now with that reusing of the connection so as we can see over here I'm going to just click that file so we can compare we are way more memory efficient now on single and first by reusing that database connection and that's how I'm going to go forward and we're also Faster by the way just to try to get as close to the actual querying and add and removing workload as possible now some of you might be wondering hey Nick you can make this even better if you actually use as no tracking all right you got me let's do that as well so now astronaut tracking is a whole different discussion but just so there's no ambiguity in whether you should be using it or not I'm just going to add it you cannot add the infined of course because uh find will track it that's why it's returned in memory uh in the first place I'm going to add it here and just rerun it and see again how they compare all right and let's see how that compares so as you can see it is a bit slower and a bit more memory inefficient why this happens is a whole different discussion I don't want to stand too much on this if you want to see an in-depth analysis to why this happens leave a comment down below and I'm happy to do that going forward I'm going to remove as no tracking now the next thing I want to take a look at is add or create or insert now here's an interesting thing about benchmark.net even though there is an iteration cleanup step it is very tricky to use reliable family so what I'm going to do is actually merge add and delete in the same Benchmark and then you can extrapolate how much add and delete is based on your use case again you can get the code from the description and run this in any way you want the reason why I don't just let this just add everything to Infinity as the benchmarks are running is because sqlite will actually get exponentially slower the more data you add into it so we're gonna get the most reliable example by actually creating and deleting on the same Benchmark this is the best way by far to get a reliable result on this execution so I'm going to add the benchmarks here I'm just creating this movie Object so the same movie over and over again I'm just adding it saving and then removing and saving to let those operations execute independent of each other because if I just had a single save they both can happen on the same execution and nothing will really happen and then for Dapper I have the same thing execute async and execute async for insert and delete and again I'm not passing down the whole movie Object even though I could I'm passing down a new Anonymous object to give EF a fair shot so with that in place let's see how add and delete actually performs all right results are back so as we can see here and that's very interesting add and delete is extremely close of course we have two operations pushed together so we don't know how much each individual thing performs but combined you have 1.1 to 1.2 milliseconds now the interesting thing about this is that we actually have quite a bit more memory allocated on the EF side of things 20 kilobytes almost compared to four so it is not ideal and this remember you are going to see in those garbage collections but if you care about speed add and delete combined very close in both scenarios now again please understand that this is not ideal to have both a delete and add in the same operation but that is the best I could do with that tooling to get reliable results and next of course we have update so EF update we're updating the year of release passing down the movement and save changes and in Dapper we have the same thing again determines random update the movie pass down the movie and then just return the thing so let's go ahead and see how that performs okay so EF update 600 microseconds and Dapper update 555 microseconds which is pretty close however again in the memory front 10 kilobytes here 2.5 kilobytes here EF just seems to be behind in memory however right from what we've seen until now are extremely close which is very impressive and very interesting because what I've generally used EF core 4 is actually right it is to generate those complicated queries that I don't want to have to write myself so it is fantastic to see that on the right front we are very close on the read we are not but for me that's fine because I can use a hybrid situation where for my right I use EF core which is what I'm doing and for my reads I'm using Dapper having The Best of Both Worlds now the last thing I want to show is actually some filtering so I want to return a list of movies that the year of release is 9 1993 so that's how you do it in EF and that's how you do it using Dapper let's go ahead and just run this and see how the two compare again all right so results are back and let's see where we are so EF filter 34 microseconds Dapper filter 13 so less than half and again in memory less than half Dapper seems to be still ahead of the curve both on the PK or just general filtering which doesn't mean that double is slow by no means it's still very efficient and very fast and if you want to use EF core for everything you totally can this is not prohibitive at all but there is still work to do on that memory front now I've updated this file with all the benchmarks again and I have all the results here so let's just quickly recap with its own connection we thought we're around 48 microseconds compared to 7 to get something by its ID in this case the PK and quite a lot more memory inefficient however the moment we started using a shared connection that memory footprint decreased and the execution speed decreased as well and again at this point we're in a better situation to see how that to compare when we added as no tracking we saw a bit of a degradation in speed and memory again leave a comment down below if you want to see an analysis to why that happened in this case and then on ADD and delete very close on the speed front not so much on the memory but it really depends to what you care for in terms of performance which can mean different things for different people but it is impressive that it can actually keep up with both add delete and update even though the memory footprint is way way higher using EF core and then filtering not fantastic it is twice the speed and twice the memory to use EF for your filter but what do you think about all this did you expect it to be as good as it is nowadays and are you using it leave a comment down below and let me know well that's all I had for you for this video thank you very much for watching special effects to my patreons making videos possible if you want to support personally you're going to find the link description down below leave a like if you like this video subscribe more gonna like listening the Bell as well and I'll see you in the next video keep coding
Info
Channel: Nick Chapsas
Views: 90,926
Rating: undefined out of 5
Keywords: Elfocrash, elfo, coding, .netcore, dot net, core, C#, how to code, tutorial, development, software engineering, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, dotnet, .net, .net 7, ef core vs dapper, ef core, dapper, entity framework core, entity framework, entity framework vs dapper, entity framework core vs dapper, ef core vs dapper performance, entity framework vs dapper performance
Id: Q4LtKa_HTHU
Channel Id: undefined
Length: 13min 58sec (838 seconds)
Published: Fri Mar 24 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.