BrandGhost

Forget Entity Framework Core! - How to Use Dapper and Strongly Typed IDs

To be clear, there's nothing wrong with using Entity Framework Core. In fact, it's the popular choice for CSharp developers! However, EF Core is now how I like to write my own code. It's just personal preference since I like having the SQL queries in front of me when I'm developing my data access patterns. Dapper, on the other hand, has been great to use! It perfectly fits my needs. When we combine this with Andrew Lock's StronglyTypedId Nuget package, we can get some great repository APIs to work with. But how can we expand upon the suggested way to integrate Strongly Typed IDs from this package with Dapper? Let's find out!
View Transcript
I've had a habit of writing all of my own SQL queries for years now when it comes to making my own applications and I know there's things like Entity framework core around but I just can't seem to get in the habit of using them hi my name is Nick centino and I'm a principal software engineering manager at Microsoft in this video we're going to look at Dapper instead of Entity framework core and alongside that we're going to be looking at the strongly typed ID library from Andrew Lock I've only recently started using Dapper but I've absolutely fallen in love with it because it fits the way that I like to write code perfectly in this video I want to walk through how we can leverage strongly typed IDs alongside Dapper and a couple of little tweaks and changes that I've done to what Andrew log has suggested in terms of being able to make these things work together a quick reminder to subscribe to the channel and check the pin comment below for my courses on dome train now let's jump over to visual studio and check this out all right on my screen I have something that's a very simple repository pattern along with a record right so this record is just going to have an ID you can see that it's a long type and the value is a string type here and the repository itself like I said it's going to be very very simple it is going to be using MySQL in the backing for this but it's not really important for this conversation so Dapper can interface with different databases depending on how you want to connect to things all that I'm doing here is I have a create method you can see that I have to write the SQL for it and again as I mentioned at the beginning of this video this is really a habit that I've had for a long time I like writing my own sequel I don't want to have any Mysteries with what's going on behind the scenes and for me I prefer it this way to each their own I know a lot of people love using edity framework core and that's totally cool too I'm not here to convince you otherwise I'm just walking you through what I got going on what we're able to do with Dapper is simply take the record that's passed in and you can see that on the parameters parameter here that I'm passing in record and that's just going to mean that it's able to map the record we're passing in into these value arguments that we have here this way we don't have to go manually add the properties and map them onto different command parameters that we would pass over to SQL it's just all handled behind the scenes for us so in my opinion this is one step more clean if you will than what I would do on my own cuz otherwise for every type of repository I'd be creating I'd end up going to do this myself so just a little bit more streamlined if we go a little bit lower we can see I just have another method that kind of does the opposite right so I'm going to go look up a record by the ID that we pass in and this actually should say long for now because we're going to get into the strongly typed IDs in just a moment but for now if you were to go look this stuff up you'd be able to go find by the ID a certain record and Dapper again kind of saves the day for us because it will go translate you can see I'm uh aliasing these here right so the ID to Capital I then D and then value with a capital v on it doing that allows these to map to the record Rec properties that we have and as a result Dapper knows how to map those so we pass in the ID this time only because that's the only parameter we need to go into the SQL query and then we get the record out but that's going to bring us over to this ID here right so you saw me change this and if we go back up and we look at the structure of our record we can see that the record itself has a long ID on it now I want to pause for a second because some people may not be familiar with strongly typed IDs and I kind of just want to explain this very briefly before continuing on especially if you're new to it if you're not new to it you can skip ahead a little bit but this is just my little preamble to give people enough context if they're not familiar so the idea with strongly typed IDs is that if you don't have them and we have other types of records and things like that so maybe there's a handful of different entities that we're dealing with so you might have something that's you know better named than my record but you have these other repositories and stuff like that they all have their own ID right maybe you have customers and products or you have services and you have different types of things that you have in your database if they all have different IDs but they're all Longs let's say it means that when you're writing code it's very easy for you to mess up and pass the wrong ID in place it's kind of like if you had a system where you could pass an email around or a phone number or a username and they're all strings what ends up happening if you pass the wrong one right like you're not going to catch it at compile time because they're all strings and as a result you have a bug that shows up lat later and you're going oh crap I passed the wrong string the same thing can happen and it happens a lot when you have really simple ID types strongly typed IDs allow us to work around this but the challenge is that it's kind of silly or it feels like it's a lot of boilerplate code to have to go write a whole type just to go replace something like a long and then you have to go do it X number of times for all of the different IDs that you have so Andrew Lock has an awesome library that we can use to work around that and if I go expand this code this is all that we need to do to coake our own strongly typed ID so you can see here that I have strongly typed ID as an attribute and then we can pass in the template that's long because I wanted to have a long type backing my strongly typed ID and then I just give it a name the important thing to note here is that this has to be marked as partial and that way that the code generator can go generate the code for this struct that we have and that's kind of behind the scenes but this allows us to get that code generated through rosin now from there what we're able to do is take this and we can replace the ID that we have on our record which is great because now when you go to create this record you do have to use a my record ID that's specific type and not just a long so if we go back around here that means if we wanted to go look up this ID we could change our signature of this method and say look if you're trying to find these things by their ID you need to give us the specific type so this just really enhances the type safety in our system a lot of the time I've shied away from this just because I didn't have a really good solution I had heard about Andrew Lock strongly typed ID package that he has and I figured okay it's time to use it for this particular project so it's been a really cool experience so far using Dapper alongside this but the problem is that as soon as you go to do this if we don't make any other changes all of a sudden Dapper does not know what to do when it goes to read this object back in and that's because it doesn't know it doesn't have any idea what this type is if we go back up here it doesn't know what my record ID is it knows about the simple types we have so if you have a string or a long or a date time because these things translate well into database Concepts that we have but my record ID is not a concept that exists in Andrew Lock's blog that he has he shows that you can use a SQL mapper in Dapper to basically tell Dapper when you encounter something like this we know how to go translate it but while this works perfectly fine one of the things that I'm not a big fan of is that we would need one of these mappers for every single ID type that we want to have so that means if we had a system with 10 different entities for example and each one had an ID even if they were all based on a long type it would mean that we would need 10 different instances or variations even of these mappers to have for each one of these types and because I use a lot of plug-in style things this kind kind of goes against a lot of the design philosophies I have I kind of want this stuff to happen more automatically instead of forcing people to remember to go do it so again nothing wrong with what Andrew Lock has suggested I think it's great that he has these things working together but I wanted to enhance it a little bit more so I want to show you that before continuing on just a quick note from this video sponsor which is packed publishing packed has plenty of awesome cpen net books in particular I have this one here architecting asp.net Cor applications and I actually have a forward in this book which is really cool so in this book you can learn all about building asp.net core applications and in addition to that you can see how you can leverage design patterns in those applications and learn how to test all included in the book with plenty of examples so I highly recommend you check it out and you can find the links in the description of the comments below thanks and back to the video okay so this is going to be the first version of this strong type Dapper mapper sounds pretty cool right that I'm going to walk you through this does use some reflection and assembly scanning so I'm not necessarily suggesting that you have to do it this way but this is how I've set it up in my own application I just want you to kind of think about the concepts that are going on here and how you could apply it in your own code again because I leverage a lot of plugins I need to be able to scan through assemblies to be able to make some of this magic happen so what I'm going to be doing is looking across the assemblies getting the types out of it and then looking for anything that has one of these attributes on it this generated code attribute right and then in particular I need to look for strongly typed ID there's probably potentially a better way to go look up types based on this but when I was doing a little bit of poking around and basically investigating through reflection how these things get mapped what you're not seeing at runtime is anything to do with this like this strongly typed ID this does not show up at runtime but instead if I go all the way back up here we do get this one that says generated code attribute but the tool property that we have on there is called strongly typed ID if we want to go find the types that are strongly typed IDs this code is what handles that now that we've identified one of those types and by the way I'm just going to be returning a tupple back that has three things in it so this is going to be something that I filter out by the way so once I have a matching type if we basically don't return out I'm going to ask for the Constructor and then I'm going to ask for the parameter that gets passed into that Constructor and from there I'm going to return back the actual type right so that's the strong type that we're dealing with the strong type ID then I'm going to get the type that is backing that strong type and then I'm going to ask for the Constructor these are three things that I'm going to need to be able to create instances of these strong types I'm going to pause for just a second because if you're seeing the reflection stuff going on and going hold on this doesn't seem like it's going to work so well bear with me for a second because there's an enhancement to this that I want to show you however it's not totally working yet but I want to seed the idea with you if you stick to the end of this videoos we do need these three pieces of data and then what I'm going to do from there like I said I'm going to filter out when we don't have a proper match and then I'm going to step through all of these pairs to do some registration and that way I don't have to go make a whole new mapper for each one of the things that goes and implements a strongly typed ID backed by a long in this case for now I would have to go will make it if you had things that were mapped by strings or ins and other variations but you would only need one of these per the better solution that I want to implement only needs one of these you don't have to do it for each backing type but stay tuned cuz we'll get to that in a moment basically what I'm doing is I'm just checking to make sure for now cuz in my case I'm only doing this for Longs so if anyone accidentally set this up not for Longs for their strongly typed IDs this is going to throw an exception we'll know pretty early if something's broken then what I'm to do from there is get some types going on here so this is a bunch of reflection so this is going to allow us to make a generic type and from there we end up calling these two things together to get the class right so the type that represents the generic type it's a little bit meta but bear with me for a little bit what we need to do from there is uh get the method that's going to allow us to access the property through reflection so there is a bunch of reflection going on here but we're going to Cache these things so that we don't have to keep looking them up now from there one more little bit of reflection I'm going to use activator to create the instance of this mapper which we're going to see below and keep in mind this stuff is only happening on Startup once so yes there is going to be a little bit of reflection later on and that will be a performance consideration but if you bear with me a little bit longer the other way that I want to do this and it's not fully working should hopefully work around some of these performance considerations this is essentially all this here is going to set an instance of a squl mapper for these strongly typed IDs that should be able to handle representing a strongly typed ID going to and from a long to go look at one of those implementations it's actually very simple this is basically borrowed right from Andrew Lock's example it's just that I've made it a little bit more generic so the way that we create the things and the way that we serialize those things basically putting them into SQL that's going to be more generic so you'll notice in here this says nothing about my record ID right that strongly typed ID that I created we don't see that anywhere here because this implementation should be able to support any of them that we create if you're paying attention to the type parameters you can see the long and long here there's a long down here and where I wanted to go with this was like Hey look if I have another type parameter I should be able to extend this even further in theory I probably can do that for this one and make it more generic for things like strings and ins and date times like I said I'm only using Longs but we're almost at the point where I'm going to show you what should be the much faster version I just kind of want to connect the dots first and then we're going to go look at that variation what we have if we put it all together is we have some assembly scanning it's going to look for all the strongly typed IDs this for each Loop is going to say for each one of those strongly typed IDs that is mapping to a long we're going to go set up one of these SQL mappers one of these handlers right in place for Dapper and it's just going to create an instance of one of these and it should be able to handle any one of our strongly typed IDs that's backed by along so a whole bunch of code but that means that we only have to basically create these things and as long as we if I scroll back up as long as we just call this map handling for assemblies it will go scan through all the assemblies and create all the necessary mappers as long as we have these defined so we never have to go worry about creating more strongly typed IDs and going oh crap did I register that because if you don't register it it's going to blow up for you at execution time this was my Approach for basically extending what Andrew log had to make it a little bit more Dynamic and allow you to add stuff without having to worry about registering it if we go look at how this stuff is getting mapped and handled you'll see that I have to call this create call back when we want to go parse it so we're reading in the stuff from SQL from Dapper right we have to go call this create call back and if we see what we were doing I basically have to go use some reflection and call that Constructor that means it's not so great right it has the Constructor info I do have some benchmarks in a video that talk about this calling the Constructor this way through reflection is definitely quicker than using activator create instance but it's still slower than just KN something up especially if it's just a simple struct like this is going to have some overhead for my current situation it's not an issue I don't have to worry about that again my specific use case is that the execution time that this will take not a big deal I'm not trying to do a ton of things very fast in bulk I'll be fine for my use case however I wanted to be able to walk through how we could make this more performant because I want to be able to use this approach probably going forward in systems where this is really going to matter for performance and if I have to use any type of reflection like this in something that's performance critical probably not going to be a good time and the same thing happens when we try to go the other way when we try to write to SQL we basically have to use reflection again the method is cached here so it's faster but we still have to use reflection in order to get the value off that strongly typed ID definitely not ideal so let's go look at the other way that I wanted to try handling this and I'm going to explain why it's not working and hopefully when it should start working okay on my screen I have one more class that looks very similar to what we just saw but there are some differences that should make a tremendous difference when it comes to Performance and that's going to be using unsafe accessor the idea here is that instead of using reflection in the traditional way we're going to use unsafe accessor and that's going to allow us to go access these things without having to use reflection at runtime if you dig up performance benchmarks on these compared to using reflection they're significantly faster basically they're comparable to Native access when you're not using any reflection at all if you think about it what I'm trying to do this is create type instance that should be replacing the Constructor info call and get value should be replacing the other reflection call I have that's essentially getting the long value you off of our strongly typed ID so these two things basically replace the reflection calls that I have if you look at the parse and the set value methods they're almost identical I'm just calling these two other methods we're talking about instead of using reflection but the problem here is when I tried to make this so generic is that these two things don't yet work with generics I believe that I read this is coming in net 9 depending on when you're watching this video that might already be the case maybe this this code will work but at the current time of recording this video this is with net 8 and net 8 has these available but they don't work with generics they look like they work right now but when you call them it basically says that you're not using this the proper way doesn't give you a lot of detail but when I started searching around for anyone else running into that it seems to come down to generics what I'm hoping is that I can live with this slower approach for a little bit longer because again right now it's not a performance bottleneck for me I'm not too concerned but I would like to follow up hopefully when these things are working with generics because you can see that I am using a tight parameter on them right if that is fixed in net 9 I will try to follow up with a video on that to show you and I'll even try to Benchmark it and show you the difference cuz I think it should be pretty significant when you're dealing with stuff that's reading in tons of data or writing tons of data that's going to wrap it up for this video so if you want to see other types of reflection benchmarks you can check out this video next the thanks and I'll see you next time

Frequently Asked Questions

What is Dapper and how does it compare to Entity Framework Core?

Dapper is a lightweight ORM that allows me to write my own SQL queries directly, which I prefer over using Entity Framework Core. While Entity Framework Core abstracts a lot of the database interactions, I like the control and clarity that comes with writing my own SQL.

What are strongly typed IDs and why should I use them?

Strongly typed IDs help prevent bugs by ensuring that I use the correct ID type for different entities. Instead of passing around simple types like longs or strings, I can create specific types for each ID, which enhances type safety and reduces the risk of passing the wrong ID.

How can I make Dapper work with strongly typed IDs?

To make Dapper work with strongly typed IDs, I use a library from Andrew Lock that allows me to define these IDs with a specific type. I also implement a SQL mapper that helps Dapper understand how to translate these strongly typed IDs to and from the database.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload