In this section, the Repository classes are updated for EF Core and dependency injection.
- [Instructor] Next, we need to migrate the repositories to EF Core. The largest change is handling of the disposing of the DbContext. The original repositories were very good about cleaning up after themselves, and that is a great pattern to follow; however, the DI framework in ASP.NET Core handles disposing of items from the container automatically. So now we have to differentiate between whether it was created through DI framework or whether we created it manually and then appropriately code the dispose method.
We also have to add interfaces for each of the repos to support the dependency injection framework within ASP.NET Core. So in the Exceptions folder, we have a custom exception that's used by the repository and we just need to drag that over. And we'll look at that in just a minute. And then we want to drag the Repos folder over as well. So if we look at the exception, straightforward, nothing fancy.
We just want to be able to catch a specific exception if somebody is trying to add more items to the cart than we have quantity available. There is a change we have to make to the invalid quantity exception, we just have to change the SystemException to Exception and save it. And we'll see how that's used shortly. When we look at the repos themselves, we'll start with the ProductRepo; this is a very, very simple repo.
It's just meant to find a particular product so that we can get the items available. In the ProductRepo, we need to change System.Data.Entity to using Microsoft.EntityFrameorkCore. We also want to have an interface for the ProductRepo; so we would call that the IProductRepo interface.
Make sure we put our colon in there. And then I am using ReSharper to do my refactoring. Visual Studio has refactoring built in as well. And then as the interface has one method, return to product, the method is Find, intakes an int. Now we also want to move this into a separate file so I'm going to move it to another file and then create a new directory called Interfaces.
And then we'll move the ProductRepo interface into that folder. We do have a little bit of name space cleanup to do now. That's Interfaces. And go back into the ProductRepo and we need to add a using for that name space. So it's SpyStoreDAL.Repos.Interfaces and save all files.
Now, let's open up the ShoppingCartRepo. There's a few more changes here. First and foremost, we need to cleanup all of the name spaces and I'm just going to copy it from the finished product so you don't have to watch me type. The main change is EntityFrameworkCore and EntityFrameworkCore.ChangeTracking. And then pulling in the interfaces as well. Let's go down to the constructor and the private and one change that we want to make is we want to add a boolean that we will accept to determine whether or not this repo should take care of disposing of resources.
So, private readonly bool_dispose. We also want to set the repo up for dependency injection so instead of creating a new ProductRepo, as we do on line 28, we want to accept that as constructor injection. This is why we set up the interface for the ProductRepo. So, IProductRepo and we'll call the variable ProductRepo and we also want to take in a StoreContext, and we'll call that db.
Now, we're going to change this line to pass in the ProductRepo to the main constructor, the db, and then a false for the yet to be added boolean parameter. The main constructor also takes in an IproductRepo and a boolean for dispose.
On line 29, instead of creating a new ProductRepo, we're just going to use the instance. We have to change the declaration to be an IProductRepo. And then we set the local dispose property to the property that was passed in. So, under normal circumstances, the way the dependency injection works, which we'll get more into in a later module, that first constructor on line 20 takes an IProductRepo, takes the constructed StoreContext, and then passes that on.
If we want to construct those ourselves, we would then call that second constructor on line 25, passing in a new ProductRepo, a new StoreContext, and true for dispose so that we know that we have to take care of the clean up. If we scroll down to these methods that return to view models, we are actually going to just delete the code that's in there for now, we will flesh that out in a later section, and then just return null so we don't get compiler errors.
Next change we have to make is in the delete method. There's two ways that I'm deleting in this repository. One is deleting from the collection itself, which means that it is being tracked, and the other way is to pass in the primary key and the timestamp value and then setting the entity state to delete it, on line 74, and then calling save changes.
The advantage of doing this is I don't have to make a database call to get the record just to turn around and delete it. There is a slight change in syntax to determine if it's in the ChangeTracker, and what we needed to do is just be EntityEntry like that. So this method looks in the ChangeTracker, if it finds it, goes ahead and deletes it from the ChangeTracker; if it doesn't find it, sets the entity state and does a delete.
One more change we need to do and that is down in the dispose method. And we just have to leverage the variable that we passed in. So, if (_dispose) then call dispose. Now that we have our code updated, we need to create an interface. I am going to cheat and copy the interface from the finished product so you don't have to see me type.
So I'll right click, Add, Class, and I can select Interface, can also select Class, we're just going to replace it anyway, IShoppingCartRepo, hit Enter. So I'm going to copy the entire IShoppingCartRepo interface from the finished product, including the name spaces, and just paste over the top.
And then we'll save that. The final change we need to make is go back into the ShoppingCartRepo and replace IDisposable with IShoppingCartRepo. Now that we've fully migrated the repos, let's take just a few minutes to talk about what they do. So let's save the file. Most of this is standard stuff; so I wrap up the find, I do a GetAll where I order by descending.
There's finding by customerId and productId, so it's not truly a find, it's a where. I can also pull them back as no tracking. Right there on line 42, AsNoTracking, informs EntityFrameworkCore not to add it to the Db.ChangeTracker because I don't plan on making any changes. It's a little more performant, little less overhead. The ones that are interesting to talk about are the update and the add.
So this is a shopping cart. If you send in an update and the total quantity then becomes higher than the quantity available, we throw, as seen on line 91, an InvalidQuantityException. That is our custom exception. If we do an add, we first look to see if that item is already in the cart. We're doin' that check on lines 104 and 105. If it's not in the cart, then we check the quantity versus the quantity in stock.
If everything matches, we do an add; otherwise, we update the quantity and then do a save. One interesting item of note, I want the entity passed into the method to have the new updated values so that my calling code doesn't have to do a new database search. So one way to do that is to just assign the new values from the item that was used to persist the changes to the database.
Save changes just wraps up the db context save changes and, again, I just do this in one place so I can save all my error checking in one spot without having to write duplicate code. And, with that, our repos are fully migrated.
- Creating the .NET Core project
- Adding Entity Framework
- Migrating the data access layer (DAL)
- Configuring services and the HTTP pipeline
- Adding remaining services to the dependency injection container
- Migrating controllers and actions
- Testing the services