Entity Framework Core Part 7 - Models vs Entities

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
One of my bugbears when it comes to software  development is overuse of the word 'model'.   Because we see the word 'model' used again and  again for all sorts of different things which are   only vaguely connected. Now it's understandable,  because what we're doing when we develop software   is we are trying to make a model - a software  model - of the real world, so the term does   come up a lot. But I think in certain situations  we do need to be careful, because it can cause   confusion. And particularly one place we need  to be careful is in the world of MVC alongside   Entity Framework, because the word 'model' is used  in those two different technologies for similar,   but different things. Let's just consider the sort  of setup we have. So a typical MVC application,   we'll have at the bottom some kind of database,  and then we'll have a controller which   extracts data from the database using Entity  Framework, and then we'll have the controller   sending information to the view to display it as  HTML. And the classes that are involved in both   of those layers of transfer are often referred  to as 'models'. So we have the model classes   in Entity Framework representing the data  in the database and then we have the model   in MVC - that's what the 'M' stands for - taking  data from the controller to the view. And in very,   very simple cases - which is what you typically  get when you're taking a course or reading a book   or something like that - the structure of those  two classes will be exactly the same. But very   quickly in any serious application they will start  to diverge. So let's suppose we've got a situation   where all you want to do is register a user for  login on our site. So in the database we're going   to have a table with - at a bare minimum -  two columns: one for the username, one for   the password. So the model class representing  that table will just have those two properties.   But when it comes to the view, we're actually  going to have the username plus the password   plus the confirm password. So typically, just  in case the user mistypes their password when   they're registering, we have that confirmation.  So the model that would be taking the data   between the view and the controller would  actually have three properties. So immediately,   we can see a situation where there is a difference  between those two structures and it will happen   again and again and again. So the terminology that  I personally use - and a lot of people use - is   this: the model that takes data from the database  to the controller we call the 'entity model', or   in fact more normally just the 'entity', and then  the classes that take data from the controller to   the view and back, I will simply call those the  'model' classes, because they are the M of MVC,   so why change that? So we talk about the 'model'  as the MVC part of it and then the 'entity' as   the Entity Framework part of it. Some people will  call the model of MVC the 'view model' to make the   distinction, which I think is kind of clear,  but the problem is 'view model' is also used   in another related design pattern: MVVM: Model  View ViewModel, and it means something slightly   different there. So I don't like to use that  term, because it can cause a bit of confusion.   So we're going to stick with 'model' for MVC;  'entity' for Entity Framework. Now, let's take   a look at some of the practical upshots of that.  So back to our application that we've seen before,   and now we can see in this BookLibrary, we can  be very clear this Book and this Author - these   are entities. So those are classes representing  the data in the database and at the moment if   we look at our controller, we are also using  those as models, because for example, if we   look at this Details we can see we're getting  back a book - and that is of that Book type   from the BookLibrary - so that's the entity. And  we're sending that straight through to the view.   Works okay, but it doesn't necessarily make a  lot of sense. One of the reasons that you'll   immediately see our divergence between models and  entities is to do a thing known as 'flattening'.   So at the moment, if we run this up ... we  can see that this is the books index page.   We've got the title and the year, but then also -  not really part of the Book class - we've also got   some of the Author information. But we're not  displaying all of the Author information. If   you look at the actual structure of our Author  entity, you can see that as well as the Name   we've got a DateOfBirth on there, but we're not  interested in that because all we're trying to   show is the Book and the Author name. But if  we look at the SQL that's generated, you'll see   that it has fetched everything, so it's done the  JOIN between Book and Author, and it has read,   for example, the DateOfBirth of the Author, even  though we didn't really need that. And that's not   going to be a huge overhead, but it is a slight  overhead and it could add up if you've got lots of   data that you're not necessarily interested in. So  what we want to do is this process of flattening   where we take the combination of the Book plus  the Author as entities and turn it into a single   Book model that we can display easily on the page.  So conveniently we've already got in our MVC a   folder called 'Models'. To be honest, quite often  in a big application I will have a whole separate   DLL for my models, but here we'll just put  them straight into the MVC application. And   so what I'm going to do is add a new class and I'm  going to call this BookModel. So although I call   my entities just the name of the thing - like  Book and Author - I could call the model just Book   because it's in a different namespace, but I find  that will get a bit confusing, so I normally call   my models '...Model' just to make a bit clearer.  So we'll add that and then the information we want   to have in our BookModel is going to be similar to  what we have in the Book, but slightly different.   So what I'll do actually is I'll pinch some of it.  I'm going to take all of that and put it in here,   but the big change I'm going to make  is I'm not going to have the Author as   an Author, I'm just going to have the Author as  the name, and so we'll just make that a 'string'.   So that's our flattened version. We're going to  get the Title and YearOfPublication from the Book   entity, we're going to get the Author name from  the Author entity. But we're going to squish them   together into a single class for the model that  we present over to the view. Then the next thing   we've got to do is obviously transfer that data  over from the entities into the model. And one way   we could do that - not actually necessarily the  best one, but we'll look at it - is just to do it   with a constructor. So we could have a constructor  for BookModel and we could give that a parameter   of type Book - so that's book from  our BookLibrary - call that 'entity'   and then we could copy over the  information. So 'Id = entity.Id;   Title = entity.Title; YearOfPublication  = entity.YearOfPublication;'   and then 'Author = entity.Author.Name;' So  that's got all the data in there. We're of course   assuming that we have done the appropriate eager  loading so that the Book does have an Author,   but we'll deal with that in the controller,  because in the controller - let's just do this   for our Index for now - what we've got there, that  is our List of Books. But if I just in here do a   '.Select(' and then 'b => new BookModel(' passing  in the book. So that will call the constructor   for the Book and then we've now got 'books 'as  a list of BookModels. What we'll then need to do   is delete the old 'Index' because  that was the index based on the Book   entity and then generate a new one. Still  going to be called 'Index', still going to   use the 'List' template, but now we're going to  have it for BookModel. So if we just add that,   we can see it's just generated the  standard list page. If we run it up,   and we can see it's listed out the books and  the authors, but this time based on that single   flat class BookModel - not the separate Book  and Author. However, if we take a look at the   generated SQL, you can see it's still not solved  one of those problems. It's still asking for the   DateOfBirth of the Author, even though we never  actually use the DateOfBirth. And the reason it   can't work things out properly is because we've  hidden away from Entity Framework the bit of the   code that decides what parts of Author we want.  So if we look in there again, we saw our BookModel   had just the need for 'Author.Name', but because  that was inside the constructor, it can't really   be seen. And so all that work has to be done after  the data has been loaded from the database. We can   get around that. What we need to do in fact is in  here we need to do things slightly different. We   don't call the BookModel constructor; we have to  initialize everything using property initializers,   because those will be visible to the Entity  Framework provider and therefore it will be able   to translate those into SQL. So what I'll need  to do, back on the BookModel I need to give it a   default constructor and then if I take  all of that code and just pop it in there,   and then obviously we need  to change 'entity' to 'b'   and we need to change those semicolons to commas,  and that should function in exactly the same way.   So we're still getting the same results  but now if we look at the generated SQL,  we can see that it hasn't selected the DateOfBirth  from the Author. The only thing we've got from   the Author was the Author's name. That's  because it has been able to look at that   in the member initializer and decide precisely  what needs to be fetched. And in fact we can take   that one step further because we don't now need  to have the 'Include', because LINQ to Entity Framework   can see that we want the Author's Name, it can  now work out for itself that we therefore need   to pull in at least part of the Author and  generate the JOIN. So if we run it like that   still works and still has the efficient  query where we're just pulling in the Name.   So to a large extent, we can forget about using  the 'Include' to do our eager loading, because   if we use this approach we have an even more  precise way of doing it. We are saying not just   that we need the Author but precisely that we  need the Author's Name and nothing else. And   although it's not a huge saving, if there's  a lot of data coming back from the database,   reducing that by potentially quite a large  amount here would be quite a useful thing to do.   There is still a slight downside to it though,  which is now our code is rather ugly. If we look   over here there we've got nice encapsulation, nice  separation of concerns. The BookModel knows how to   initialize itself, whereas because we can't use  that, now we've got to duplicate that knowledge   of how a BookModel should initialize itself into  the controller, which isn't really very nice. And if   we're doing this again and again - which we would  be in the other methods of the controller - we   would have code duplication. So how do we have  the best of both worlds? Well, the trick we can do   is to use an extension method. So what we can do  is in here - put it in the same file because it   is so tightly related to the BookModel class  - I'm going to have a 'public static class'   called 'BookModelExtensions'. And in there  I'm going to have my 'public static' method   and this is going to return an 'IQueryable' of our  'BookModel'. I'm simply going to call it 'ToModel'   and then the parameter it's going to take - so  this is the data type it's extending - is going   to be 'IQueryable' of the entity 'Book', okay? And  we'll call that 'source'. So what we're doing with   this 'ToModel' method is defining a way of mapping  a collection of Book entities onto a collection of   BookModels. What we then need to  do is, we're going to say 'return   source.Select' - so you'll notice really  very similar to what we were doing in there -   and then 'b =>' and in fact let's just steal  all of that, because it is that similar, so   right down to there. Let's take that code and  put it into there, so exactly the same code but   we've put it into the file that makes more sense.  We've put it alongside BookModel, not put it in   the controller. And then in the controller, all we  now need to do is in there, just say 'ToModel()'   and you'll see it's picked that up because we  already have the namespace in there. But 'books'   is - although it's a DbSet there - it implements  IQueryable. So that's our IQueryable<Book> that   we are extending. And then ToModel does the  work for us. And so now if we run that up  we can see it's still working, and again we  can see that the query is really as efficient   as it possibly could be. And then once we've  done that, we can start reusing all of that.   So let's, say, go to our Details. And  the way we do this now is we still need   our '_context.Books', we don't need the  'Include' again, instead we simply put the   'ToModel()' there. So we put it early on  before we do something like the 'Single',   so that all this work can be done in the  database, and then we have our selection.   Once again details previously was using the entity  directly, so let's get rid of our 'Details' view   and replace it with one generated for  the model ... Details ... BookModel, and then   get rid of the context ... add that. And then  let's just go to our 'Index' and change   the link down the bottom here so the Details  is going to work. And so if we run that up   and go to Details there, so there we can see we've  got the information. Again if we look at the query   we can see that's where it selected the single  Book, but again the only bit of information about   the Author it needed was the Author's Name. So  quite a nice way, where we can have reusable code,   we can have efficient code because it's optimizing  the SQL and we do that, as we saw there, by   creating the model and then each model we use  an extension method that we can call 'ToModel'   to convert a collection of entities  into a collection of models.   The only slight downside that some  people have with this kind of approach   is when you're doing this bit of the mapping -  whether you're doing it there in the constructor   or here in the 'Select' - it is a bit repetitive,  because very often you'll find in the majority   of cases the name of the property and  the type of the property in the model   is the same as the entity. So we're mapping  over 'Id' to 'Id', 'Title' to 'Title',   'YearOfPublication' to 'YearOfPublication'.  The only one that's a slight difference is   it's 'Author.Name' going to 'Author'. And so you  could well imagine that that's the sort of task   you might want to automate. And in fact there are  a number of libraries available for that. Probably   the best known one is one called 'AutoMapper',  which is available through NuGet.   So there we can see that's AutoMapper - lots  and lots of downloads, very popular one.   Personally, although I've used AutoMapper, I'm not  a fan of it because although it does save you that   effort we're talking about here of writing this  very repetitive code, the problem is that it can   then risk doing things for you that you didn't  really want it to do. An example is a situation   where I'd been using AutoMapper, later on I added  a new property that I only wanted to be updated in   certain circumstances. So it was a field that  wanted to be set when an object was created   but not when it was edited or anything like that.  And I spotted that it was being changed - even   though I hadn't coded this - when I was doing the  edit. And so first thing I did was a search for   this particular property name to see where it was  being referenced - couldn't find hardly anywhere   in the code where it was being referenced. And  then I realized what had happened was AutoMapper   had simply automatically started mapping it,  even though I hadn't explicitly told it to. Now   AutoMapper has lots of configuration where we can  say, 'no don't do it for this particular property'   or 'do it in a certain way,' but the thing is, if  you don't configure it, it does it automatically.   Personally, given the small, one-off effort up  front, I prefer to write my own code to do this   and then if I have to change it - if I need to  put a breakpoint, if I need to do a search - then   it's all available there for me. But plenty  of people like AutoMapper, so I'm not going   to tell you not to use it. You can use it just  like this, in both these ways. So you can use it   to generate an efficient SQL query when using  Entity Framework as well. So that's it. We've   looked at the relationship between models and  entities - entities relating to Entity Framework,   models relating to MVC. We've seen how, although  they start out the same, when you get any degree   of complexity they start to diverge. And we've  also seen how we can actually use them to really   produce efficient queries. We no longer need  to worry about eager loading in terms of the   'Include' method, because now we can use this  really precise targeted loading that only   loads what is required in the model, rather than  loading everything that the entities may provide.   Hope you enjoyed that. If you did, please click on  the 'Like' button because that helps promote us in   the YouTube rankings and therefore more people  will get to see the video. And also of course,   do subscribe so you can see  all the other videos as well.
Info
Channel: Coding Tutorials
Views: 1,108
Rating: undefined out of 5
Keywords: dotnet core, entity framework, c#, entities, models, eager loading, coding, tutorial
Id: 6145Q1juVHI
Channel Id: undefined
Length: 19min 54sec (1194 seconds)
Published: Fri Oct 30 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.