Building APIs with C# and ASP.NET Core

Extending Data Models

Spencer Schneidenbach

Spencer Schneidenbach

Aviron Software, Microsoft MVP
Building APIs with C# and ASP.NET Core

Check out a free preview of the full Building APIs with C# and ASP.NET Core course

The "Extending Data Models" Lesson is part of the full, Building APIs with C# and ASP.NET Core course featured in this preview video. Here's what you'd learn in this lesson:

Spencer explains why extending data models is important to manage complexity as the application grows. An EmployeeBenefits class is created to represent the joined data between the employees table and the benefits table.

Preview
Close

Transcript from the "Extending Data Models" Lesson

[00:00:00]
>> Spencer Schneidenbach: So one of the things that you can depend on in life is change. Change will happen everywhere and it will certainly happen in your applications. So I wanted to take this opportunity to change one of the classes, the EmployeeBenefits class. Because this is one of those things, I promised you, if I saw something that I would not, what I would use to demonstrate a thing, but I would not do in real life, I would call it out.

[00:00:22]
And I would say, EmployeeBenefits, besides the fact that I've never worked in a payroll system very closely, as a programmer, I probably wouldn't model this like this. I could think about a dozen different ways as an employer to change this, benefits are incredibly complicated. So I have a mental model, but I can tell you right now that for the purposes of this section of the course about modification and about change, I can say that this thing needs to go.

[00:00:49]
So we're gonna rip it out entirely. So, sheesh, is that really necessary? Well, this is something we discussed earlier that sometimes abstractions fit your current needs, and then one day, they don't. And we're asked about migrations, and how do you migrate data? You migrate data very carefully, and with lots of people watching, ideally, to make sure that that data migration is successful.

[00:01:11]
So we're gonna go ahead and create a couple of different classes. We're going to create a benefit class, and then we're gonna create an EmployeeBenefit class to represent the joining of those two things. We are going to remove our benefit type and just have a benefit class entirely.

[00:01:25]
So I'm gonna close this folder. I'm gonna open up my next version. Extending your data model and model configuration, perfect. Make sure that my tests are in order, good, this one does have. Okay, make sure our tests are good, make sure our employee API is good. Okay, so I'm gonna go down to this EmployeeBenefits.

[00:01:46]
I'm going to wholesale remove this, and I am going to go here, and I am going to destroy all of that as well. And I'm gonna paste in our new classes. It's worth noting that everything has an Int Id, and that's because we need a place to track a unique record inside of a database.

[00:02:06]
And an integer ID is frequently, or a long, or a GUID, is frequently used to do so. So now we have the idea of a benefit, could be dental, vision, health. And then we have an EmployeeBenefit, which joins those two things together. It's basically a record that says, this employee has this benefit, pretty awesome.

[00:02:27]
So let's see, let's dotnet build our way out of, let's get all of our things figured out here. We don't have a benefit anymore, this does not fit anymore. So actually, I'm just gonna delete it, and I am going to take that out as well. And then rebuild the project, like I said, let the compiler do all the work for me.

[00:02:51]
I don't need to know where all of it is, if the compiler is willing to tell me, I will take it. So we will take this out because it is not, well, we'll go ahead and delete this now in case we need this class in a second. Should be good to dotnet build everything.

[00:03:06]
We got lots of other errors in our C data class, we need to take those out as well. Boom, boom, boom, all right, I'm gonna keep asking the compiler, what is all the good stuff? We don't have a benefits, we take that out as well. We don't have a, let's see, you get employee response.

[00:03:26]
Get employee response has benefits, axiom, okay.
>> Spencer Schneidenbach: Dotnet test, let our compiler guide the way, or getnet build. And, no, we've just wanna dotnet build for now. We don't need to run our tests yet, perfect. Okay, we've gotten all of our old benefits out of there. In practicality, you're probably not gonna do it like that where you just wholesale, drop a table, and remove all that stuff.

[00:03:52]
You're probably gonna refactor in place. But for the purposes of this demonstration, that'll be good enough. So now we're gonna add a migration to actually remove our old benefits table. So let's go ls, cd TheEmployeeAPI,
>> Spencer Schneidenbach: Paste that in. And one rule about migrations, you never run them until you look at them and make sure they make sense.

[00:04:20]
In this case, I haven't made any other data changes that I know of that would cause problems. This is exactly what I want, I wanna drop that table. So I'm gonna say, dotnet ef database update.
>> Spencer Schneidenbach: And we're gonna see that the EmployeeBenefits table did get indeed dropped, which is what we expect, awesome.

[00:04:47]
Okay, now we can add our new entities to our context, which is benefit and EmployeeBenefits. We're gonna go ahead and do that. And that looks pretty good, but we're not quite ready for a migration. There's one other change that I want to make, which is, I wanna focus in on this EmployeeBenefits.

[00:05:06]
I'm going to pretend for a second that this EmployeeBenefits wants to represent one unique record, for an employee and a benefit. So an employee with record id 1 should have no more than zero or one records with benefit id 1. So I wanna do what's called add a unique index.

[00:05:22]
So what I can do is use the Entity Framework Core configuration tools in the DBContext to actually model that relationship. And that's where the on-model created method comes in. So if we go here, and you see on-model creating, we'll just blank that out. And we will say that we want to add a specific property to this EmployeeBenefits class.

[00:05:45]
So we'll say modelBuilder.Entity. We'll give it the generic type, EmployeeBenefit, and then we will say, .HasIndex. So the way that you typically create uniqueness across lots of fields is they would either be your key, or you will create an index on them. And we will create the index like so where we got our benefit.

[00:06:06]
So we will say, new, and this is just object initialization syntax to instruct EF Core that these are the two fields we wanna create our index on. So we've got BenefitId and b.EmployeeId. And then we say, IsUnique. Now we should be ready to run our migration. Let's see, a little bit more about model creating, first though, you can use it to configure indexes, foreign keys, relationships.

[00:06:42]
You can use it to alter the property types of columns. So for example, in SQL Server, you can have columns with a maximum width or even a fixed width. And you can define those properties, you can either do it using attributes, which I have done. I prefer to use the fluent on-model creating API.

[00:07:01]
So if you needed to change the data type for something. And I'll do this often. If I have something I wanna store in a database but I don't want a table for it, what I'll do is, I'll just store it as a JSON blob and then store it as text in a database field.

[00:07:14]
That's something that I do sometimes. So let's add our things here, we'll go ahead and paste that in. And then we're gonna take a look at our migrations, cuz we never look at migrations. We never run them before we look at them. We got our new benefits table, which is exactly what we would expect, and we've got our new EmployeeBenefits table as well.

[00:07:37]
And you'll notice here that there is, indeed, EF Core has instructed the migration that we will, indeed, create a unique index on these two columns. So that way, there can only be exactly zero or one record with those employee and BenefitId fields. So that's good, it's what we want.

[00:08:06]
We do need to update our migrate and seed to add in benefits, so we will go ahead and do that. I'm gonna just copy and paste that here, Cmd+T for find things really fast. I think I copied this wholesale, perfect.
>> Spencer Schneidenbach: And then, we don't have a benefits class, so we're gonna go ahead and go back and add that.

[00:08:30]
We're gonna go back and add that to our employee property. We're gonna see, so I'm gonna say, Cmd+dot, nope, that's not gonna do it. I'm gonna go to employee, and I'm going to say, public list of benefit benefits get set. And we will set it to a new instance of a list, which will be good.

[00:09:01]
And let's see, these are EmployeeBenefits, so we need to put that there, perfect. Okay, compiler no longer complaining, that's what we like. The last thing that we will do is uncomment out, you remember our EmployeeId/Benefits endpoint. We'll go ahead and uncomment that out. And I'm gonna do that by simply copying and pasting over here.

[00:09:25]
Go to my EmployeesController and paste that in. And then we did change this record, we did delete something from it in order for it to compile. But we now have a name and a description, which we're getting from our benefit records, so we're gonna go ahead and add that.

[00:09:46]
We take our EmployeeId, prop string Name, prop string Description.
>> Spencer Schneidenbach: We'll set these fields as required to be set so that it doesn't complain. And we'll switch back here, and we will see that this works, which is good, which is what we want. And then we're gonna uncomment out our tests and run them and see what happens.

[00:10:18]
>> Spencer Schneidenbach: We want to see an okay result, and we are expecting to see two benefits from this test. We're expecting to see two benefits from this API. And why didn't it run, I need to run it in the root directory, don't I? Dotnet test, there we go.
>> Spencer Schneidenbach: Modifier public, what is it complaining about?

[00:10:43]
Wholesale erased the class, and I didn't mean to, public class SeedData
>> Spencer Schneidenbach: Redefine that and so that our compiler is happy with us.
>> Spencer Schneidenbach: Run dotnet test. We see that we have one failure, the collection contained two items, this is again, what is this? Is this the same thing that was happening before?

[00:11:13]
Custom application factory, employees returns one result. Yes, I think we need to change this to and then rerun our tests to see that everything passed. That's what we wanted, perfect. We don't want two jobs, we want one, we were creating one. Okay, good to go. One a couple of other notes that I wanna make.

[00:11:44]
We stopped returning benefits in the employee mainly for convenience in this refactor, because it is already a pretty extensive refactor. Sometimes you'll keep things like that, sometimes you won't. It really is just going to depend on the case that you're doing. I do wanna note as well that our call to get our benefits has a call to, first, we're getting the employee, and we are including the benefits object from the employee.

[00:12:08]
And then we have this call called ThenInclude, which points to the benefit on our EmployeeBenefits, which we see right here. Let's go, Ctrl+T, EmployeesController, scroll to the bottom, look here. And we see that this then include, is actually operating off of this include statement, which is for our EmployeeBenefit.

[00:12:34]
And if we navigate it to this property, you will see that it is, indeed, our EmployeeBenefit. So you can use ThenInclude to say, not only do I want to include employees, I wanna have my employees record, and then I wanna get my benefits. But I also, on top of that, wanna get the benefit record, the original benefit record from that EmployeeBenefit.

[00:12:55]
And we did this to prevent null reference exception. If we run this, if we don't have this in here, it's not gonna return the benefit object, and Entity Framework won't automatically bring it to us, and we'll get a null reference exception, and thus a failed test. Yeah, 500 internal server error.

[00:13:11]
And it'll be because this benefit property wasn't set and get no reference, so what happens? So that is updating the data model. There is a ton of stuff under the hood in terms of databases that we simply cannot cover. We talked about unique indexes, we talked about migrations, creating tables, dropping tables.

[00:13:33]
I highly recommend you learn about SQL and take a SQL course of some kind if you don't know it already because a lot of this stuff will eventually require some SQL knowledge in order to be as most effective with EF Core as you can be.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now