Exercises: Building A Model Using Database First Workflow
Exercises: Building A Model Using Database First Workflow
Your job is to build an application for a video rental store called Vidzy. For the
purpose of this course, you don’t need to worry about user interface, so you’ll be
doing all these exercises in a console application.
1st Iteration
You attempt to build the Vidzy app in an iterative way. In the first iteration, you
want to implement the ability to add videos in the database.
You start by building your database first. So, create a new database called Vidzy.
Download the database script in the supplementary materials of this lecture and
run it on the Vidzy database to create the initial tables: Videos, Genres and
VideoGenres. (Note that here we are assuming there is a many-to-many
relationship between videos and genres.)
Once your database is ready, build an entity data model using database-first
workflow. Bring all the tables and spAddVideo stored procedure. We don’t prefix
method names with sp in C#. So, change the name of the function import to
AddVideo.
2nd Iteration
You realize that you over-engineered the solution and each Video needs one and
only one genre.
So, add a new NULLABLE tinyint column to Videos table called GenreId. Edit
the records in the table and assign them a genre.
Open the Videos table in the table designer again, remove the nullable attribute
from the GenreId column and create a relationship between Genres and Videos.
1
Exercises By: Mosh Hamedani
Finally, modify the spAddVideo stored procedure accordingly so you can still add
new videos.
Bring the changes into your model. Note that there are two relationships between
Genre and Video: One many-to-many relationship (from before) and a new one-
to-many relationship because of the recent changes. So, as you see, sometimes
refreshing your model doesn’t quite work the way you expect. In this case, the old
relationship was not deleted, so you need to manually delete it.
Delete the many-to-many relationship and validate your model. Save the changes
and re-build the project. Ensure you can still add videos to the database in your
console application.
3rd Iteration
Vidzy tells you that they need to classify their videos into three categories: Silver,
Gold and Platinum. You decide to implement this by adding a new tinyint column
in the Videos table, called Classification. Make sure this column is not nullable
and use 1 as the default value to assign all existing videos to the silver category.
Bring the changes into your model, and map the Classification property to a new
enum called: Classification. This enum should have three members: Silver, Gold
and Platinum.
Ensure you can still add new videos using your console application.
Solution
To see my solution, download the solution zip file. Temporarily rename your
database to Vidzy_Exercise. Create a new database called Vidzy and run the
SQL script included in the solution zip file on this database. This would be the
2
Exercises By: Mosh Hamedani
database after all changes in these iterations. Now you can open the console
application in Visual Studio and run the code.
3
Exercises By: Mosh Hamedani
1st Iteration
You attempt to build the Vidzy app in an iterative way. In the first iteration, you
want to implement the ability to add videos in the database.
Create a new console application and build the following model using the code-
first workflow:
Use code-first migrations to generate the database and populate the Genres
table with some reference data.
Hint: Use two migrations, one for creating the tables, another for populating the
Genres table. (reason: if you include your INSERT INTO statements into the
InitialModel migration, and then you decide to overwrite it as a result of some
recent changes, you’ll lose all your INSERT INTO statements.)
1
Exercises By: Mosh Hamedani
2nd Iteration
You realize that you over-engineered the solution and each Video needs one and
only one genre.
Change your model such that each Video has only one Genre.
Use code-first migrations to update the database. Inspect the database and note
the changes to your tables.
3rd Iteration
Vidzy tells you that they need to classify their videos into three categories: Silver,
Gold and Platinum. You decide to implement this by using an enum:
Classification. Add a property of type Classification to your Video class.
Use code-first migrations to update the database. Inspect the database and note
the change to the Videos table.
Deployment
You’re ready to deploy the application. Your DBA expects you to provide a
database script. Run the following command to get a SQL script of all your
migrations:
This command generates the SQL script from the very first migration to the last
one. In a real-world scenario, you may want to change the range of migrations
included in the SQL script in each deployment. To do this, you can use:
2
Exercises By: Mosh Hamedani
4th Iteration
In the last exercise, you generated a SQL script for your DBA to deploy the
database. He was not happy about the schema you provided and has requested
a few changes:
Videos table
• Name column cannot be NULL and its length should be maximum 255
characters.
• Genre_Id column should be renamed to GenreId, and it cannot be NULL.
• Classification column should be a tinyint.
Genres table
• Name column cannot be NULL and its length should be maximum 255
characters.
Implement the requested changes using Fluent API. Organize your configurations
into separates, one class per entity (see the lecture “Organizing Fluent API
Configurations” for more details).
Hint 1: Open the Videos table and note the type of Classification column. The
reason this column is an int is because in C# enums are integers (by default). To
change the type of this column, you need to change the Classification enum as
follows:
Hint 2: Once you override the default conventions and try to run Update-
Database, you’ll encounter the following error:
1
Exercises By: Mosh Hamedani
In situations where you receive errors like this about database objects with some
random identifiers (eg DF__Videos__Classifi__1920BF5C), you need to inspect
your database in SQL Server Management Studio. Expand Videos >
Constraints and you’ll see the constraint. Right-click it and select Script
Constraint as > CREATE TO. This way you can see what this constraint does. In
this case, it sets 0 as the default value for Classification. This was automatically
added by one of your earlier code-first migrations.
The reason this constraint was added is because the Classification property of
Video class is not nullable. So, in situations like that, Entity Framework assigns
default values to prevent the migration from failing in case there are existing
records in the table.
So, to finish this exercise, you need to manually drop this unnecessary constraint.
Modify your current migration that includes schema changes and add the
following line at the beginning:
Then run the migration to make the changes to the Videos table.
Hint 3: If you have a table open in the Table Designer in SQL Server
Management Studio and then update your database using code-first migrations,
you’ll not see the changes, even if you right-click on the table and open it again in
the designer. You need to close the existing table design session and re-open
your table in the designer.
Once you’ve made all the requested changes, generate a new script for your
DBA.
2
Exercises By: Mosh Hamedani
5th Iteration
Vidzy is happy with your job and want a new feature: the ability to tag videos.
Each video can have zero or more tags and each tag can be applied to zero or
more videos.
Change your model to satisfy this requirement. Unlike the last iteration where you
created a separate migration to overwrite code-first conventions, in this iteration,
override the default conventions at the same time as adding the new classes.
• The intermediary table for the many-to-many relationship between videos and
tags should be called VideoTags.
Apply the changes and create a new database script for your DBA.
3
Exercises By: Mosh Hamedani
Querying Data
Download Section 6 - Exercise Files.zip attached to this lecture and open the
solution in Visual Studio. This is the model for Vidzy video store built using the
code-first workflow. Whether you build a model using database-first or code-first
workflow, querying data is exactly the same. So you can use this solution to
exercise querying data.
Once you open the solution, go to Tools > Package Manager > Package
Manager Console and run Update-Database. This will generate a new database
called Vidzy_Queries populated with some data.
(Continued on the next page)
1
Exercises By: Mosh Hamedani
• All movies grouped by their classification: Project the group into a new
anonymous type with two properties: Classification (string), Movies
(IEnumerable<Video>). For each group, display the classification and list of
videos in that class sorted alphabetically.
Output:
Classification: Silver
Anchorman
Classification: Gold
Die Hard
Schindlre’s List
The Dark Knight
The Hangover
The Shawshank Redemption
Classification: Platinum
Terminator 2: Judgment Day
The Godfather
Hint: after applying GroupBy, you need to use Select to do the projection.
Each group has a property called Key that you can use to get the
2
Exercises By: Mosh Hamedani
Classification. The group variable can be used to get all videos in that group.
• List of genres and number of videos they include, sorted by the number
of videos. Genres with the highest number of videos come first.
Output:
Action (3)
Drama (3)
Comedy (2)
Horror (0)
Thriller (0)
Family (0)
Romance (0)
3
Exercises By: Mosh Hamedani
Write a query to load all videos in the database. Iterate over the videos and
display the name and genre of each video. You’ll get a NullReferenceException.
Why? Because you didn’t eager load the videos with their genres and lazy
loading is not working in the current solution.
Modify the code to enable lazy loading. Then run the program and ensure that
videos and their genres are displayed. Inspect the queries ran by Entity
Framework using SQL Profiler. Observe the N + 1 problem.
Next, use eager loading to solve the N + 1 problem. Inspect the queries using
SQL Profiler again and notice the difference.
1
Exercises By: Mosh Hamedani
Changing Data
Download Section 8 - Exercise Files.zip attached to this lecture and open the
solution in Visual Studio. Then, go to Tools > Package Manager > Package
Manager Console and run Update-Database. This will generate a new database
called Vidzy_UpdatingData populated with some data.
Note: For each exercise, create a separate method. Make sure that your
methods are properly named and they reflect the intent. Pay great attention to the
number and type of arguments for these methods so that they’re reusable. In the
Main() method, simply call each of your methods.
Note: In each method, wrap your context with the “using” block to ensure it’s
disposed at the end of the method. Example:
1- Add a new video called “Terminator 1” with genre Action, release date 26
Oct, 1984, and Silver classification. Ensure the Action genre is not duplicated in
the Genres table.
2- Add two tags “classics” and “drama” to the database. Ensure if your method
is called twice, these tags are not duplicated.
3- Add three tags “classics”, “drama” and “comedy” to the video with Id 1 (The
Godfather). Ensure the “classics” and “drama” tags are not duplicated in the
Tags table. Also, ensure that if your method is called twice, these tags are not
duplicated in VideoTags table.
4- Remove the “comedy” tag from the the video with Id 1 (The Godfather).
6- Remove the genre with Id 2 (Action). Ensure all courses with this genre are
deleted from the database.
1
Data Annotations By: Mosh Hamedani
Tables
[Table(“tbl_Course”,
Schema
=
“catalog”
)]
public
class
Course
{
}
Columns
[Column(“sName”,
TypeName
=
“varchar”)]
[Required]
[MaxLength(255)]
public
string
Name
{
get;
set;
}
Primary Keys
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public
class
Isbn
{
get;
set;
}
1
Data Annotations By: Mosh Hamedani
[Key]
[Column(Order
=
2)]
public
int
OrderItemId
{
get;
set;
}
}
Indices
[Index]
public
int
AuthorId
{
get;
set;
}
[Index(IsUnique
=
true)]
public
string
Name
{
get;
set;
}
2
Data Annotations By: Mosh Hamedani
Composite Indices
[Index(“IX_AuthorStudentsCount”,
1)]
public
int
AuthorId
{
get;
set;
}
[Index(“IX_AuthorStudentsCount”,
2)]
public
int
StudentsCount
{
get;
set;
}
Foreign Keys
public
class
Course
{
[ForeignKey(“Author”)]
public
int
AuthorId
{
get;
set;
}
3
Fluent API By: Mosh Hamedani
Basics
public
class
PlutoContext
:
DbContext
{
protected
override
void
OnModelCreating(DbModelBuilder
modelBuilder)
{
modelBuilder
.Entity<Course>
.Property(t
=>
t.Name)
.IsRequired();
}
}
Tables
Entity<Course>.ToTable(“tbl_Course”,
“catalog”);
Primary Keys
Entity<Book>.HasKey(t
=>
t.Isbn);
Entity<Book>.Property(t
=>
t.Isbn)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
1
Fluent API By: Mosh Hamedani
Columns
Entity<Course>.Property(t
=>
t.Name)
.HasColumnName(“sName”)
.HasColumnType(“varchar”)
.HasColumnOrder(2)
.IsRequired()
.HasMaxLength(255);
One-to-many Relationship
Entity<Author>
.HasMany(a
=>
a.Courses)
.WithRequired(c
=>
c.Author)
.HasForeignKey(c
=>
c.AuthorId);
Many-to-many Relationship
Entity<Course>
.HasMany(c
=>
c.Tags)
.WithMany(t
=>
t.Courses)
.Map(m
=>
{
m.ToTable(“CourseTag”);
m.MapLeftKey(“CourseId”);
m.MapRightKey(“TagId”);
});
2
Fluent API By: Mosh Hamedani
One-to-zero/one Relationship
Entity<Course>
.HasOptional(c
=>
c.Caption)
.WithRequired(c
=>
c.Course);
One-to-one Relationship
Entity<Course>
.HasRequired(c
=>
c.Cover)
.WithRequiredPrincipal(c
=>
c.Course);
Entity<Cover>
.HasRequired(c
=>
c.Course)
.WithRequiredDependent(c
=>
c.Cover);
3
LINQ Cheat Sheet By: Mosh Hamedani
Restriction
from
c
in
context.Courses
where
c.Level
==
1
select
c;
Ordering
from
c
in
context.Courses
where
c.Level
==
1
orderby
c.Name,
c.Price
descending
select
c;
Projection
from
c
in
context.Courses
select
new
{
Course
=
c.Name,
AuthorName
=
c.Author.Name
};
Grouping
from
c
in
context.Courses
group
c
by
c.Level
into
g
select
g;
1
LINQ Cheat Sheet By: Mosh Hamedani
Inner Join
Use when there is no relationship between your entities and you need to link them based on a
key.
Group Join
Useful when you need to group objects by a property and count the number of objects in each
group. In SQL we do this with LEFT JOIN, COUNT(*) and GROUP BY. In LINQ, we use group
join.
Cross Join
To get full combinations of all objects on the left and the ones on the right.
2
LINQ Cheat Sheet By: Mosh Hamedani
Restriction
context.Courses.Where(c
=>
c.Level
==
1);
Ordering
context.Courses
.OrderBy(c
=>
c.Name)
//
or
OrderByDescending
.ThenBy(c
=>
c.Level);
//
or
ThenByDescending
Projection
context.Courses.Select(c
=>
new
{
CourseName
=
c.Name,
AuthorName
=
c.Author.Name
//
no
join
required
});
Grouping
var
groups
=
context.Courses.GroupBy(c
=>
c.Level);
1
LINQ Cheat Sheet By: Mosh Hamedani
Inner Join
Use when there is no relationship between your entities and you need to link them based on a
key.
Group Join
Useful when you need to group objects by a property and count the number of objects in each
group. In SQL we do this with LEFT JOIN, COUNT(*) and GROUP BY. In LINQ, we use group
join.
2
LINQ Cheat Sheet By: Mosh Hamedani
Cross Join
To get full combinations of all objects on the left and the ones on the right.
Partitioning
To get records in a given page.
context.Courses.Skip(10).Take(10);
Element Operators
//
throws
an
exception
if
no
elements
found
context.Courses.First();
context.Courses.First(c
=>
c.Level
==
1);
3
LINQ Cheat Sheet By: Mosh Hamedani
Quantifying
bool
allInLevel1
=
context.Courses.All(c
=>
c.Level
==
1);
Aggregating
int
count
=
context.Courses.Count();
int
count
=
context.Courses.Count(c
=>
c.Level
==
1);
4
LINQ Cheat Sheet By: Mosh Hamedani
Restriction
context.Courses.Where(c
=>
c.Level
==
1);
Ordering
context.Courses
.OrderBy(c
=>
c.Name)
//
or
OrderByDescending
.ThenBy(c
=>
c.Level);
//
or
ThenByDescending
Projection
context.Courses.Select(c
=>
new
{
CourseName
=
c.Name,
AuthorName
=
c.Author.Name
//
no
join
required
});
Grouping
var
groups
=
context.Courses.GroupBy(c
=>
c.Level);
1
LINQ Cheat Sheet By: Mosh Hamedani
Inner Join
Use when there is no relationship between your entities and you need to link them based on a
key.
Group Join
Useful when you need to group objects by a property and count the number of objects in each
group. In SQL we do this with LEFT JOIN, COUNT(*) and GROUP BY. In LINQ, we use group
join.
2
LINQ Cheat Sheet By: Mosh Hamedani
Cross Join
To get full combinations of all objects on the left and the ones on the right.
Partitioning
To get records in a given page.
context.Courses.Skip(10).Take(10);
Element Operators
//
throws
an
exception
if
no
elements
found
context.Courses.First();
context.Courses.First(c
=>
c.Level
==
1);
3
LINQ Cheat Sheet By: Mosh Hamedani
Quantifying
bool
allInLevel1
=
context.Courses.All(c
=>
c.Level
==
1);
Aggregating
int
count
=
context.Courses.Count();
int
count
=
context.Courses.Count(c
=>
c.Level
==
1);
4
LINQ Cheat Sheet By: Mosh Hamedani
Restriction
from
c
in
context.Courses
where
c.Level
==
1
select
c;
Ordering
from
c
in
context.Courses
where
c.Level
==
1
orderby
c.Name,
c.Price
descending
select
c;
Projection
from
c
in
context.Courses
select
new
{
Course
=
c.Name,
AuthorName
=
c.Author.Name
};
Grouping
from
c
in
context.Courses
group
c
by
c.Level
into
g
select
g;
1
LINQ Cheat Sheet By: Mosh Hamedani
Inner Join
Use when there is no relationship between your entities and you need to link them based on a
key.
Group Join
Useful when you need to group objects by a property and count the number of objects in each
group. In SQL we do this with LEFT JOIN, COUNT(*) and GROUP BY. In LINQ, we use group
join.
Cross Join
To get full combinations of all objects on the left and the ones on the right.
2
LINQ Cheat Sheet By: Mosh Hamedani
Restriction
context.Courses.Where(c
=>
c.Level
==
1);
Ordering
context.Courses
.OrderBy(c
=>
c.Name)
//
or
OrderByDescending
.ThenBy(c
=>
c.Level);
//
or
ThenByDescending
Projection
context.Courses.Select(c
=>
new
{
CourseName
=
c.Name,
AuthorName
=
c.Author.Name
//
no
join
required
});
Grouping
var
groups
=
context.Courses.GroupBy(c
=>
c.Level);
1
LINQ Cheat Sheet By: Mosh Hamedani
Inner Join
Use when there is no relationship between your entities and you need to link them based on a
key.
Group Join
Useful when you need to group objects by a property and count the number of objects in each
group. In SQL we do this with LEFT JOIN, COUNT(*) and GROUP BY. In LINQ, we use group
join.
2
LINQ Cheat Sheet By: Mosh Hamedani
Cross Join
To get full combinations of all objects on the left and the ones on the right.
Partitioning
To get records in a given page.
context.Courses.Skip(10).Take(10);
Element Operators
//
throws
an
exception
if
no
elements
found
context.Courses.First();
context.Courses.First(c
=>
c.Level
==
1);
3
LINQ Cheat Sheet By: Mosh Hamedani
Quantifying
bool
allInLevel1
=
context.Courses.All(c
=>
c.Level
==
1);
Aggregating
int
count
=
context.Courses.Count();
int
count
=
context.Courses.Count(c
=>
c.Level
==
1);