Services (w/ Entity Framework) - WPF MVVM TUTORIAL #6

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
we have the core functionality of our wpf mvvm application but we're ready to make our application more powerful and to do that i ultimately want to read and write our reservations from a database now to do this interaction with the database we're going to be creating service classes in a service class in my opinion is a self-contained class that performs some kind of business logic now keep in mind i say opinionated because the definition of a service really does vary between context such as a web service or a windows service that runs in the background and it's also a pretty broad term so people even have their own definitions of services as well but in our case just a class that performs a specific piece of business logic but if we look at our model you might ask well you already have business logic in here so why isn't this a service and ideally i would like to keep my business logic in the model but in this case we're going to be interacting with a database and i don't want that database to infest my model layer so i do want to extract and encapsulate that database interaction into a service and last thing before we get started if i had to put services into either m for models v for views or vms for view models then i would put it into the model layer so to get started as we see we are in the reservation book right now and currently we just have a list of reservations in our reservation book so we are storing our reservations in memory that's not what we want we want to store these in a database so to store these reservations in a database we're going to be using entity framework so let's head into our manage nuget packages and browse for entity framework core and i'm using net5 so i will install version 5.0.5 let's install that and i'm also going to need a database provider and i think the easiest to set up will be sqlite so we will search for ending the dot sql lite here we go second item in the list let's install that version 5.0.5 again same as our version for any firmware core now first thing we have to do with any of the framework is create a db context so i was debating where to put this db context i could put it in the services folder but i think i'm just going to create a new folder for db context and in here we're going to have our resurroom db context and we will inherit from db context as all db context do you need to do and the most important thing we need here in the db context is a db set to interact with our reservations so let's create a property for that a db set the type being our reservation and we'll call this reservations but we kind of have an issue here let's go look at our reservation and this does have properties for the data we want to write to the database but there's no setters on any of these properties so it's going to be kind of hard to map these to the database we could add setters to these properties but then that would conflict with requirements that we discussed in the models part of the series so we can't do that there's also no id property that we could use as a primary key and we also have other business logic on our reservation such as calculating the length of the reservation and checking of a reservation conflicts with another reservation and this logic we really do not need in the database layer so really what i'm saying is that i don't want to use this reservation as the type for my db set instead what we're going to do is create a different object that'll be dedicated to our database layer and we'll map to the database perfectly and we're going to call this the reservation dto so dto data transfer object and all its job is going to do is transfer our data between our application and the database so let's create a new folder in our application we'll call this dtos and we'll have a reservation dto so this dto should match the columns that we're going to have in our database so we are going to have a guide for the reservation id and this is going to be the primary key so we can decorate this with a key attribute let's import that so another nice benefit of using dtos is that our domain object doesn't have to be littered with a bunch of data annotations we can keep those in our dto i also want to write the room id and this contains a floor number and room number so i'm going to put both of these as columns in my reservation table so let's create properties for those the floor number and room number and then we do want the username start time and end time of the reservation so we can just copy these in and add some setters so now this is going to match our database table perfectly whereas if we had used the reservation class it kind of would have been a mess we would have to do some weird stuff to map the room id columns now keep in mind some applications do have very simple models and in those cases you can reuse the model as the type for your db set but here in the db set let's import our dto so now that our db context is complete we're ready to generate our database now to do that we're going to need another package and this is microsoft.net framework.tools let's install version 5.0.5 same as our entity framework version and once that is done we can head over to the package manager console and we'll create a migration for this database schema that we currently have so to do that we will use add migration and we'll name this migration initial so it's our very first one so there we go starting to do that and we get an error because no database provider has been configured yet and there's multiple ways to do this we could do it directly in the db context which is easy but then we're hard coding our connection string most likely we could use adddb context which is the best option but we don't have a service provider set up right now and the last option which they don't actually list but the option that we are going to use is a design time db context factory so we can add a class to our db context folder we'll call this the reservoom design time db context factory and we're going to inherit from i design time db context factory and the type of our db context is the reservim db context so let's implement this interface and in here we're going to configure our db context and the db context that we return from this method will be used for our migrations so to configure a db context we're going to use db context options so we can create a db context options builder and we can use this builder to specify our database configuration so we just want to use sqlite and pass in a connection string and i think the way this goes is data source equals the path to our file database so we're just going to have reservoom.db so just a relative path it'll probably just throw it in the bin and now we're going to build our options by calling options and this returns db contact option so let's put these into a variable and we're going to pass these options to the db context that we returned from this method so let's generate a constructor that'll take in those options and that's actually all we have to do because we pass it to the base constructor which also takes the options and that'll take care of everything for us so now let's add migrations again and it was successful here's our migrations here's the table that we're going to create looks good so i do want to say we are still hard coding our connection string right here but this is the design time db context factory so if you were to check this into source control you could just exclude this file we are not going to be using this at runtime now to actually generate our database we could just use update database here and that does create our database and run the migrations but it puts our database at the root of our project and our application is gonna be running from the bin and since our connection string is a relative path our application is gonna be looking for the database in the bin so this is not gonna work we do not want the database here and the easiest way to get our database to generate in the bin is to generate our database on application startup so we can get our db context in our startup method just instantiate that we're going to need db context options again let's just copy this over from the design time db context factory we'll move our connection string to a const variable so the connection string eventually you might want to move that to a config file or something and now pass in those options and to run migrations we can use our db context get the database and just call migrate and now if we run this we didn't get any errors and if i go into my bin we have our resume.db file and it has 20 kilobytes so it did run those migrations it does have data associated with it so finally we have our database set up let's start creating our services so i'm creating the folder in my services folder and this is going to be for the reservation providers so i'm going to create an interface here for n i reservation provider because i eventually want to depend on interfaces throughout my models so i'm not directly dependent on the database and it'll also make unit testing possible so we are going to have a reservation provider interface and all this going to do is asynchronously so it's going to be a task give us back a collection of reservations so import reservation from our models keep in mind this is the reservation not the reservation dto i don't want my database stuff to leak throughout my model layer and this is going to be called get all reservations so now let's implement this interface with a class this will be the database reservation provider implement the interface and for this we're going to use our db context to get the reservations from the database but one thing i did not cover in this startup is that the db context is not a long-lived object and what i mean by that is that we should dispose of this db context when we're done using it so we can wrap it in a using statement and then it'll get disposed but that also means that in this reservation provider we're gonna have to create a new db context in this method so to do that we're gonna create a new class the reservoom db context factory and the difference between this and the design time db context factory is that we're not going to be hard coding the connection string in this db context factory so in fact we can copy most of this into the db context factory get rid of the hard-coded connection string instead we're going to get that through the constructor so read only field generate our constructor and import our db context option stuff and we also didn't need these string args anymore that was just for the design time db context factory and now we're ready to use this so in the database reservation provider we're going to have our db context factory as a field coming from the constructor and then to get our reservations that's going to be pretty straightforward we're first going to create our db context using our factory and then use the context to get the reservations using just a to list a sync i think we have to import that there we go and we have to await this and make this method async and actually this gives us back our reservation dtos so we're gonna have to map those to reservations so we got our dtos and what we're gonna do is use some link to map those using select so map the dto to a new reservation and this takes a room id we can instantiate one of those using our dto's floor number and room number we got our username on the dto we have our start time on the dto and we have our end time and this line is kind of long let's extract this to a method we'll call this two reservation so mapping a dto to a reservation and then we'll simply return these reservations so all good there what else do we need on our model we're going to need a service to create a reservation so let's create a new folder in our services the reservation creators will create an interface that we're ultimately going to depend on the i reservation creator and this will have a simple asynchronous method i don't think it has to return anything in this case and we'll just call it create reservation and it'll take in the reservation that we want to create in the database so let's implement this as a class same kind of pattern the database reservation creator implement our brand new interface we're going to need our db context factory in here again so i'm just going to copy this from the reservation provider paste that in update the constructor name import what we need and let's use that db context from our dbcontext factory and this mapping is going to be the opposite of our reservation provider where we went from dto to reservation this time we want to go from reservation to dto so i'm going to do the same thing and put that into a method which is going to return a reservation dto we'll call this 2 reservation dto and this will map from our reservation model and let's generate that method so one thing i want to mention is that this will be a really good use case for automapper which is a popular library to map objects to and from one another rather than manually doing it with these methods so there's probably plenty of tutorials out there if you're interested in doing that automatically but for now just going to keep it simple and manually do it so we can set all these properties from our reservation we should probably null check the room id just in case we need a fallback value we just do zero and then we just get everything else from the reservation as well so username start time and end time we got everything we need we can write this to the database now and to do that we'll take our context get our reservations db set and add our reservation dto and then we just save our changes using save changes async and that'll write everything to the database and we have to await this and make our method a sync and now we're ready to use our services in our model so instead of storing these reservations in memory we're going to get them from an i reservation provider and then to add a reservation we're going to use an i reservation creator and we'll get both of these through the constructor so we can get rid of our old one and now we're going to use these services so get our reservations we'll use the reservation provider which means that all of this is gonna have to be async now and we'll await this and now to add a reservation we're gonna use our reservation creator to create the reservation and let's make this a sync as well and await that but now the last issue we have is checking for conflicts and what we could do is just get all reservations here asynchronously but that would be loading all reservations into memory for this loop when really we could just have a database query that checks for conflicts and then we wouldn't actually have to load all of the reservations so we are going to create another service to interact with the database and perform this conflict business logic so let's create a new folder in our services we'll call this the reservation conflict validators that sounds pretty cool create an interface for that and we'll have a single method that'll return true or false so it does a conflict or does it not conflict and we'll call this method does cause conflict and that'll take in the reservation that we want to test for conflicts so does the reservation cause conflicts let's create a class to implement this interface the database reservation conflict validator implement our interface again let's get our db context in here so just copy this constructor and now we're going to have to create our db context again let's just copy this using statement and what we're going to do is take our reservations db set and what we can actually do is map our reservation dtos to reservations so we're going to need the same mapper that we have in our reservation provider i think for now i'm just going to copy and paste this but ideally i would move this into a class so we can reuse it and then we can use any async and we can pass in a predicate and we want to check if any reservation in the database conflicts with our incoming reservation and that'll return the true false that we want for does cause conflict so we can simply await that and return the value all right that was easier than expected actually now let's get that i reservation conflict validator in here we can just add that to the constructor and oh one sec so for this reservation conflict exception i do want to pass in the conflicting reservation so i'm going to change this real quick and instead of returning true false we're going to return the conflicting reservation and we'll call this git conflicting reservation and make sure we update this method instead of any async we can use first or default async and let's update this method name there we go simple fix so now we will get the conflicting reservation by using our reservation conflict validator and passing in the incoming reservation and if the conflicting reservation does not equal null that means there is a conflict and we do want to throw our conflict exception and pass in the conflict so there we go now we have all of our ad reservation logic tucked away into our services and we wrap these in interfaces so our domain model does not depend on our database but now let's head up in our model to our hotel and our reservation book takes a bunch of services now i think what i'm going to do is just pass in the reservation book as a parameter to our hotel so we'll take care of all the services higher up and now all of this has to be async since we call into our reservation book to make sure we await everything make everything a task and now we have to pass in our reservation book so we'll instantiate that and that needs a reservation provider a reservation creator and a reservation conflict validator so let's create some locals for that as well so we'll create some variables for all of these and instantiate those using the database versions and then all of these need a db context factory so we can create that real quick and pass in our connection stream and actually i'm going to put this db context factory into a field and the reason i'm doing that is so that we can just use that down here so create a db context don't need our options to find here now a little bit simpler but now let's pass in the db context factory everywhere and this is kind of starting to get messy so setting up a dependency injection container would definitely be beneficial i think maybe we'll look into that later and now we're almost ready to test this out the only issue is that we changed our hotel methods so these are async now and we call these in our commands so our make reservation command calls make reservation but we don't await this so what we're going to need is an async command so let's create an async command base that will handle all the async logic and that we can inherit from and this can actually inherit from command base and i'm going to cover this quickly because i do have another video on async commands that i recommend checking out but we are going to make this abstract still and we're gonna have an abstract method to allow us to execute a sync and then we'll make execute an async void and we'll await execute a sync but before execution we're gonna set an is executing flag to true and then afterwards we'll set that to false so create a property for that and i actually want this to be private so can't change is executing from outside the class and when is executing changes i want to call one can execute changed because we are going to override can execute and we can only execute if we are currently not executing so this makes sure that we cannot spam the button which would definitely be an issue because this execution is async and could be a long running execution and actually i should wrap this in a try finally so if we get exceptions then we will set is executing the false keep in mind there is weird behavior with async void and exception handling and i cover that more in my async command video but for now we're just going to make sure that all of our commands handled our own exceptions but now we can change this to an async command base make this override execute a sync and now we can gracefully await this there we go all good we also said that we are going to catch all exceptions and handle them here in our command so if we get some kind of weird database exception i definitely want to handle that and show a message box just failed to make reservation and now for loading reservations we do that in our reservation listing view model so doing this asynchronously is a little bit difficult but the easiest way to do it is to create another command and this will be the load reservations command it'll have to be an async command so implement that and this will take our hotel which we'll use to get the reservations and it's also going to take our reservation listing view model so that we can pass the reservations to that view model so let's get those through the constructor and let's set up this command so this will be the load reservations command instantiate that passing in this v model and our hotel and now rather than calling update reservations here we're going to execute our load reservations command but i don't actually want to do that in the constructor because i do like my constructors to really not do anything and be simple so what i'm going to do is have a static factory method on here that'll give me back a reservation listing remodel and i'll call this loadviewmodel and it'll take the same dependencies as our viewmodel does through the constructor and what this will do will instantiate the view model pass in the dependencies and then it will take that view model and load it using the load reservations command and then we can return the view model which will be loading so we're going to do the loading in here let's do that real quick so load the reservations using our hotel we should probably try catch this just in case and do a message box in here so let's just copy this from our make reservation command so failed to load reservations this time as our message but now we have our reservations and we can send those to the view model so we'll call update reservations which is currently private we want that to be public and it'll take the reservations as a parameter and just iterate over those so we do not have to do anything async in here in fact we don't even need our hotel in a field anymore because we don't use it down there so get rid of that and now keep in mind we are loading and we don't have any loading indicator on the us we might want to look into adding that later and now the last thing we have to do is in our app.xaml.cs where we have our create reservation viewmodel function we don't want to use this constructor instead we want to use our new loadviewmodel method which will kick the viewmodel into loading and start executing our load reservations command automatically so we are finally ready to test this out we have our database we have our services we're using those services let's see this in action all right so we hit our load reservations command currently we have no reservations that's expected but now let's make a reservation submit that and fail to make reservation darn so let's see what our exception was oh and i thought so so it's our database reservation conflict validator so we cannot do this complex mapping which i did have a small feeling because i was like how is this going to get translated into sql which obviously can't so instead we're just going to have to use a bunch of where filters that are going to be based off our reservation conflict method so first we want to filter for matching room ids so we can do a where and we want to check if the dto has a floor number that equals our incoming reservations room id floor number and then we also want to filter for matching room numbers and then two more filters and those are going to be for the start date and end it so it is a conflict if the existing reservation's end time is greater than the incoming reservation start time and the existing reservation start time is less than the incoming reservation's end time and then we don't have to call conflicts anymore which means we really don't need this conflicts method either and this gives us back a reservation dto so we do have to map that to a reservation still but we do have to keep in mind that this could be null so if it is null then we can just return them all right so this query is much simpler it doesn't really look much simpler but it's using simple types that should translate in the sql let's see this in action finally so there we go successfully reserve the room thankfully and now we get our reservations from the database let's restart this application and there we go there's our reservations let's try and get a conflict real quick i forget the floor number and room number let's just try this all right the room is already taken perfect good guess and there we go our logic is still there except now we are using a database and we're interacting with that database via services now there's still much more to do the most notable thing is that every time we go to the reservation listing page we execute this load reservations command so there we go executed on startup and go back execute it again and this is where stores could come in so that we could really load this data only once and we can manage things like lazy loading as well so that's actually where we're heading next but there's other things we have to tackle such as some ui work styling maybe loading indicators and error handling and maybe setting up a dependency injection container for registering all these services but services database interaction definitely a crucial part to most applications so hopefully you can apply this to your own application if you have any questions criticisms or concerns be sure to leave them below in the comments section if you enjoyed the video or are enjoying the channel consider becoming a member other than that leave a like or subscribe for more thank you
Info
Channel: SingletonSean
Views: 3,104
Rating: undefined out of 5
Keywords: wpf, programming, visual, studio, xaml, custom, control, generic, system, line, display, timer, template, binding, c#, how, to, series, tutorial, easy, time, maintain, package, design, part, event, code, framework, register, static, state, default, view, style, wrap, panel, stack, scroll, viewer, first, width, action, void, model, layout, user, box, mvvm, data, error, icon, class, relay, clean, simple, sub, log, file, host, grid, scope, align, margin, deploy, github, actions, release, download, essential, rule, logic, story, navigation, service, database, architecture
Id: STt3U122wiU
Channel Id: undefined
Length: 23min 36sec (1416 seconds)
Published: Sat Oct 23 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.