Setup Generic Unit of Work in ASP.NET Core API | Ultimate ASP.NET Web API Tutorial For Beginners

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] welcome back so nowhere on the point of creating our repositories and what we'll be implementing is a development pattern really so let me just give some background as to why we need to go through that when we're doing our work with our api it's easy enough to just write the queries using entity framework core and link and we just write it especially like when they have two tables contending with which are country and hotel but what happens when we have eight nine ten twenty tables it becomes kind of tedious to try and write queries every time we have a table and then that's going to lead to repetition and then if we have the same query multiple places then that's a maintenance nightmare so that's why we're going to be implementing a pattern and we'll be using the generic repository pattern alongside the unit of work pattern to kind of create a container to define basic functionality that will be shared across all our tables and all our database operations in our project so to get this started i'm going to create two new folders one will be called i i repository and the other one will be called repository right so we have i repository and that's going to be a capital i and then we're going to have another one that i'm just going to call repository so there's a concept called separation of concerns where you want to make sure that your every file knows that it is doing one thing so that's why we made sure to define two different class files for this we're going to define some amount of individual classes along the way but then we want to make sure that certain things are generic and we're not repeating certain things right so in our i repository we're going to create an interface so i'm going to create a class because that's just quick but i'm going to call it i generic repository and go ahead and click add and i'm just going to change this all to interface so if you chose interface that would be fine the only thing is that you have to make sure remember to put on public right so that's the only reason i really choose class because i've forgotten to put on public in the past and i couldn't understand why i was getting the area i was getting so that's my little fail-safe based on my experience so what we're going to do is make this generic repository take a generic parameter and in the form of t where t is going to be a class all right so when we talk about generics this t just says well i'm prepared to take any type of class that you send my way and that helps us in creating those base operations for different situations with different class types all right so the first one that we're going to create is a function called get or well get all right so i'm going to say task i list of type t get all right and then i'm going to be defining certain things so let me just explain exactly what is going on in this function header so task when we're dealing with asynchronous programming we have to use tasks if you're getting an arrow it's probably because you don't have that library that i have highlighted there at which point you know you just do control dot and go ahead and include the using statements so task i list of type t once again generic and the function name is get all so get all has a parameter which for which i'm going to include the missing reference which is of type expression function and expression and we're making it an optional parameter it's also going to take an iqueryable i queryable of type t i ordered queryable right so you can just rewrite that how i have it and order by that's also optional and then we have the ability to include if we need to all right then i'm going to have a follow-up function for get so task t get so this one is going to get a list this one is just getting one all right so that's why it's task t and this one is task i list so this one is just going to get one record and it's taking the same expression parameter and it's going to have the include so when we're fleshing out the the actual function then you'll understand what the parameters really stand for we're going to have similar functions for our crud operations so i'm going to have task insert t entity so this is going to be the one to create we're going to have insert range so i like to keep this one nearby because sometimes you have bulk operations and instead of calling this multiple times you could just define one that takes a list and it just does the rest anyway um we have delete and i also have delete range all right and update i don't like doing the update range really but we have the update so notice that the task the delete is task all of these are tasks but these two are void operations all right so that is it for our generic repository so once again this is being defined as like a base catch-all no matter what the data type if we introduce five more tables and and accompanying domain classes we don't really have to change much here because these functions will be defined to handle any other table or domain class that gets thrown at it so now that we have the interface i'm going to go ahead and create the concrete class i'm going to go over to repository add class and this time it's an actual class and i'm calling it generic repository so i have i generic repository and now i have just generic repository and generic repository is going to take the same generic t but it's also going to inherit from i generic repository where t is a class so i'm just going to go ahead and include the missing reference right there and then it's going to complain now because once we're inheriting we have to make sure we implement so i'm just going to go ahead and implement the interface and then it generates all of those method stubs for me also kindly so let us flesh out exactly what we need here so we're going to have to do some amount of dependency injection so i've mentioned that if i have before i haven't mentioned it much but dependency injection is basically the concept by which whatever we loaded up in the startup is now available application wide so we don't need to instantiate every time that we need it all we do is make reference to the already existing object because it was defined in our startup right so more contextually our connection or our bridge to the database we can now just get a copy of that to use in our file here instead of trying to create a whole new bridge or a whole new instance of this bridge we can just leverage the one that exists as a result of it being included in the startup procedure all right that's that's in a nutshell what dependency injection really helps us to do so what we're going to do is define a private read only it's private it's read only because we don't need to make any modifications to it and it's going to be an instance of data base context all right include any missing references there we go private read only and i'm just going to call it underscore context all right and then similarly i'll just duplicate this line because i want to private and read only i want db set remember db set coming over from the same database context and it's going to be defined for a generic so i'll just go ahead and include the missing reference there also eb set of type t and i'm going to call this one db now these are defined for this class however dependency injection now will require me to create a constructor and it's going to take a parameter of type database context and i'm going to change the name so the private one is underscore this one does not and then having taken that parameter it is now able it's not accessing the one or the copy from the startup procedure so for my local use i'm going to initialize my current context that i have as my private read only to be the same value as the one being injected in so that's pretty much dependency injection in case that was a mystery to you up until now that is how it basically works there are other nuances to it but for no it doesn't need to get much more complicated than that so the next one is underscore db is going to be equal to context now that i've initialized context i can say context give me a set of whatever t is and remember that at this point t needs to basically correspond with something that has been outlined in our db set in our database context all right so that is why i can confidently say go to the context file and give me a set of whatever this is because the context file should know about it by the time we're building this repository so now that we have that part done let us go on to our implementation so let's do the delete which is fairly easy all i'm going to say is var entity is equal to and then i'll await so because we're doing asynchronous programming after await so i have task here but what i'm missing is the async keyword before it all right anytime you have a task that you're going to be using await you have to have async all right notice when you click one the other one gets highlighted they go hand in hand all right so we say await underscore db dot find and i'm just going to find it find async so in.net core sorry in entity framework or for almost every operation you have a corresponding async operation so at this point it's there use it and if you're using async you have to precede it with a weight and by extension the method in which this awaits keyword is being used needs to have async on the method stop so it's not in the interface it's not in the interface but in the definition you have to have that as you write it more and more you'll get the hang of it so we'll find async and we'll pass in the id for the record that we want to find and then we'll say db dot remove and we're removing the entity that has been found with that id and that's it for delete now delete range is pretty much one line it's just going to be db dot remove range so we have a list of entities so i'm going to say db dot remove range and remove rain says well give me the list of entities well there is your list of entities just remove them right that's it for the remove range so i'm going to go ahead and kind of go through the easier ones so insert is easy insert range is easy and update is easy and then we'll go back to the get and get all because those can get a bit more complicated so i'm just going to prepend each task in the method header with the keyword async that one is void so i don't need async there just to make sure that i don't run into any difficulties later on all right so for insert that one is fairly simple all we're going to say is await db dot add async and we add the entity whatever came over as data add it that's all we need to do right and then similarly for the insert range all we're going to do is db dot add range async so this one has an async notice remove range did not have an async all right so add range async and the list of entities that you got to add and then for our update we have a two-part operation one i'm going to attach the entity to the db so in other words when the data comes over it might not be attached or it might be an object that exists in memory with no direct connection to the database at that point in time so attach means pay attention to this and check if you have it already check if there is any difference between it and what you have in the database because i mean we're about to do an update right so then it starts tracking to see that okay there are certain fields that are different so these are two different um let's say records right and then the next line would say context dot entry and then we reference entity that state is equal to modified so once we tell it that okay it has been modified then it will know that okay i need to do an update to it so let's go up to our get so get is actually easier to do than get all but once you do get then get all will make a bit more sense so the first thing that i'm going to do is get a an iqueryable i'm going to call it query of what's in the db so remember db is already defined to be whatever it is data type that we're dealing with right so just get all of the records basically that are in that table pretty much that's what this is going to do and then i'm going to check to see if there was an includes right it was there and includes requirement meaning did the user or did the calling code whoever called this get method would they like to include additional details so i did say earlier that when it comes to hotel and country instead of looking for the hotel and then getting the country id and then calling the database again to get the country with that id and then that's two database because we could make one database call and we're getting the hotel and including the country so that it will automatically fill this property with all the corresponding countries details right so that's what that includes is for so this code snippet basically says that if includes is not equal to null so by default it's not it's optional you don't have to put a value if however you choose to put a value if it is not equal to null then for each property in includes i want to include to the query whatever property was asked for so they said country then this forage loop is going to run once if you have five foreign keys and you put all five of them in that includes this then this is going to run five times including each one pretty much that's all it's going to do all right so it's completely optional because there might be times when you need to include and there are times when they don't need to for speed purposes you don't want to include all the time for one query for one bit of information when you didn't have to so this helps us to keep the application kind of quicker than it would normally be if it included everything all the time then the next thing we want to do is return await query dot and then i'm going to say as no tracking so that's where that tracking thing comes in because any record that is retrieved here is not being tracked it a copy is taken and it's sent into memory sent over to the client the database and entity framework don't really care about it so that's why down here we say attach it so we get that record which wasn't being tracked or being looked at and we say start looking at it and just note that it has been modified by the time it reaches here it has been modified so we get as no tracking but then after that what i really want to do is get the first or default record and there's a first or default async there we go and then i can say expression so let me explain what expression is so expression is defined as expression function t boolean and that's the name of the parameter so this data type basically allows us to put in a lambda expression because then a lambda expression would allow us to say something like q and then the lambda arrow and then would say query dot something is equal to something else so that's why it said bool right whatever condition so when we want to retrieve the one record what is the condition that we want to use to retrieve the one record is it by id is it by name so this allows it to be very generic and flexible because then we can write our different expressions based on the context but calling one method at any given time all right so that's it for the next for the get sorry so the next one that we have to do is the get all right and get all is going to have all of that code with some tweaks along the way so firstly we're not going to be looking at any first or default so let me just modify this error line we get the query that has no tracking and then i'm just going to make it go to list async all right so that's why we have that await once you're calling an asynchronous function you have to precede it with a weight all right so we're returning a list so we parse it to list as opposed to this one where we just got the first our defaults now we have the same parameter for the includes but we have another one for order by so i'm going to put another little snippet right after well actually i'm going to put two snippets in so firstly we got the query then i'm going to check if there was an expression so i'm going to filter the query first before i even look at the includes right so i'm going to say if the expression is not equal to not because we could be looking for a list of records but we want to see where the country is one understand give me the list of hotels from jamaica so we could put in the expression to say where whatever condition is needed to specify from jamaica and then if it is not even if it is not equal to null meaning condition is there then filter the query for me please so query is equal to the initial query which was all records dot where that expression is true then after you've done that because you could have had 500 and now the filter brought it on to 20 then you go ahead and put on the includes all right and then finally after you put on the includes and everything i would like you to order if necessary so if the person passed in an order by oh i want it in descending ascending or this or that you just say if it if the command came in then query is equal to order by query all right so that's really all there is to the get and get all so we've gotten the majority of the work out of the way we have about two more steps to go and then we are done with this task so the next thing that we want to do is create that well i'm going to call it unit of work right so we have the generic repository now we're going to give it the unit of work framework on top so in irepository i'm just going to go ahead and add what will be another interface so i'm just adding it and i'm calling it i unit of work changing this to interface and this one is going to inherit from i disposable all right and then what we're going to be defining in our unit of work would be it's going to act like a register for every variation of the generic repository relative to the class t right so right now we only have two tables to cater for so i'm just going to say i generic repository for country sorry for country and include any missing reference and i'm just going to call it countries so different people have different naming conventions here i'm i call it countries some people would say country repository or whatever i'm calling it countries because when i call on the unit of work i can say this dot countries dot whatever operation i want to carry out right so we're going to do that also for hotels or hotel and i'm calling the property hotels and then we're going to have one more operation which is a task to save all right so when we're carrying out all of these operations to add to updates we're making changes to the context but at this point it's just staged right so when we say save or save changes then it will be effected so that is what this is for but this is outside of the repository because if there are multiple changes to be made at a time then all of them can be caught in one operation as opposed to making multiple calls to the database so we have that unit of work the next thing would be to create the concrete class so in repository go add class call it unit of work and then unit of work is going to inherit from a unit of work and once again include any missing references and then implement the interface all right so then what this is really just going to do now is say all right um you know about countries what should i return when you call on countries from me what should i call and hear what does dispose do what does save do those are the things that we need to define so firstly i'm going to include a reference to the database context right similar to what we had before and then i'm going to define my constructor c door and try that again there we go and i'm going to put in the data context so i'm not i just don't want to write it out again so i'm just going to copy the relevant parts there and paste there we go so i'm initializing the data context inside of the unit of work here and it will know that okay i have a copy of the data context also so then you're probably wondering so why do i need the data context here and that is really because what we need to do is return an instance of the repository but then we have to backtrack a bit so i need two private the same way that we would have defined them over this side as private let's see if i can just do this quickly so i'm just going to take that and i'm going to define two private properties of the respective hotel and and countries but i'm going to rename them i'm going to take off the accessor so this is going to be countries and this one is going to be hotels all right so now that i have these private properties i'm going to say down here that if the private property is empty then return a new instance of the generic repository so i'm just going to do that shorten because c sharp nine is pretty cool so i can just say something like countries and then question mark question mark equals so it's basically saying if this is null then what should i do so instead of a whole if statement right and i'll just say if it is null then return an object of the generic repository of type country and of course based on our definition of it you need the database context which is why we have it injected here so we pass in the context all right and then i'm basically just going to do the same thing for hotels so this is like a register note this is just like a rolodex to say that all of these are potential generic repositories once you're in the unit of work i have access to the countries the hotels and for every table that you define you need to make sure that you put or you make representation for it in the i unit of work as well as the unit of work but all of what we just did with the generic repository and the itinerary repository we don't have to redo any of that with any other data table that we may add in the future right so it's just about maintaining this list and these functions make sure that we have access to what we need right here in our unit of work register all right so the next thing that we need to do or well two more things so i want to define this pose and then we need to set up the save and then we're done with this so this pose i'm going to kind of write dispose through so this is just the way i do it you may see variations of the unit of work elsewhere but this is the way that i have come to do it for various reasons once again context is king so what i'm saying here is i'm going to call another method so i'm going to define a method here i'll just let visual studio generate a method right underneath it for that purpose and then i'm just going to say well actually now that i'm doing it i'm just going to say right here um underscore context so this pose is really like a garbage collector it's just saying when i'm done or when the operations are done please free up the memory that's that's the purpose so gc here is actually short for garbage collector if you look at the description garbage collector so this is saying when dispose is called then please dispose of the context meaning kill any memory that the connection to the database was using kill the connection kill all the resources it was using and go ahead and do what it would do by default anyway so that is it for the dispose the next one would be our save changes and this one is actually pretty simple because it's really only one line at least right now where we say await context dot save changes async here's the arrow i'm seeing away it but what don't i have i don't have the async once i put that there everything is fine in the world so let me just do a build to see if i have broken anything or if there's anything out of place and i have a successful build so that means i've successfully put in the code for my unit of work at least code wise or syntactically later on we'll find out if it works or not but right now this is a good victory to have and this task is done [Music]
Info
Channel: Trevoir Williams
Views: 7,215
Rating: undefined out of 5
Keywords: asp net core generic controller, asp.net core generic repository pattern, asp.net core generic web api controller, asp.net core web api, build a generic crud api with asp.net core, c# api, c# generic class constructor with parameters, c# generic method where t multiple types, crud operation in asp.net core 5 web api, generic web api crud controller, generics in c# with real time example, use generic type controller, use generic type controller and interface
Id: xqNLPB64LMk
Channel Id: undefined
Length: 31min 40sec (1900 seconds)
Published: Mon Mar 21 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.