Corey delivers a masterclass in architectural discipline, favoring robust database patterns like stored procedures over the fragile shortcuts often found in modern tutorials. It is an essential guide for any developer aiming to build scalable, production-ready systems.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
SQL Database Design for the Ticket App - C# on Linux Episode 13Added:
We have started working on building out a full demo app on Linux. Now, in this video, we're going to be building out our SQL database content to support that application.
This video is part of a series dedicated to developing C# applications on Linux.
Along the way, working at better understanding of how Linux works and how to make our apps truly cross-platform.
If you haven't already, subscribe to this channel to stay informed of new episodes as they come out. And if you're looking for even more.NET SQL AI training and more, go to I am Tim Corey.com.
Okay, let's jump over to Linux. And we have here I've opened up in VS Code the Tiny Ticket app. And we have already defined this. This is a really small tiny application. So, we haven't gone to the full development process. We've kind of mapped out and opened up Aspire and and started that way. What we're going to do today is build out our database, which again, we haven't gone through and modeled. Now, I'd encourage you whenever you're building an application, don't start by doing a file new project. Start by planning things out. I've done that in my head. I haven't shared that in video because that's not really the focus of this particular building of the app. We're focusing on working on Linux.
And so, to do that for this little application, it's a tiny and the purpose is not to you know, have this really complex application. The purpose is to show off various parts of how you'd work with different things on Linux. In this video, we're going to see how to work with a database. So, we've already seen our SQL server. We connected to our Docker SQL.
Now, because we're using Aspire, we could have Aspire just build for us a SQL container and build off of scripts a SQL database as well. And that would be pretty simple because we could just say, "Hey, build that out." And it just does it for us. And then production, we would just swap that out for our actual production database. But, I'm not going to do that. I'm going to do it by hand.
I want to show you how to build a database by hand and and then how to connect to it by hand. Um so, you know how to do that on Linux. So, that's why I'm doing it this way.
So, Docker SQL. And in here, we have one database we we developed the people DB.
We're going to develop a new database that is really simple. So, we're going to say new database. And the database name is going to be uh ticket DB.
Owner is default. Um it's going to be SA. So, we could say SA just because um but but that's it. Uh compatibility level, we're at 2025. Cool, that works. Um and we're going to say create.
So, now you have our ticket DB. And we have one table. So, let's go ahead and create a table.
Now, the other way of going about this is to use the um the SQL Server Data Tool, the SSDT, um which is right here, database project.
And we could create database project and then we could, you know, attach that to our solution. And I would encourage you to use that. For this video, we're not going to. I want to do it all manually.
But, um the the database project is a great way to put your database into source control. And have it be part of the deployment system without using Entity Framework. So, I'm not a huge Entity Framework fan um because of the fact that it gets abused so much and it's so difficult to use well. Um a lot of people use it, not many people use it well. So, um I typically use Dapper and connect to my database directly that way.
And to do that, you know, people say, "Well, but you got to, you know, build out your whole database manually." Which is even easier now with AI because it can help you with the the grunt work.
Just make sure you check your work. Um but the other benefit here is you can put that into a a database project, which then can be put into a source control.
You can have it um you know, do the the upgrade for you as part of your CI/CD process and so on.
That's a totally separate video that we'll probably do at some point. I've done it before, but probably do it again um with these new tools. But, just know it's possible and the way we're building right now is manual.
And it's going to mimic what you already have probably in production. But, you can create it down the road. You can create a SSDT project or a database project type off an existing database and just say, "Import this database."
And it takes care of all the scripts for you. So, that's also a way to go. And then you can continue to manage that existing database going forward. Anyways, all that to say, we're going to do it by hand. So, we're going to create one table. We're going to call it uh tickets.
Um so, we have our our one table and we have ID, primary key, identity. Cool. Um and you can either um you can let's let's do this. Let's uh say new let's do it using the designer.
Uh I believe you can come down here and modify too.
And I tend to do both. Um but I'm just going to do it in the designer.
So, our tickets we're going to have ticket title.
And let's just add our columns for now.
I'll close You know what? Let's just shrink it down. Um we have our ticket description.
We're going to have our date created.
And then let's add a couple more. Uh date completed.
And then we have one more down here.
Make it priority.
Okay. So, this is going to be a really simple help ticket or um similar type system. But, we're going to have one table.
One. And that table is going to be just the um the table for containing tickets. Notice you don't have anything like authentication or which user and all this other stuff. You'd have to create a massive database with a lot of stuff.
We're not doing that. This is a simple one-person use it system that's going to just track title and description, really. And when it was created and then date completed will tell us if the if it's null, it will be it will mean that it's not yet complete. If it has a date, it's been completed. So, really simple stuff.
Priority, uh this is a fun one.
It's going to be a um we'll not say null here.
We're going to default value of five.
And that default value just means that it's the lowest priority. One through five priorities, one is, you know, the world's on fire. Five is, you know, fix this when you can. Now, side note, give me a second. Users, have a separate one if you're giving it to users. So, if I was going to have give users the opportunity to set a priority, I'd have user priority and priority. And the reason why is because users will say that everything is a one. Everything is the most important thing. And when you go through and triage, you would probably go, "You know what?
The fact that you lost your spare power cable is not a world-ending emergency.
Let's go ahead and bump that down to where it should be." But, you can still show the user that you've, you know, seen their priority or something like that. But, for this, we're just going to have one priority.
Date completed, uh datetime2.
And date created, datetime2. If you don't know, SQL has datetime and now datetime2. Datetime2 is the one you want to use because datetime was they wanted to fix it because it it needed to be improved. And so, they created a second datetime called datetime2. The reason why is so it doesn't break compatibility with databases that use datetime. So, therefore, we have datetime2. That's the one to use. Okay. Now, for date completed, we'll leave that as as nullable. But, this one is not null, the date created. However, we're going to say get UTC date.
Like so.
Um we'll wrap that in parentheses. That's going to be the uh default value for that. That's going to get the UTC date and time for when it was created. That way when you create a record, you don't have to put date created in. It will automatically populate with exactly when that record was created.
Now, title and description should both be not null.
We have a primary key identity for the ID that's going to have an auto increment. Notice if we uh look here, we'll see the identity 1 1. Meaning it starts at one and goes up by one every time.
So, we're good there.
So, it looks like we're set here. Let's go ahead and apply these changes. It's going to say, you know, here we're going to do go ahead and publish this.
Okay.
Hit close. So, now we have our tickets.
And there's all our columns. Cool, we're all good. That means that now we can create stored procedures. Now, some people don't like stored procedures. Some people say they don't you know, they don't want to see a stored procedure. I do. And the reason why is because the fact that too often what happens is that that people will create um the the if they had used Dapper, they put select star in the code. You shouldn't do that.
You need to have a a call to, in my opinion, a call to a stored procedure.
Not because you want to put business logic in the database, but because the fact that you want to put that call in the database where it can be optimized, it can be somewhat optimized if it's an ad hoc call, but not nearly as well as if you put it in the database. And the fact that it's much easier to track down when you're looking at which database calls take them, you know, cost the most, you can very easily see which ones because they're being called by their stored procedure name as opposed to an ad hoc query.
That's another reason. You are tuning your database, right? Um and then also because the fact that they're now in source control and they're part of the database in source control, not part of your code in source control, right? So, okay. So, now we have our our stored procedure we're going to create stored procedures here, which to do this what I'm going to do is I'm going to actually say, um new query.
I'm going to generate this way. This is one area where it'd be nice to have the tool that we have for the [clears throat] the table. Um but stored procedures are not that hard to create. Um so, let's just create by hand. Create procedure.
And we're going to say dbo.
Uh sp.
People get confused here. Don't create sp_ because that is going to confuse it with confuse the system with the stored procedures in a master database.
So, don't use that. Just use sp for stored procedure. Um so, that's what I do and I say stored procedure, I'm going to say the table name is tickets.
Uh we want the plural for this one. Um tickets. So, tickets get all.
And I'm going to say as Don't I want to not do the um uppercase. So, begin end and I'm going to say select star from dbo.
tickets.
Okay. Now, select star, we freaks out about select star. And yeah, you probably should change that at some point. The good news is if you put it in a stored procedure, you don't have to worry too much about it because you can change it as long as you um change it in a way it doesn't break dependencies, right? But um but this is fine because we're we're just creating it for us. Um oops, I hit save. That's not what you're supposed to do. Um say run. So, this is going to create our first stored procedure.
So, command completed successfully. We come down here to stored procedures and hit refresh, we'll see that we have the get all. And now if we were to execute this, so to execute a stored procedure you say exec and it's like, hey, I don't know what that is because um it hasn't yet updated the the um cache. No problem. We can still run it and it ran and it's now just, you know, empty results because there are no tickets yet, but it did see ID and ticket title and ticket description, etc. So, we're good there.
Okay.
So, it would have been nice if actually I can undo this. Um there's a pattern here. So, I'm going to say get and I'm going to say here at um and let's say ID int and then here we can say where ID equals at ID.
Now, this is another important point. It doesn't matter here because it's an integer, but with Dapper, Dapper is going to um pass in a string as a varchar typically and if you're going against an nvarchar table, that can cause a a conflict. Um and so, what you want to make sure you do is you use types. And so, it knows, hey, I'm passing this in as this type and it'll make sure it's that type. Uh again, integer it doesn't really matter because you can kind of map it to an integer, but now you know that it's the correct type. Again, another reason why you use stored procedures as opposed to select star from um from tickets where first name equals, you know, Tim or something like that because it's going to convert that Tim to a a varchar and you want to make sure you're using the correct typing.
Okay. So, now this is going to create a stored procedure that's the get. So, I have a get all or we're doing basic CRUD operations here, right? So, we have a get all and that's going to do um the get everything, but the get is going to require an ID. It's going to return just that ticket. So, that's the the get all, get one essentially.
Um but let's run this. Make sure you don't highlight anything when you're running this because otherwise it just executes what's highlighted.
So, there you go. And um now we can s- you know, do this over and we're going to say, um insert and we can pass in not the ID, you don't need that, but and here's where I open up the uh the table the tickets table. There we go. Uh for some reason it hid the columns for a bit.
Oh, I scrolled. Um evidently it it collapses. That's kind of interesting.
Um I hadn't noticed that before. But anyway, um there are the columns and we're going to use that because it tells us Oh goodness. Um Tim needs to modify the table.
Um edit this table because since I put all of the um Well, that's interesting.
Nope. Um I want to modify table structure. There we go.
Uh you probably yelled at me. And I probably wasn't listening.
So, let's kill this.
Um We have ticket title. It's not an integer. That should be a either a varchar or nvarchar.
Um I'm going to say nvarchar 50 and it's the nvarchar max for description.
And the reason why we're going to change this is because well, first of all, you have to use a string for your ticket title and description, not an integer.
Uh that should be a duh moment. But I'm going to say nvarchar, not varchar. And there's a difference here some people don't know um what the difference is.
varchar and nvarchar both hold strings, but it's nvarchar holds Unicode, which allows for a much wider character set uh that includes emojis, that includes um characters from non uh non-English uh like like not ABC kind of languages.
Um so, we're talking about anything with an Asian language or similar that would, you know, Arabic languages. These are not um in the normal ASCII character set. I know that varchar is technically not just ASCII. It's a little bit different. It has a few more, but it's roughly ASCII as opposed to nvarchar has the full Unicode. So, I'm doing Unicode because again, emojis.
That's one that probably's going to pop up at some point and also again, supporting other characters maybe they're in um messages. You know, so if you have an error message and it had a character that's not ASCII, you don't want to have those little square boxes.
You want to actually see what the the message is so you can at least Google it or ChatGPT it. So, nvarchar for both of these.
It does mean it takes up double the space on disk compared to varchar. So, if you're on a massive database, that can have a difference. And so, you do want to think about what you allow and don't allow. There we go. Apply changes.
And if that had data in there, of course it would it would cause problems. You might have to drop and re-add.
Um but that should be fine now. So, now we have our columns and they're the right types, which is great. And this is the reason why I opened this up and display the columns because now when I am creating my stored procedure, I can say I want ticket title and I'm going to say nvarchar 50 because it's right here. I can see nvarchar 50.
So, I know what the type is.
Ticket description nvarchar max.
And I want the I don't need date created, date completed or so I actually priority.
And that would be an int.
>> [snorts] >> And I can leave priority off if um if I wanted to. I could say, you know, or I could say equals five.
Um that way I have a default value if I don't pass it in.
Okay. So, >> [snorts] >> insert into dbo.tickets Um and let's get rid of the where clause here.
I'm going to say um ticket title ticket description and priority.
And then values at ticket title at ticket description and at priority.
Okay. So, knowing how to write SQL very important. Uh very important. If you're going to work with SQL, you should know how to write it. And and yes, you don't have to know all the syntax and yes, you can, you know, mess things up. I will mess things up. Obviously, I messed up creating a table because I created integers for for text fields. Um but you really should know how to navigate around the basics of T-SQL if you're working with SQL because if you're working with SQL, you should be in a SQL database. You should be understanding the performance of it. You should be testing out what is my least performant queries and you should be doing this in production where it actually matters. Um sure, first you do it in development to see if you know what works and doesn't work, but you should test it out in production. You should be reading the execution plans, etc. So, you should know how these things work because if you don't you'll have performance problems. It's almost guaranteed. You will not luck your way into a good SQL database performance. It just won't happen. So, when we talk about making your apps performant, one of the primary ways you can make an app performant is by making the database performant. And you can't just say, "Well, I'm going to use Entity Framework and it's going to do it for me." No, it's not.
Which is why if you use Entity Framework, you should know how to do this even better because it's even harder to make sure you're checking your performance when you use a tool like Entity Framework. So, you have to do even more work in SQL. And if you're like, "I don't know SQL databases, therefore I'll just use EF." Well, that's actually the wrong way of going about it. Again, you're going to cause massive performance issues.
I've got videos on that. We don't have to go into more depth on that, but I want to make sure that we're clear on this. You should know how to work with your SQL database if you're using a SQL database. If you're using a different database type, SQL Lite, MySQL, doesn't matter.
You should know how that database works and how to optimize for that database.
So, or that that database product. So, make sure that you know how to it works.
Make sure you know more than just I can create a table and get data in out of it. Otherwise, you will have performance problems. Your app will have performance problems. And these performance problems won't show up until production. They won't show up in development because you don't have enough going on in development to really have the performance problems you will in production. So, okay. Soapbox over.
Let's go ahead and execute that and it executes successfully. Cool. So, now we're going to do an update.
Because update is kind of like the um the insert. We're just going to take all the values and this time we're also going to take in the date created.
Actually, date completed.
And this is a date time two.
Ah.
Date time two. I don't like the uppercase. Um so, we're going to And we're going to put a comma at the end. Okay. So, this takes in date complete as well. Now, I'm going to really simple update that says, "Hey, pass all the values back."
That's probably not what you're going to do in most cases. Now, this works because I I asked for the data, I give it right back and it's going to update those data data points. What I'd probably do in a production environment of any size is I would have different updates. So, we'd say, you know, instead of update say mark complete and it would just mark the record complete for the ID.
And we could say update priority and just be the priority update for this table. So, I wouldn't have just one query or one stored procedure that's all the updates, but this is what we're going to do.
So, update.
Um And then we come down here. Here's another, you know, piece of wisdom that is hard learned. Put your where clause in first. Where ID equals at ID.
So, the reason why I do this first is [snorts] otherwise you get talking, you get doing things, you get a phone call, you get text, whatever, you get distracted and you forget to put your where clause and you update it and it runs. It's great.
And the first time this is called it's going to update every single record in your database.
And you're not going to find out about that until after you've done it and after you have that oh no sinking feeling in your gut hoping that this thing didn't run when of course it did.
So, don't do that. You know, don't make that mistake. Put your where clause in first and then go back and put your your values in. So, ticket title Oops.
Set.
Ticket title equals at ticket title.
Ticket description equals at ticket description.
Date completed equals at date completed.
Priority equals at priority.
Okay. There is your update statement.
So, you can actually do something like this and then tab in one so you can see what the set is and it it reads clearer.
Okay. So, there's our our four values for our update. So, we can go ahead and run that.
Again, don't hit save. Hit the green run. You can also come down here to stored procedures and refresh and make sure you've got your your values here.
Okay. The last one we're going to do is we need one more and this is for delete.
And there's going to be some some um >> [sighs and gasps] >> irritation or or at least a discussion around how to do this.
Delete tickets where ID equals ID. This is This is a full delete.
You don't always want to do that.
Because sometimes you say, "Hey, I want to recover that." It's like, "Well, that's too late because it's gone."
Um or you have this problem where you have a history table where you have a history of all the things that you've done and you want to reference back. Well, if you delete the ticket and it has a history, that history points to an ID that doesn't exist. And that can be a problem. And so, you'd either have to delete, you know, cascade delete all of your history or you'd have to say that those history records are unmoored from an ID.
And neither one is a great option there.
So, sometimes what you do is not actually delete, but you do a soft delete where you mark it as, you know, date deleted and put a date deleted value in there. And then what you do is after a certain length of time, you go through and delete for real all the records that are after a certain date or before a certain date, right?
So, you'd say, "Hey, any record that is 2 years old or older, go ahead and delete those records and all their associated records or something of that nature. It depends on your how long you need to keep data for, etc. We're just going to have a full on delete. So, um if you if you delete it, it's gone.
Okay. So, there is our five stored procedures. You won't have exact stored procedures. Some people get really caught up in this the idea they have to have all these different stored procedures for every table and and don't do that. Don't just say because I have a table I have to have these five. Because first of all, like I said with the with the update, you might have multiple update stored procedures for this table or you might have a get that doesn't just get tickets, it also gets tickets with their associated, you know, user associated, whatever. And do multiple joins to bring in all the data it needs.
You don't have this one-to-one relationship. You've got to figure out what you need for data and then go create a stored procedure for that. The cool thing about using the not this way of doing things, but using the database project is that it's associated with your C# project. They're connected. So, you can see, "Hey, I need this data.
Let's go ahead and create a procedure for that." And then just put it right in the project and it updates with your code when that code needs it, it it's going to have it in the database. So, some cool stuff there, but just make sure that you don't get so caught up in the idea that I'm going to have it this way.
There's a lot more complexity around databases than just being a one-to-one relationship or this is the way we always do things.
Okay. So, now in theory we could make calls to this if we wanted to.
I'm not going to create tickets for now.
I'm going to leave it alone. We're going to do that. Actually, you know what?
Let's go ahead and create some tickets.
Just because of the fact that I want to have something for when we wire this up to the API, which will be coming up soon. So, let's go ahead and um I'm going to do a an insert. So, exec dbo.sp Uh actually, you know what? Let's Is this the refresh button?
Um no, but let's do this. Let's close this out. Don't save and I'm going to create a new query.
I don't know what the refresh is um in in this version. I know it's like control shift R I think it is for a SQL Server Management Studio. I forget. It's been a little bit. I do it by by memory sometimes and I don't remember the keywords. But, if we do exec dbo.sp Uh nope. So, tickets sp tickets uh underscore insert and we're going to pass in the values.
What are the values going to be?
Um My first ticket.
This is an easy one.
And it has a default value, but we're going to Let's let's pass it in with with no value um, for the priority because of the fact that there's default value. So, let's run this.
And it says, um, oh, I used double quotes.
Let's use single quotes.
My first ticket, that should be correct.
Nope.
Um, let's do this.
Nope. Um, I'm going to look at the stored procedure first.
Um, script is execute, we can do that.
>> [snorts] >> Yep. Uh, execute tickets, the ticket title.
Um, interesting, um, how it does that. Okay, set parameter values here.
Um, we do something like set ticket title equals um, my first ticket.
And set uh, ticket description equals this is an easy ticket.
Okay. And we're going to take off priority.
Um, we want to make sure we test that that we can call it without the priority. Let's just execute this and just see if we have Yeah, one row affected. I'm not sure what I'm doing wrong. Um, I'll be the other one.
But, let's go look at the the Actually, you know what? No, let's do this. Let's come over here and say, um, execute ticket or execute stored procedure.
Um, and we're going to say, uh, get all.
And run that. And my first ticket. This is an easy ticket. There's a date and no completed date, priority is five. Okay, so if I were to say get and pass in one, there we go.
It's the same value. Okay, pass in two, it's going to return no records. So, it that is working, too.
Okay. So, let's, um, let's do this. Let's do one or two more, um, my second ticket.
This is a hard ticket.
And I'm going to pass in, um, priority int.
And I'm going to say, set at priority, uh, of two.
Okay, and then, uh, I don't love how I've done this, but we'll do it manual by hand just a minute. Um, there's an insert another insert, we'll run this.
And cool.
And now if I do the same call again, we get my second ticket. Uh, this is a hard ticket and the priority is two. And if we do the get all, and run again, there's our two tickets.
Let's do the, you know, the insert here.
And I forgot I believe it's just the fact that I I put, um, parentheses around it. So, my third ticket.
And we're going to say, um, this is a medium ticket.
And I'm going to say three.
So, that should execute that. Let's go ahead and execute. It executed correctly. Okay, so that was just my bad about putting the parentheses around parentheses around it. Um, we can say I can do a select star if I wanted to.
Star from dbo.tickets.
And I can just highlight this and run it.
And it only runs this, not this to get highlighted just that code.
Um, so there's my three tickets. If I ran it again, you're not going to see four because it's it's just going to have three because it's not running this part of it. Now, just be careful with this because if you highlight the wrong parts, like if if you were to do, this is a really scary one, uh, delete delete, uh, from dbo.tickets where, uh, ID equals one, and you were to go, let's run this.
That's going to delete everything because you've highlighted just the delete and not the where clause. So, be careful of of that. Be careful of relying on the the highlights without being very careful what you highlight.
Okay. So, that's it. That's what we're going to do for this video. We're just going to set up the this very, very simple database, one, uh, table, five stored procedures. We put a couple of sample records into the table. Then we are ready to wire up the API and start, um, you know, having the the basic CRUD operations go through the API, which then we can then put it into the front end and create a little UI for it, maybe some some styling, etc. But, um, but that's it. That's all we are going to do for the database. So, this is part of the the ticket app, uh, part of this course. We're actually building a little a little app here. So, stay tuned for the next lesson. If you missed the previous lesson about the, um, Aspire setup, go ahead and check that out. Make sure you're up to date there. You can also, um, this this stuff right here, um, I won't include this, but coming up later you'll any source code we do will be attached to the descriptions of the videos. So, um, you won't get the database, um, probably. Maybe I'll get ambitious and put the database there, too. But for right now, you'll get just the source code for the the change we do to the the actual application. Okay, and that that'll be in the description. All right, thanks for watching. As always, I am Tim Corey.
>> [music] [music]
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29
So What's Odin Lang Even Good For
TechOverTea
131 views•2026-06-01











