Entity Framework Core In-Memory Database

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Just about my favourite thing in .NET Core - certainly in Entity Framework Core - is the in-memory database, because it really makes your life much much easier when you're trying to unit test against a system that normally runs with a database. I'm going to split this video into two parts: first part I'm going to show you the reasons why an in-memory database is such a good thing and how it works, and then the second part, just a few of the caveats and pitfalls to really make best use of it. So let's take a look at the program I've put together. What I've got here is a fairly simple ASP.NET MVC website. I've got a couple of entities; I've got this Book here with the title and the year of publication and then a reference to an Author and then I've got an Author which has a name and then a collection of Books by that Author. So we've got a two-way references there that obviously in the database is going to be handled by a foreign key, but in Entity Framework and in code they're handled by references and collections. I've then got a DbContext - so pretty straightforward what we've got there connecting to the database - but then for testing, I don't want it to be directly injecting my DbContext into the controller because then we'd always be running against the database, so I've used the Repository Pattern instead. So I have an interface called ILibraryRepository, which gives us access to the Books and the Authors. It also allows us to add a new entity and to save changes to commit things to the database. For completeness we should also really have a DeleteEntity in there but just to keep things simple we can miss that out. And then we implement that repository in two ways. For the real use of it we have a DbRepository, so implements that interface, has the actual LibraryContext - the DbContext - injected into it and then just delegates each one of those methods and properties down to the corresponding one in the LibraryContext. Then on the other side of things, in our test project, here we have the MockRepository, and that again implements the same interface, but in this case we've had to just have in-memory lists of Books and Authors. We can access those by converting them to queryables - you'll have seen in an earlier video I was explaining why you've got to use IQueryable here rather than IEnumerable. We've got an Add method that just determines what type of entity it is and puts it into the appropriate collection and then SaveChanges does absolutely nothing at all because this is all in-memory when we're doing our unit testing. And the way we configure that then is - in our normal running - in Startup you can see we've declared that we want to inject a DbRepository whenever anyone asks for the interface and then we're also configuring the context to use SQLServer, whereas in the test code you can see that we just create this MockRepository and no DbContext, pass that into the controller and that's what we're going to use. And then our controller, finally, accepts just the interface. And so the code will either be using the Mock Repository or the DbRepository, whichever one we go for. But let's highlight the differences now. So in here I've got an Index that just displays all of the Books and Authors and then, not particularly realistic 'cause I've got a Create that doesn't actually take data in from the user, we've just hard-coded it to create an author 'Jane Austen' and a book called 'Emma' and add that into the data store via the repository. But I'm doing it that way so we can really highlight now the differences in behaviour that we get between a real database through the DbContext and our MockRepository. So let's pop a break in there and then run up the application and then we'll go to that Create Sample which will hit our break-point. And then if we just step over the first two statements we've created in memory our Author and our Book. But if we look at those in the debugger then we can see a couple of interesting things about them. Obviously we've got the name of the Author but the Id is set to zero, so that hasn't been given a value and also our collection of Books is currently null, whereas if we look at the Book we can see also it's not got an Id yet but because we put the Author straight in there, in there. So the Book knows who its Author is, but the Author doesn't yet know what its Books are. If we then step over and do the Add to the repository, which will add to the DbContext and the SaveChanges, and then we look at our objects again, now we can see that our Book for example now has a valid Id which has been generated by the database and also our Author now has a collection of Books that contains the Book that we wanted. So what Entity Framework has done for us through the DBContext is it's fixed up all those details as you'd expect a database to do. And then at the end of that we just redirect to the Index and we get of the Books and Authors listed out there. If we contrast that though with what's happened with our mock data ... so let's go to our Controller tests and here we can see we're just calling that Create directly from a test method so if we go to our Test Explorer we can see we've already got a failure in there on that test. So let's debug that test and then we hit the break point and if we just go through the same process again, at this stage it's just the same: we can see that the Id hasn't been set and we've got no Books. But when we step over now we hit the problem, because this is just in-memory data and we've done nothing special to deal with it, we still have no Id, we still have no collection of Books, and this is the limitation of writing a MockRepository in the way that I've done it, that it doesn't do some of the things that a database would do. And so ultimately if we just step back to the tests we can see that although we've got the Books okay, when we start looking for Authors, for example, that's not properly been set up and so that's why we're getting our exception that gives us the red light. And so what I find I end up doing is actually writing a far more complicated MockRepository that has to deal with all of those issues. So some of them are relatively simple - like every time we add an entity, make sure we just give it a unique Id, that can be written in a fairly general-purpose way - but all of those fix-ups like the fact that when a Book has a reference to an Author the Author also has to have a reference back to the Book, you need to put those in there to make the tests work. But they are very, very specific and you just find yourself writing lots and lots of special cases for each and every association that you have. And that's really the big benefit of the in-memory database: that it's still not dealing with the database directly, but it does that sort of work for you. And it's very simple to introduce. First thing we need to do is just make sure we add an additional NuGet package. So if we look at our test project and look at the installed packages we can see that amongst the other things we've got Microsoft.EntityFrameworkCore.InMemory. So along with the other Entity Framework packages that you've got to add, make sure that you've got that one in there as well. And then the code, which I've written earlier, is this GetInMemoryRepository that we're not using yet, but the main thing we do is we set up our DbContextOptions for using the in-memory database. So it's very, very similar to what we had in Startup when we're configuring it to use SQLServer, but here we're configuring it to use an in-memory database and then we just give the in-memory database a name to identify it. Call that whatever you like - you can have multiple databases coexisting in memory as long as you give them different names - but I've just called that 'MockDB'. We then use that to pass into the options to our LibraryContext. So the thing to notice here is we are using the DbContext, so when we had our own MockRepository we completely bypassed the DbContext, but now we do have the context and then we pass that into the real repository, so that DbRepository is the one that we were using in the real code and our MockRepository in fact we could now delete. We're not going to be using that at all. So that's what we do and that gives us back a repository and then the repository we can inject into the Controller in just the same way. So all I need to do here is change this from using the MockRepository to using this in-memory repository and if we run the test again ... we see that we're getting green lights. So it seems to have fixed the problems. Let's debug that so we can see what's actually going on. So we hit the break-point again this time, though when we step over we still obviously have no Books and no Id - and no Id there - but now because we're using an in-memory database, not our simple collection of objects, now if we look we can see that we do have a collection of Books that we do have Ids. And so although it's still all in memory it's working just like a real database. And so that means it's ideal for unit testing. It means we're in control of our data. Just to remind you of the problem with unit testing through to a database, it means that if someone happens to change the data in the database that means our tests are going to fail and we're going to get in red lights even though it's nothing wrong with the software; it's simply that the software is processing different data, and therefore you get different results, whereas here the database is completely under our control. It's completely recreated on each run of the tests so there's no risk of that sort of thing going on. And also the other advantage is it's faster because you don't have to make the connection through to the real database. So that's the the basic way of doing it. We can create our in-memory database very, very simply and get all of those benefits. One thing you may noticed is that we now don't need the repository at all. We've already got rid of the MockRepository and so at the moment we're injecting the DbRepository into the controller every time and just making things different by configuring the DbContext either with a real database or an in-memory database. And so you could completely eliminate the repository and simply put the DbContext directly into the Controller both when you're testing and when you're running. But something to bear in mind is there are actually two reasons people like to use repositories. One is what we've seen here: so you can switch between the mock repository and real repository. Well we've eliminated that. But the other reason people like to use it is to encapsulate their queries. So if you've got a number of quite complicated queries - let's say very often you want to list books between certain years, so everything in say from 1800 to 1900, something like that, you may not want to have to be writing LINQ all over your code. So you may in your repository have a method called GetBooksInRange and that isolates the LINQ code. And that's the second reason for having the repository. And not everybody's worried about that, but if you are concerned about that then it's still worth having the repository, even though you no longer need the MockRepository. There's a link to all that code below and I'll be following up very shortly with another video just pointing out a few of the tips and tricks that can be useful when we're using these in-memory databases for testing.
Info
Channel: Coding Tutorials
Views: 4,993
Rating: undefined out of 5
Keywords: C#, Entity Framework, Database, Unit Testing, .Net Core, coding, training, tutorial, captioned
Id: JKytAdsWaQs
Channel Id: undefined
Length: 12min 13sec (733 seconds)
Published: Sun Feb 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.