SlideShare une entreprise Scribd logo
1  sur  70
Télécharger pour lire hors ligne
12
How to use EF
Core
In this chapter, you’ll learn how to use Entity Framework (EF)
Core to work
with data in a database. This includes how to create a new
database from code
(Code First development) and how to create code from an
existing database
(Database First development).
By : Eng. Mahmoud Hassouna
Email : M.hassuna2@gmail.com
How to create a
database from code
Entity Framework (EF) Core is an object-relational
mapping (ORM)
framework that allows you to map your entity classes
to the tables of a database.
The most common way to work with EF Core is to
code your entity classes and
a database context class first. Then, you can use EF
to create a new database
from these classes. This is called Code First
development, and it’s the approach
that you’ll learn about now.
How to code entity and
DB context classes
Entity classes represent the data structure for an app.
For example, a
bookstore has entities like books, authors, and
genres. As a result, a Bookstore
app needs entity classes to store data for those
entities.
To illustrate, figure 12-1 begins with two examples
that show the Book and
Author entity classes. You can create objects from
these classes to store the data
that’s needed by your app.
Description
• Entity classes define the data structure for the app
and map to tables in a relational
database. A database (DB) context class inherits the
DB Context class and includes
entity classes as DB Set properties.
• In the DB context class, the On Configuring()
method allows you to configure the
context, such as providing the connection string.
Usually, though, you do this in the
Startup's file.
• In the DB context class, the OnModelCreating()
method allows you to configure the
entity classes, such as providing initial seed data.
• To work with EF, you need to configure the
database context. To do that, you can
modify your appsettings.json and Startup.cs files as
described in figure 4-6 of chapter 4.
How to configure the
database
You can configure your entity classes so they
create the database tables you
want in three ways. First, you can configure them
by convention. Second, you
can configure them with data attributes. Third,
you can configure them by using
code.
When you configure by convention, you code
your classes following
established conventions, and EF takes it from
there. Figure 12-2 starts by
presenting some of these conventions. For
example, if you code a property
named Id (or ID) or join the name of the entity
and Id (or ID), EF treats that
property as the primary key of the database
table. Additionally, if this property .
Some of the conventions for configuration in EF Core
• A property named Id or ClassNameId is the primary key. If the
property is of the int type,
the key is an identity column and its value is generated automatically
by the database.
• A string property has a database type of nvarchar(max) and is
nullable.
• An int or double property is not nullable.
Some of the data attributes for configuration
Some of the Fluent API methods for configuration
How to manage
configuration files
The first code example in figure 12-3 shows the
OnModelCreating() method
of a context class with several statements that
configure a Book entity. The
first statement sets the ISBN property as the primary
key. This is necessary
because that property doesn’t follow the naming
convention for a primary key.
The second statement makes the Title property not
allow nulls and to have a
maximum length of 200 characters. And the third
statement uses the HasData()
method to provide seed data when the table is
created.
The statements in this example are for a single
entity that has two properties.
However, imagine how this code will grow as you
add more properties to this
Description
• If you have a lot of Fluent API configuration code in
the OnModelCreating() method
of your context class, it can become difficult to
understand and maintain. In that
case, it’s considered a best practice to divide this
code into one configuration file per
entity.
• To create a configuration file, you code a class
that implements the
IEntityTypeConfiguration<T> interface and its
Configure() method.
• The Configure() method accepts an
EntityTypeBuilder<T> object that represents the
entity being configured.
• To apply the configuration code, you pass a new
instance of the configuration class
to the ApplyConfiguration() method of the
ModelBuilder object that’s passed to the
OnModelCreating() method of the context class.
EF commands for working with a
database
In chapter 4, you learned how to use the NuGet
Package Manager Console
(PMC) to execute PowerShell commands such as
Add-Migration and Update-
Database to create a database from DB context
and entity classes. Figure 12-4
starts by reviewing how you open the PMC window.
Then, it summarizes the
Add-Migration and Update-Database commands,
as well as some additional
commands for working with a database. For any of
these commands to work,
your app needs to be configured to access a
database as shown in figure 4-6.
The first command, Add-Migration, generates a
migration file based on
your context and entity classes. By default, this
command creates the file with
the name you specify in the Migrations folder. If that
folder doesn’t exist, this
command creates it. If you want, you can use the -
OutputDir parameter to
specify another folder, as you’ll see shortly.
The second command, Remove-Migration,
removes the last migration file
from the Migrations folder. This only works, though, if
the migration file hasn’t
yet been applied to the database. If it has, you need
to use the Update-Database
command to revert the migration before you can
remove the migration file.
You’ll see an example of this in the next figure.
The third command, Update-Database, applies
any migration files that
haven’t yet been applied to the database. You can
also use the Update-Database
command to revert migrations as shown in the next
figure.
The fourth command, Drop-Database, deletes
the database. If this command
doesn’t work correctly, you can use SQL Server
Object Explorer or another
comparable tool to delete the database.
The fifth command, Script-Migration, generates a
SQL script based on one
or more migration files.
The sixth command, Scaffold-DbContext,
generates DB context and entity
classes from an existing
Description
• The PMC executes PowerShell commands to
create, apply, and revert migration files.
• A migration file contains C# code for creating or
updating database objects. Specifically,
each migration file has an Up() method with code
that runs when a migration is applied,
and a Down() method with code that runs when a
migration is reverted.
• The first time you run the Add-Migration command,
EF creates a file named
DbContextNameModelSnapshot.cs. This file contains
a “snapshot” of the current
database schema. When you add or remove
subsequent migrations, that file is updated.
How to use EF migration commands
Figure 12-5 presents some examples that use EF
commands in the Package
Manager Console (PMC) to work with the Bookstore
database. The first scenario
in this figure shows how to create and then update
the database. Step 1 creates a
migration file named Initial.cs in the Migrations folder.
Then, step 2 updates the
database. This applies the Initial migration and
creates the database.
How to create and then update a
database
1. Create a migration file named Initial based
on the context and entity classes
presented earlier in this chapter by entering this
command:
PM> Add-Migration Initial
2. Create a database based on the migration
file by entering this command:
PM> Update-Database
3. Add a property named Discount of the
double type to the Book class.
4. Create a migration file named AddDiscount
by entering this command:
PM> Add-Migration AddDiscount
5. Review the migration file and note that the
Discount property doesn’t accept nulls.
6. Change the Discount property in the Book
class to the data type of double?.
7. Generate another migration file by entering this
command:
PM> Add-Migration MakeDiscountNullable
8. Apply the migration files to the database by
entering this command:
PM> Update-Database
How to revert one or more migrations
1. To revert changes to the database by running the
Down() method in every migration
file that comes after the AddDiscount file, enter this
command:
PM> Update-Database AddDiscount
2. To remove the unapplied MakeDiscountNullable
migration file that was reverted in
step 1 from the Migrations folder, enter this
command:
PM> Remove-Migration
.
How to revert all the migrations
1. To revert all changes that have been applied to
the database by running the Down()
method in all the migration files, enter this command:
PM> Update-Database 0
2. To remove all migration files from the Migrations
folder, enter the Remove-
Migration command repeatedly. Or, manually
delete all the migration files from the
Migrations folder, including the snapshot file
Note
• When you run one of these commands, you might
see a warning to update your
tools. To do that, you can run an Install-Package
command like this:
PM> Install-Package
Microsoft.EntityFrameworkCore.Tools -Version 3.0.1
However, you should use the version number that’s
included in the warning message.
How to work with
relationships
So far, you have learned how to code
entity classes and use those classes to
create database tables. However,
entities are typically related to other
entities.
Similarly, the corresponding database
tables are typically related to other
tables.
For example, a genre might have
many books. An author might have a
detailed
bio. A book might have several
authors. And an author might have
several books.
That’s why the next few figures show
how to configure relationships
between
entities and their corresponding tables.
How entities are
related
Entities can be related to other entities
by values in specific properties. The
two entities at the top of figure 12
-
6
illustrate this concept. Here, the Genre
entity is related to the Book entity
because they share a GenreId property.
Typically, relationships exist between the
primary key in one entity and the
foreign key in another entity. A primary
key (PK) uniquely identifies an instance
of an entity. A foreign key (FK) refers to a
primary key in another entity. In
this figure, the diagram shows that the
GenreId property in the Genre entity is
a primary key, and the GenreId property
in the Book entity is a foreign key. In
other words, the foreign key in the Book
entity refers to the primary key of the
Genre entity.
Description
•The PMC executes PowerShell
commands to create, apply, and revert
migration files.
•A migration file contains C# code for
creating or updating database objects.
Specifically,
each migration file has an Up() method
with code that runs when a migration is
applied,
and a Down() method with code that
runs when a migration is reverted.
•The first time you run the Add-
Migration command, EF creates a file
named
DbContextNameModelSnapshot.cs. This
file contains a “snapshot ”of the current
database schema. When you add or
remove subsequent migrations, that file
is updated.
How to configure a one-to-
many relationship
The first code example in figure 12
-
7
shows the simplest way to configure
a one-to-many relationship by
convention. Here, the relationship is
created by
nesting a Genre entity as a property of
the Book entity.
This is a common way of creating a
one-to-many relationship. However,
this approach can cause some
problems. For example, you need to
specifically
include the nested entity and all of its
data even if all you need is the ID
value.
.
As a result, it’s typically a better
practice to fully define the
relationship by
explicitly coding the foreign key
property. The second example in this
figure
shows how this works. Here, the Book
entity explicitly includes the foreign
key
In addition, you can run into
problems if the nested entity
has data validation
requirements
How to configure a one-
to-many relationship by
convention
public class Book{
public int BookId { get; set; }
public Genre Genre { get; set; }
}
public class Genre{
public int GenreId { get; set; }
public string Name { get; set; }
}
How to fully define the one-to-many
relationship by convention
)recommended)
public class Book{
public int BookId { get; set; }
public int GenreId { get; set; } // foreign key property
public Genre Genre { get; set; } // navigation
property
}
public class Genre{
public int GenreId { get; set; }
public string Name { get; set; }
public ICollection<Book> Books { get; set; } //
navigation property
}
How to configure a one-to-
many relationship with
attributes
public class Book{
public int BookId { get; set; }
public int CategoryId { get; set; }
ytreporp KF // ]("dIyrogetaC")yeKngieroF[
ssalc kooB ni
ytreporp van // ]("skooB")ytreporPesrevnI[
ssalc erneG ni
public Genre Category { get; set; }
}
public class Genre{
public int GenreId { get; set; }
public string Name { get; set; }
van // ]("yrogetaC")ytreporPesrevnI[
ssalc kooB ni ytreporp
public ICollection<Book> Books { get; set;
{
}
How to configure a one-to-many
relationship with the Fluent API
protected override void
OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book >
)(
.HasOne(b => b.Genre) // nav property in Book class
.WithMany(g => g.Books); // nav property in Genre
class
}
How to configure a one-
to-one relationship
If an entity has a one-to-one
relationship with another entity, the
data in the
two entities could be stored in one
entity, and the data in the two
underlying
database tables could be stored in
one table. This is useful when you
need to
store large objects such as images,
sound, and videos. Then, you can
store those
objects in a separate entity and
underlying table and only join them
when you
need to retrieve the large object.
A one-to-one
relationship can also
be useful when an
entity has both
essential data and
data that is frequently
null. For example,
suppose that in most
cases, the only data
you need to store for
an author is the name.
The first example in
figure 12
-
8 shows how
to configure a fully
defined
one-to-one
relationship by
convention. This
example works
similarly to the
second example in
the previous figure. To
start, each entity
configures its
primary key by
convention and
defines a navigation
property.
How to configure a one-to-
one relationship by
convention
public class Author{
public int AuthorId { get; set; } //
primary key property
public string Name { get; set; }
public AuthorBio Bio { get; set; } //
navigation property
}
public class AuthorBio{
public int AuthorBioId { get; set; } //
primary key property
public int AuthorId { get; set; } // foreign
key property
public DateTime? DOB { get; set; }
public Author Author { get; set; } //
navigation property
}
How to configure a one-to-
one relationship with attributes
public class Author{
//same as above
}
public class AuthorBio{
[Key]
public int AuthorId { get; set; } // PK and
FK property
public DateTime? DOB { get; set; }
ytreporp KF // ]("dIrohtuA")yeKngieroF[
public Author Author { get; set; } //
navigation property
}
How to configure a one-to-
one relationship with the
Fluent API
protected override void
OnModelCreating(ModelBuilder
modelBuilder){
modelBuilder.Entity<Author >
)(
.HasOne(a => a.Bio) // nav property in
Author class
.WithOne(ab => ab.Author) // nav
property in AuthorBio class
.HasForeignKey<AuthorBio>(ab =>
ab.AuthorId); // FK property
}
How to configure a one-to-
one relationship within a single
table
protected override void
OnModelCreating(ModelBuilder
modelBuilder){
modelBuilder.Entity<Author >
)(
.HasOne(a => a.Bio)
.WithOne(ab => ab.Author)
.HasForeignKey<AuthorBio>(ab =>
ab.AuthorId);
modelBuilder.Entity<Author>().ToTable("A
uthors");
modelBuilder.Entity<AuthorBio>().ToTable
("Authors");
}
How to configure a
many-to-many
relationship
A many-to-many relationship is
configured by using an
intermediate entity
called a join entity or a linking
entity. This creates an
intermediate table in the
database called a join table or a
linking table.
The linking entity has a one-to-
many relationship with the two
entities in
the many-to-many relationship. In
other words, a many-to-many
relationship is
broken down into two one-to-
many relationships.
Unlike the other two relationships, a many-to-many
relationship can only be
configured with the Fluent API. Figure 12
-
9 shows how
to configure a many-to-
many relationship between the Book entity and the
Author entity.
The first example in this figure presents the Book and
Author entities that
have a many-to-many relationship. Each of these
entities contains a navigation
property of a collection of BookAuthor entities. These
navigation properties
represent the many side of the two one-to-many
relationships.
The entities to be linked
public class Book{
public int BookId { get; set; }
public string Title { get; set; }
//navigation property to linking
entity
public ICollection<BookAuthor>
BookAuthors { get; set; }
}
public class Author{
public int AuthorId { get; set; }
public string Name { get; set; }
//navigation property to linking
entity
public ICollection<BookAuthor>
BookAuthors { get; set; }
}
The linking entity
public class BookAuthor{
//composite primary key
public int BookId { get; set; } // foreign
key for Book
public int AuthorId { get; set; } // foreign
key for Author
//navigation properties
public Book Book { get; set; }
public Author Author { get; set; }
}
How to configure a many-to-many
relationship with
the Fluent API
protected override void
OnModelCreating(ModelBuilder modelBuilder){
//composite primary key for BookAuthor
modelBuilder.Entity<BookAuthor >
)(
.HasKey(ba => new { ba.BookId, ba.AuthorId });
Description
•A many-to-many relationship requires a third entity
known as a join entity or
linking entity. The linking entity has two one-to-many
relationships with the
entities to be joined.
•The linking entity has a composite primary key that
consists of the primary key of
each linked entity.
How to control delete behavior
When an app deletes a row from a database,
related rows that are dependent
on that row can become corrupted. For example, if
an app deletes the genre
“Novel ”from the Genre table, all the rows in the
Books table whose genre is
“Novel ”have invalid data.
You can use the OnDelete() method of the Fluent
API to configure how
dependent rows are handled when a parent row is
deleted. This method accepts a
value of the DeleteBehavior enum as an argument.
The table at the top of figure
12
-
10 shows the values of this enum.
Description
•When a row is deleted from a database, related rows that are
dependent on that row
can become corrupted. To prevent this, most databases throw an
exception when
you try to delete a row that has dependent rows.
•Most databases also allow you to configure cascading deletes,
which cause
dependent rows to be automatically deleted.
•You can use the OnDelete() method of the Fluent API to configure
how dependent
rows are handled when a parent row is deleted.
The Bookstore
database classes
Now that you know how to
code entity classes that are
related to each
other, you’re ready to see the
entity, context, and
configuration classes for the
Bookstore website that’s
presented in the next
chapter.
The entity classes
Figure 12
-
11 shows the
classes for the Author,
Book, BookAuthor, and
Genre entities that are
used by the Bookstore
website. For the most
part, these
entities are configured
by convention. For
example, the Author,
The context and configuration classes
Figure 12
-
12 shows the context class for the Bookstore
website. This class
begins by defining the DbSet properties for the
Author, Book, BookAuthor, and
Genre entities presented in figure 12
-
11
.
The Author class
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace Bookstore.Models
The Book class
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
The BookAuthor class
namespace Bookstore.Models
{
public class BookAuthor
{
//composite primary key and foreign keys
public int BookId { get; set; }
public int AuthorId { get; set; }
//navigation properties
public Author Author { get; set; }
public Book Book { get; set; }
}
}
The Genre class
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
The BookstoreContext class
using Microsoft.EntityFrameworkCore;
namespace Bookstore.Models
{
public class BookstoreContext : DbContext
{
public
BookstoreContext(DbContextOptions<BookstoreCont
ext> options)
:base(options)
The SeedAuthors class
using Microsoft.EntityFrameworkCore;
using
Microsoft.EntityFrameworkCore.Metadata.Builders;
The SeedGenres class
using Microsoft.EntityFrameworkCore;
using
Microsoft.EntityFrameworkCore.Metadata.Builders;
How to create code from a database
So far in this book, you have learned how to use
Code First development
to create a database from your context and entity
classes. Sometimes, though,
you already have a database. In that case, you
need to create context and entity
classes from your database. This approach is called
Database First development.
How to generate DB context and entity
classes
To get started with Database First development, you
can use the Scaffold-
DbContext command to generate the code for the
context and entity classes. The
table at the top of figure
Description
•You can use the Scaffold-Database command to
generate context and entity classes
based on an existing database.
•You can omit the flag for required parameters.
•When specifying the connection string, it’s
considered a best practice to use a
connection string that’s stored in the appsettings.json
file.
How to configure a generated DB
context class
When you use the Scaffold-DbContext command to
generate context and
entity classes, the OnConfiguring() method of the
context class contains the
connection string and provider information. Usually,
though, you need to make
some adjustments before your project is ready to run.
The OnConfiguring() method in the
generated context class
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("name=BookstoreContext
"(;
}
} The Startup.cs file that injects the DB
context into the app
using Microsoft.EntityFrameworkCore;
using Bookstore.Models.DataLayer;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
Description
•When you use the Scaffold-
Database command to
create DB context and entity
classes, it adds configuration
code to the OnConfiguring()
method of the context
class.
•To get the DB context to
work, you need to edit the
Startup.cs file so it injects the
context into the app.
•For this code to work, the
appsettings.json file must store
the specified connection
string. If it doesn’t, you can
edit this file so it contains the
specified connection
string.
How to modify a generated
entity class
When you work with Database First
development, it’s common to enhance
the entity classes that it generates. For
instance, you may want to add data
validation attributes. Or, you may want to
add more properties such as read-only
properties that indicate the state of the
entity.
However, if you make changes to the database, it’s
common to need to regenerate the entity classes.
When this happens, though, you lose any additions
you’ve made to those classes. Fortunately, you can
use partial classes to avoid this problem.
A partial class generated by EF Database First
namespace Bookstore.Models.DataLayer
{
public partial class Books
{
[Key]
public int BookId { get; set; }
[Required]
[StringLength(200)]
public string Title { get; set; }
public double Price { get; set; }
...
}
}
A partial class that adds a metadata class and
a read-only property
using Microsoft.AspNetCore.Mvc;
namespace Bookstore.Models.DataLayer
{
[ModelMetadataType(typeof(BooksMetadata))]
public partial class Books
{
public bool HasTitle => !string.IsNullOrEmpty(Title);
}
}
How to work with data in a database
Chapter 4 introduced some basic skills for working with the data in a
database. Now, you’ll review those skills and learn some new ones.
How to query data
You can use LINQ to Entities to query the DbSet properties of a context
class. The first code example in figure 12-17 shows a context property
named context that’s used by subsequent examples. This context has
DbSet properties named Books and Authors.
The second set of examples shows how to create and execute a query. When you do
that using two statements, you create a query variable of the IQueryable type that
holds a query, not data. Then, when you execute the query, the data is retrieved from
the database and stored in an object of the IEnumerable type. When you do that
using one statement, you can use the var keyword to declare the variable where the
results are stored so the compiler will automatically determine the type. In this case, it
uses the List type, which is usually what you want.
The context class used in the following examples
private BookstoreContext context { get; set; }
Code that creates and executes a query
In two statements
IQueryable query = context.Books; // create the query
IEnumerable books = query.ToList(); // execute the query
In one statement
var books = context.Books.ToList(); // implicit typing is common
Code that sorts the results
Sort by Title in ascending order (A to Z)
var books = context.Books.OrderBy(b => b.Title).ToList();
Sort by Title in descending order (Z to A)
var books = context.Books.OrderByDescending(b => b.Title).ToList();
Code that filters the results
Get a single book by ID
var book = context.Books.Where(b => b.BookId == 1).FirstOrDefault();
var book = context.Books.Find(1); // shortcut for above
Get a list of books by genre
var books = context.Books.Where(b => b.Genre.Name == "Mystery").ToList();
Get a list of books in a price range
var books = context.Books.Where(b => b.Price > 10 && b.Price < 20).ToList();
Conditionally filter by multiple criteria
// build the query (can’t use implicit typing here)
IQueryable query = context.Books;
if (selectedMaxPrice != null)
query = query.Where(b => b.Price < selectedMaxPrice);
if (selectedGenre != null)
query = query.Where(b => b.Genre.Name == selectedGenre);
// execute the query
var books = query.ToList();
How to work with projections and related entities
When you query data as described in the last figure, the query retrieves all of the
properties in an entity, whether you need them or not. Usually, this is fine. Sometimes,
though, you may have entities with so many properties that it’s inefficient to retrieve all
of them. Or, you may have properties with sensitive data, like passwords, that you don’t
want to retrieve.
One way to limit the number of properties returned is to use table splitting as mentioned
earlier in this chapter. Another way to do that is to use projections. A projection uses the
LINQ Select() method to retrieve only some of an entity’s properties.
One way to create a projection is to use an anonymous type like the one shown in
the first example in figure 12-18. This type consists of the AuthorId property of the Authors
entity and a property that’s a combination of the FirstName and LastName properties.
Because the second property doesn’t match an entity property, it must be explicitly
named as shown here.
Anonymous types are convenient and quick, but they can be hard to work with in
an MVC app. That’s because they’re hard to pass to views, as the error message below
the first example shows.
How to create a projection with an anonymous type
var authors = context.Authors
.Select(a => new {
a.AuthorId, // can infer property name
Name = a.FirstName + ' ' + a.LastName // must specify property name
})
.ToList();
Error when you pass the projection to a view that expects a list of Authors
InvalidOperationException: The model item passed into the ViewDataDictionary
is of type 'System.Collections.Generic.List`1[<>f_AnonymousType0`2 [System.
Int32,System.String]]', but this ViewDataDictionary instance requires a model
item of type System.Collections.Generic.IEnumberable`1 [BookList.
Models.Author]'.
How to create a projection with a concrete type
The concrete type
public class DropdownDTO
{
public string Value { get; set; }
public string Text { get; set; }
}
The projection
var authors = context.Authors
.Select(a => new DropdownDTO {
Value = a.AuthorId.ToString(),
Text = a.FirstName + ' ' + a.LastName
})
.ToList();
Description
• A projection allows you to retrieve a subset of the properties of an entity.
• To create a projection, you can use an anonymous type or a concrete type.
However, anonymous types can be hard to use in views.
• You can use the Include() and ThenInclude() methods to include related entities
in your query. These methods accept lambda expressions to identify the entities to
include.
How to insert, update, and delete data
In addition to querying data, you need to know how to insert, update, and
delete data. Fortunately, the methods of the DbSet and DbContext classes make that
easy to do. You learned about these methods in chapter 4, but now figure 12-19
reviews them and explains them in more detail.
The Add(), Modify(), and Delete() methods of the DbSet class only affect the
DbSet. The Add() method adds a new entity to the DbSet and marks it as Added, and
the Update() and Remove() methods mark an existing entity as Modified or Deleted. To
mark an entity, EF uses the EntityState enum. In most cases, you can let EF mark these
changes for you automatically. However, you can also set the state of an entity
manually, if necessary.
The SaveChanges() method of the DbContext class, by contrast, affects the
underlying database. When you call this method, all the Added, Modified, and
Deleted actions pending in the DbSet properties are executed against the database.
The code examples show how to use these methods. The first example adds a
new entity, the second and third examples update an existing entity, and the fourth
example deletes an existing entity. While this code is easy to follow, you should notice a
few things.
First, although these examples all perform a single action and then call SaveChanges(),
that’s not a requirement. You can make several calls to Add(), Update(), or Delete() before you call
SaveChanges(). In fact, in some scenarios, it can improve efficiency to only call SaveChanges() once
after all your modifications are done.
Second, when it comes to updates, the second example shows how to work with a disconnected
scenario because it’s commonly used with web apps. In this scenario, the Edit() action method gets
the Book entity from an HTTP POST request. As a result, the code in this figure must use Update() to
update the state of the entity before calling SaveChanges().
With desktop apps, it’s more common to use a connected scenario. However, a connected
scenario can also occur in web apps as shown by the third example. Here, the first statement of the
Edit() action method uses the Find() method to retrieve the Book entity from the database. This causes
the context to track the state of this entity. As a result, you don’t need to call the Update() method.
Instead, this code just sets the new price for the book and calls the SaveChanges() method. This
generates SQL that’s slightly more efficient than calling Update(). However, if you wanted to make the
code easier to read and understand, you could add a statement that calls Update() before calling
SaveChanges().
Finally, when you delete a parent entity in EF Core, it’s common for all of the related child entities
to be automatically deleted too. That’s because EF Core enables cascading deletes by default for
foreign keys that are not nullable. However, if you disable cascading deletes as described earlier in
this chapter, you’ll need to delete all the child entities before you can delete the parent entity.
Code that adds a new entity
[HttpPost]
public IActionResult Add(Book book)
{
context.Books.Add(book);
context.SaveChanges();
return RedirectToAction("List", "Book");
}
Code that updates an existing entity in a disconnected scenario
[HttpPost]
public IActionResult Edit(Book book) // Book object is disconnected
{
context.Books.Update(book); // call to Update() required
context.SaveChanges();
return RedirectToAction("List", "Book");
}
Code that updates an existing entity in a connected scenario
[HttpPost]
public IActionResult Edit(int id, double price)
{
Book book = context.Books.Find(id); // Book object is connected
book.Price = price; // call to Update() not required
context.SaveChanges();
return RedirectToAction("List", "Book");
}
How to handle concurrency conflicts
Concurrency allows two or more users to work with a
database at the same time. However, if two users
retrieve and then attempt to update the same entity
(row in a table), their updates may conflict with
each other, and you need to handle this
concurrency conflict.
In EF Core, you have two options for concurrency control.
The default option is called “last in wins”. This option
doesn’t perform any checking. Instead, the last update
overwrites any previous changes.
In some cases, this option is adequate, but it can lead to
corrupted data.
The other option is called optimistic concurrency.
It checks whether a row has been changed since it was
retrieved. If so, EF refuses the update or deletion and throws an
exception. Then, the app can handle this exception.
You can use data attributes or the Fluent API to configure your
apps to use optimistic concurrency.
How to simulate a concurrency conflict
var book = context.Books.Find(1); // get a book from the database
book.Price = 14.99; // change price in memory
context.Database.ExecuteSqlRaw( // change price in the database
"UPDATE dbo.Books SET Price = Price + 1 WHERE BookId = 1");
context.SaveChanges();
Description
• A concurrency conflict is when data is modified after it’s retrieved for editing or
deletion.
• A rowversion property lets you check all the properties in an entity for conflicts
and must be an array of bytes.
• The DbContext class has a Database property whose ExecuteSqlRaw() method
can be used to simulate concurrency conflicts.
• A concurrency token lets you check an individual property for conflicts.
How handle a concurrency exception
If you’ve enabled optimistic concurrency and an update or delete operation fails
because of a concurrency problem, EF throws a concurrency exception. To handle this
exception, you call the SaveChanges() method within the try block of a try-catch
statement, and you include a catch block that catches the exception.
Figure 12-21 shows a controller action method named Edit() that handles a
concurrency exception. Here, within the catch block, this code uses the Entries property
of the concurrency exception to get the current values of the entity being updated. The
Entries property is a collection of DbEntityEntry objects, but it only has one entry here. As a
result, the code uses the LINQ Single() method to retrieve the entry. If you want to be
more cautious, you can use the SingleOrDefault() method and check for null.
The DbEntityEntry class has properties named CurrentValues and OriginalValues, but
those properties typically aren’t useful in a disconnected scenario. Instead, this example
uses the GetDatabaseValues() method to retrieve the current values of the entity from
the database.
After calling the GetDatabaseValues() method, the code checks whether the
return value is null. If it is, the entity has been deleted and is no longer in the database. As
a result, the code adds a class-level error to the ModelState object notifying the user that
the row no longer exists.
Some of the code in the Edit view
@* both primary key and row version value needed for edit *@
<input type=“hidden” asp-for=“Book ID”/>
<button type=“submit” class=“btn”>submit</button>
Description
• The DbUpdateConcurrencyException is thrown when there’s a
concurrency conflict.
• The DbUpdateConcurrencyException has an Entries property
that provides a way to get the new database values for the
row that’s being saved.
<input type=“hidden” asp-for=“RowVersion”/>
So far, the EF Core code you’ve seen has been in the action
methods of controllers. Usually, though, this code should be in its own
data layer. This makes your code easier to test. In addition, it allows
you to store your data access code in a separate project. Finally, it
makes it possible to change from EF to another ORM framework
without affecting the rest of your code.
How to encapsulate your EF code
A class that adds an extension method to the Iqueryable <T> interface
public static class QueryExtensions
{
public static Iqueryable <T> <T>(this IQueryable <T> query,
int pageNumber, int pageSize)
{
return query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
}
}
PageBy
How to use a generic query options class
In the previous figure, you learned how to code a data
access class that provides two methods for querying: one for
retrieving a page of data and one for sorting and filtering data.
However, these methods might cause logistical problems. For
instance, what if you don’t need to sort the data? What if you
don’t need to filter the data? Where does paging fit?
One way to avoid these problems is to code a generic class
for query options like the one presented in the first example of
figure 12-23. Here, the QueryOptions class begins by defining public
lambda expression properties for sorting and filtering and public int
properties for paging. Then, it defines a private array for storing
strings that indicate any related entities the query should include.
The write-only Includes property accepts a comma-separated
string, removes any spaces, splits the string at the commas, and
stores the resulting string array in the private field. The method
named GetIncludes() is used to return the value of the private field.
Or, if the private field is null, it returns an empty string array.
A generic query options class
using System.Linq.Expressions;
public class QueryOptions <T>
{
// public properties for sorting, filtering, and paging
public Expression<Func> OrderBy { get; set; }
public Expression<Func> Where { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
// public write-only property for includes private string array
private string[] includes;
public string Includes {
set => includes = value.Replace(" ", "").Split(',');
}
// public method returns includes array
public string[] GetIncludes() => includes ?? new string[0];
// read-only properties
public bool HasWhere => Where != null;
public bool HasOrderBy => OrderBy != null;
public bool HasPaging => PageNumber > 0 && PageSize > 0;
}
How to use the repository pattern
One popular way to implement a data layer is to use
the repository pattern. This pattern encapsulates data
code within a data access layer and also uses
interfaces to provide a layer of abstraction. One benefit
of this pattern is that it makes it easier to automate
testing as shown in chapter 14.
The first example in figure 12-24 presents a simple
generic interface named IRepository that you can
create to work with a repository. This interface has six
methods. The first two query data, the next three modify
data, and the last one saves changes to the data.
When using the repository pattern, you should use
one repository per entity. For example, a Bookstore app
shouldn’t use one big Bookstore repository. Instead, it
should use a Book repository, an Author repository, a
Genre repository, and so on.
You can use this generic
repository class to create
various entity collections.
For example, this code creates
an Author repository:
var data = new Repository(ctx);
The generic IRepository interface
public interface IRepository <T>where T : class {
Ienumerable <T> List (QueryOptions <T> options);
T Get(int id);
void Insert(T entity);
void Update(T entity);
void Delete(T entity);
void Save();
} A controller that uses the generic Repository class
public class AuthorController : Controller {
private Repository data { get; set; }
public AuthorController(BookstoreContext ctx) =>
data = new Repository(ctx);
public ViewResult Index() {
var authors = data.List(new QueryOptions <Auther> {
OrderBy = a => a.FirstName
});
return View(authors);
}
...
}
How to use the unit of work pattern
Although some programmers think that including a
Save() method in each repository is OK, others think that a
repository should never have a Save() method. Either way, if
you need to coordinate between multiple repositories,
including a Save() method in each repository doesn’t work. In
those cases, you can combine the repository pattern with the
unit of work pattern.
The unit of work pattern adds a central class that has
repository objects as properties. Then, the central class passes
each repository its context object, so they all share the same
DB context. Finally, the central class includes a Save()
method that calls the SaveChanges() method of the DB
context. That way, when you call the Save() method of the
central class, EF executes all changes in all repositories
against the database. Then, if one change fails, they all fail.
The first code example in
figure 12-25 shows a unit of
work class for a Bookstore
app.
This class implements an
interface that specifies all
of its methods.
This isn’t required, but it’s
generally considered a
good practice since it
makes it easier to
automate testing.
A controller that uses the unit of work class to update a book
public class BookController : Controller
{
private BookstoreUnitOfWork data { get; set; }
public BookController(BookstoreContext ctx) =>
data = new BookstoreUnitOfWork(ctx);
[HttpPost]
public IActionResult Edit(BookViewModel vm) {
if (ModelState.IsValid){
data.DeleteCurrentBookAuthors(vm.Book);
data.AddNewBookAuthors(vm.Book, vm.SelectedAuthors);
data.Books.Update(vm.Book);
data.Save();
...
}
...
}
Perspective
This chapter showed how to use EF Core to work with data in a database,
including how to encapsulate your EF code in a data layer. This presents the most
important classes of the data layer that are used by the Bookstore website
presented in the next chapter. As a result, studying that app is a great way to get
started with EF Core.
Terms
entity class
database (DB) context class
Entity Framework (EF) Core
object relational mapping (ORM)
Code First development
Database First development
Fluent API
primary key (PK)
foreign key (FK)
one-to-many relationship
many-to-many relationship
one-to-one relationship
navigation property
table splitting
join entity
linking entity
join table
linking table
composite primary key
cascading delete
LINQ to Entities
projection
data transfer object (DTO)
disconnected scenario
connected scenario
concurrency
concurrency conflict optimistic
concurrency rowversion
property concurrency token
extension method
repository pattern
CRUD (Create, Read, Update,
Delete)
unit of work pattern
Exercise 12-1 Review the data layer
of an app and improve it
Review the domain model and configuration files
1. Open the Ch12Ex1ClassSchedule web app in the ex_starts directory.
2. Open the Models/DomainModels folder and review the code in the
three class files it contains.
3. Open the Models/Configuration folder and review the code in the
three configuration files it contains.
Review the migration file
4. Open the Migrations folder and review the code in the Initial migration file.
5. Note how the migration file determines the primary and foreign keys based on
how the properties in the domain model are coded (configuration by convention).
Create the database
6. Open the Package Manager Console and enter the Update-Database
command to run the migration and create the database.
7. Take a moment to review the SQL code in the console after the
command runs.
8. Run the app and review the data it displays for classes and teachers.
Test the cascading delete behavior of the Teacher object
9. Run the app and navigate to the Show All Teachers page. Click the Add
Teacher link and add your name as a teacher.
10. Navigate to the Show All Classes page. Click the Add Class link and add
a new class with yourself as the teacher.
11. Review the updated class list to make sure it displays the class you just
added.
12. Navigate back to the Show All Teachers page and delete your name.
13. Navigate back to the Show All Classes page, and note that the class you
previously added has also been deleted.
Restrict the cascading delete behavior of the Teacher object
14. In the Models/DomainModels folder, open the configuration file for the domain
model named Class.
15. Add code to change the delete behavior for the Teacher foreign key to Restrict.
16. Open the Package Manager Console and use the Add-Migration command to
create a new migration file. When you do that, use a descriptive name for the
migration file.
17. Review the code in the migration file that’s generated.
18. Enter the Update-Database command to apply the migration to the database.
19. Repeat steps 9 through 13 to test the cascading delete behavior. When you
attempt to delete your name from the teacher list, you should get an error message.
20. Navigate to the Show All Classes page, find the class you added, click on Edit,
and change the teacher to another teacher.
21. Navigate to the Show All Teachers page and delete your name from the teacher
list. This time, the app should let you.
Update the app to use the unit of work pattern
22. Open the Controllers folder and review the three controller class files. Note that
two of them initialize more than one Repository class in the constructor.
23. Open the Models/DataLayer folder, add an interface named
IClassScheduleUnitOfWork and a class named ClassScheduleUnitOfWork.
24. Adjust the namespaces of the interface and class to match the namespaces of
the other classes in the DataLayer folder.
25. Code the interface to have read-only properties for a Class, Teacher, and Day
repository and a Save() method that returns void.
26. Code the class to implement the interface, have a private ClassScheduleContext
object that it gets in its constructor, initialize and return Repository objects in its
properties, and call the context object’s SaveChanges() method in the Save()
method.
27. Update the Home and Class controllers to use the unit of work class.
28. Run the app and make sure it still works the same.
Change how the list of classes is ordered
29. Open the Home controller and review the code in its Index() action method.
Note that the classes are ordered by day on first load and ordered by time when
filtering by day.
30. Run the app. When it displays all classes, note that it doesn’t display the classes
for Monday in ascending order by time. Then, click on the filter link for Monday and
note that it does display the classes in ascending order by time.
31. In the Models/DataLayer folder, open the QueryOptions class. Note that this
version is shorter than the QueryOptions class presented in the chapter. That’s
because it doesn’t provide for paging.
32. Add a new property named ThenOrderBy that works like the OrderBy property.
33. Add a new read-only property named HasThenOrderBy that works like the
HasOrderBy property.
34. Open the Repository class and find the List() method. Update the code that
handles ordering so it uses the ThenOrderBy property like this:
if (options.HasOrderBy) {
if (options.HasThenOrderBy) {
query = query.OrderBy(options.OrderBy).ThenBy(options.ThenOrderBy);
}
else {
query = query.OrderBy(options.OrderBy);
}
}
35. In the Home controller, update the Index() action method to use the new
property to sort by time as well as day on first load.
36. Repeat step 30. The class times should be in ascending order on both pages.
Add an overload for the Get() method in the Repository class
37. Open the Class controller and review the private helper method named GetClass().
Note that it uses the List() method of the Repository class to get an IEnumerable with
one Class item. Then, it uses the LINQ FirstOrDefault() method to retrieve that item.
38. In the Models folder, open the IRepository interface and add a second Get()
method that accepts a QueryOptions object.
39. Open the Repository class and implement the new Get() method. To do that, you
can copy the code from the repository’s List() method that builds a query, leaving
out the code for ordering the list. Then, you can return the single object by calling
the FirstOrDefault() method instead of the List() method.
40. In the Class controller, update the GetClass() method to use the new Get() method.
41. Run the app and test it. It should work the same as it did before.

Contenu connexe

Tendances

Repository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity FrameworkRepository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity Framework
Akhil Mittal
 
Yui3.4.0 What's new
Yui3.4.0 What's newYui3.4.0 What's new
Yui3.4.0 What's new
enmaai
 

Tendances (17)

Simple ado program by visual studio
Simple ado program by visual studioSimple ado program by visual studio
Simple ado program by visual studio
 
Programming Without Coding Technology (PWCT) Environment
Programming Without Coding Technology (PWCT) EnvironmentProgramming Without Coding Technology (PWCT) Environment
Programming Without Coding Technology (PWCT) Environment
 
Oracle9i reports developer
Oracle9i reports developerOracle9i reports developer
Oracle9i reports developer
 
ADO Controls - Database Usage from Exploring MS Visual Basic 6.0 Book
ADO Controls - Database Usage from Exploring MS Visual Basic 6.0 BookADO Controls - Database Usage from Exploring MS Visual Basic 6.0 Book
ADO Controls - Database Usage from Exploring MS Visual Basic 6.0 Book
 
Generate Excel documents with Rational Publishing Engine 1.1.2 and Reporting ...
Generate Excel documents with Rational Publishing Engine 1.1.2 and Reporting ...Generate Excel documents with Rational Publishing Engine 1.1.2 and Reporting ...
Generate Excel documents with Rational Publishing Engine 1.1.2 and Reporting ...
 
Resolve dependency of dependencies using Inversion of Control and dependency ...
Resolve dependency of dependencies using Inversion of Control and dependency ...Resolve dependency of dependencies using Inversion of Control and dependency ...
Resolve dependency of dependencies using Inversion of Control and dependency ...
 
Create an android app for database creation using.pptx
Create an android app for database creation using.pptxCreate an android app for database creation using.pptx
Create an android app for database creation using.pptx
 
Sql Server 2014 Course Content
Sql Server 2014 Course ContentSql Server 2014 Course Content
Sql Server 2014 Course Content
 
Data weave
Data weave Data weave
Data weave
 
Dataweave by nagarjuna
Dataweave  by nagarjunaDataweave  by nagarjuna
Dataweave by nagarjuna
 
Android User Interface Tutorial: DatePicker, TimePicker & Spinner
Android User Interface Tutorial: DatePicker, TimePicker & SpinnerAndroid User Interface Tutorial: DatePicker, TimePicker & Spinner
Android User Interface Tutorial: DatePicker, TimePicker & Spinner
 
VB6 Using ADO Data Control
VB6 Using ADO Data ControlVB6 Using ADO Data Control
VB6 Using ADO Data Control
 
Actionview
ActionviewActionview
Actionview
 
Repository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity FrameworkRepository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity Framework
 
Data weave
Data weaveData weave
Data weave
 
Yui3.4.0 What's new
Yui3.4.0 What's newYui3.4.0 What's new
Yui3.4.0 What's new
 
Using Rational Publishing Engine to generate documents from Rational Rhapsody
Using Rational Publishing Engine to generate documents from Rational RhapsodyUsing Rational Publishing Engine to generate documents from Rational Rhapsody
Using Rational Publishing Engine to generate documents from Rational Rhapsody
 

Similaire à Murach: How to use Entity Framework EF Core

Learning MVC Part 3 Creating MVC Application with EntityFramework
Learning MVC Part 3 Creating MVC Application with EntityFrameworkLearning MVC Part 3 Creating MVC Application with EntityFramework
Learning MVC Part 3 Creating MVC Application with EntityFramework
Akhil Mittal
 
MVC Application using EntityFramework Code-First approach Part4
MVC Application using EntityFramework Code-First approach Part4MVC Application using EntityFramework Code-First approach Part4
MVC Application using EntityFramework Code-First approach Part4
Akhil Mittal
 
Rational Publishing Engine with Rational DOORS
Rational Publishing Engine with Rational DOORSRational Publishing Engine with Rational DOORS
Rational Publishing Engine with Rational DOORS
GEBS Reporting
 

Similaire à Murach: How to use Entity Framework EF Core (20)

Ef code first
Ef code firstEf code first
Ef code first
 
Visual Basic.Net & Ado.Net
Visual Basic.Net & Ado.NetVisual Basic.Net & Ado.Net
Visual Basic.Net & Ado.Net
 
Intake 37 ef2
Intake 37 ef2Intake 37 ef2
Intake 37 ef2
 
Create Components in TomatoCMS
Create Components in TomatoCMSCreate Components in TomatoCMS
Create Components in TomatoCMS
 
MCS,BCS-7(A,B) Visual programming Syllabus for Final exams @ ISP
MCS,BCS-7(A,B) Visual programming Syllabus for Final exams @ ISPMCS,BCS-7(A,B) Visual programming Syllabus for Final exams @ ISP
MCS,BCS-7(A,B) Visual programming Syllabus for Final exams @ ISP
 
unit 3.docx
unit 3.docxunit 3.docx
unit 3.docx
 
Learning .NET Attributes
Learning .NET AttributesLearning .NET Attributes
Learning .NET Attributes
 
Learn dot net attributes
Learn dot net attributesLearn dot net attributes
Learn dot net attributes
 
Learning MVC Part 3 Creating MVC Application with EntityFramework
Learning MVC Part 3 Creating MVC Application with EntityFrameworkLearning MVC Part 3 Creating MVC Application with EntityFramework
Learning MVC Part 3 Creating MVC Application with EntityFramework
 
Entity framework
Entity frameworkEntity framework
Entity framework
 
MVC Application using EntityFramework Code-First approach Part4
MVC Application using EntityFramework Code-First approach Part4MVC Application using EntityFramework Code-First approach Part4
MVC Application using EntityFramework Code-First approach Part4
 
Microsoft Information Protection Implementation using C# and PowerShell
Microsoft Information Protection Implementation using C# and PowerShellMicrosoft Information Protection Implementation using C# and PowerShell
Microsoft Information Protection Implementation using C# and PowerShell
 
Simple ado program by visual studio
Simple ado program by visual studioSimple ado program by visual studio
Simple ado program by visual studio
 
Intake 38 data access 5
Intake 38 data access 5Intake 38 data access 5
Intake 38 data access 5
 
Overview of CSharp MVC3 and EF4
Overview of CSharp MVC3 and EF4Overview of CSharp MVC3 and EF4
Overview of CSharp MVC3 and EF4
 
3 tier architecture in asp.net
3 tier architecture in asp.net3 tier architecture in asp.net
3 tier architecture in asp.net
 
Entity Framework Code First Migrations
Entity Framework Code First MigrationsEntity Framework Code First Migrations
Entity Framework Code First Migrations
 
.NET Core, ASP.NET Core Course, Session 13
.NET Core, ASP.NET Core Course, Session 13.NET Core, ASP.NET Core Course, Session 13
.NET Core, ASP.NET Core Course, Session 13
 
Entity framework1
Entity framework1Entity framework1
Entity framework1
 
Rational Publishing Engine with Rational DOORS
Rational Publishing Engine with Rational DOORSRational Publishing Engine with Rational DOORS
Rational Publishing Engine with Rational DOORS
 

Dernier

Spellings Wk 4 and Wk 5 for Grade 4 at CAPS
Spellings Wk 4 and Wk 5 for Grade 4 at CAPSSpellings Wk 4 and Wk 5 for Grade 4 at CAPS
Spellings Wk 4 and Wk 5 for Grade 4 at CAPS
AnaAcapella
 
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
MysoreMuleSoftMeetup
 
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdf
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdfContoh Aksi Nyata Refleksi Diri ( NUR ).pdf
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdf
cupulin
 
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
EADTU
 

Dernier (20)

Spellings Wk 4 and Wk 5 for Grade 4 at CAPS
Spellings Wk 4 and Wk 5 for Grade 4 at CAPSSpellings Wk 4 and Wk 5 for Grade 4 at CAPS
Spellings Wk 4 and Wk 5 for Grade 4 at CAPS
 
How to Manage Website in Odoo 17 Studio App.pptx
How to Manage Website in Odoo 17 Studio App.pptxHow to Manage Website in Odoo 17 Studio App.pptx
How to Manage Website in Odoo 17 Studio App.pptx
 
HMCS Vancouver Pre-Deployment Brief - May 2024 (Web Version).pptx
HMCS Vancouver Pre-Deployment Brief - May 2024 (Web Version).pptxHMCS Vancouver Pre-Deployment Brief - May 2024 (Web Version).pptx
HMCS Vancouver Pre-Deployment Brief - May 2024 (Web Version).pptx
 
OS-operating systems- ch05 (CPU Scheduling) ...
OS-operating systems- ch05 (CPU Scheduling) ...OS-operating systems- ch05 (CPU Scheduling) ...
OS-operating systems- ch05 (CPU Scheduling) ...
 
When Quality Assurance Meets Innovation in Higher Education - Report launch w...
When Quality Assurance Meets Innovation in Higher Education - Report launch w...When Quality Assurance Meets Innovation in Higher Education - Report launch w...
When Quality Assurance Meets Innovation in Higher Education - Report launch w...
 
Including Mental Health Support in Project Delivery, 14 May.pdf
Including Mental Health Support in Project Delivery, 14 May.pdfIncluding Mental Health Support in Project Delivery, 14 May.pdf
Including Mental Health Support in Project Delivery, 14 May.pdf
 
COMMUNICATING NEGATIVE NEWS - APPROACHES .pptx
COMMUNICATING NEGATIVE NEWS - APPROACHES .pptxCOMMUNICATING NEGATIVE NEWS - APPROACHES .pptx
COMMUNICATING NEGATIVE NEWS - APPROACHES .pptx
 
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
MuleSoft Integration with AWS Textract | Calling AWS Textract API |AWS - Clou...
 
Spring gala 2024 photo slideshow - Celebrating School-Community Partnerships
Spring gala 2024 photo slideshow - Celebrating School-Community PartnershipsSpring gala 2024 photo slideshow - Celebrating School-Community Partnerships
Spring gala 2024 photo slideshow - Celebrating School-Community Partnerships
 
Stl Algorithms in C++ jjjjjjjjjjjjjjjjjj
Stl Algorithms in C++ jjjjjjjjjjjjjjjjjjStl Algorithms in C++ jjjjjjjjjjjjjjjjjj
Stl Algorithms in C++ jjjjjjjjjjjjjjjjjj
 
8 Tips for Effective Working Capital Management
8 Tips for Effective Working Capital Management8 Tips for Effective Working Capital Management
8 Tips for Effective Working Capital Management
 
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdf
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdfContoh Aksi Nyata Refleksi Diri ( NUR ).pdf
Contoh Aksi Nyata Refleksi Diri ( NUR ).pdf
 
Mattingly "AI and Prompt Design: LLMs with NER"
Mattingly "AI and Prompt Design: LLMs with NER"Mattingly "AI and Prompt Design: LLMs with NER"
Mattingly "AI and Prompt Design: LLMs with NER"
 
Trauma-Informed Leadership - Five Practical Principles
Trauma-Informed Leadership - Five Practical PrinciplesTrauma-Informed Leadership - Five Practical Principles
Trauma-Informed Leadership - Five Practical Principles
 
DEMONSTRATION LESSON IN ENGLISH 4 MATATAG CURRICULUM
DEMONSTRATION LESSON IN ENGLISH 4 MATATAG CURRICULUMDEMONSTRATION LESSON IN ENGLISH 4 MATATAG CURRICULUM
DEMONSTRATION LESSON IN ENGLISH 4 MATATAG CURRICULUM
 
An overview of the various scriptures in Hinduism
An overview of the various scriptures in HinduismAn overview of the various scriptures in Hinduism
An overview of the various scriptures in Hinduism
 
FICTIONAL SALESMAN/SALESMAN SNSW 2024.pdf
FICTIONAL SALESMAN/SALESMAN SNSW 2024.pdfFICTIONAL SALESMAN/SALESMAN SNSW 2024.pdf
FICTIONAL SALESMAN/SALESMAN SNSW 2024.pdf
 
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
Transparency, Recognition and the role of eSealing - Ildiko Mazar and Koen No...
 
UChicago CMSC 23320 - The Best Commit Messages of 2024
UChicago CMSC 23320 - The Best Commit Messages of 2024UChicago CMSC 23320 - The Best Commit Messages of 2024
UChicago CMSC 23320 - The Best Commit Messages of 2024
 
VAMOS CUIDAR DO NOSSO PLANETA! .
VAMOS CUIDAR DO NOSSO PLANETA!                    .VAMOS CUIDAR DO NOSSO PLANETA!                    .
VAMOS CUIDAR DO NOSSO PLANETA! .
 

Murach: How to use Entity Framework EF Core

  • 1. 12 How to use EF Core In this chapter, you’ll learn how to use Entity Framework (EF) Core to work with data in a database. This includes how to create a new database from code (Code First development) and how to create code from an existing database (Database First development). By : Eng. Mahmoud Hassouna Email : M.hassuna2@gmail.com
  • 2. How to create a database from code Entity Framework (EF) Core is an object-relational mapping (ORM) framework that allows you to map your entity classes to the tables of a database. The most common way to work with EF Core is to code your entity classes and a database context class first. Then, you can use EF to create a new database from these classes. This is called Code First development, and it’s the approach that you’ll learn about now.
  • 3. How to code entity and DB context classes Entity classes represent the data structure for an app. For example, a bookstore has entities like books, authors, and genres. As a result, a Bookstore app needs entity classes to store data for those entities. To illustrate, figure 12-1 begins with two examples that show the Book and Author entity classes. You can create objects from these classes to store the data that’s needed by your app.
  • 4. Description • Entity classes define the data structure for the app and map to tables in a relational database. A database (DB) context class inherits the DB Context class and includes entity classes as DB Set properties. • In the DB context class, the On Configuring() method allows you to configure the context, such as providing the connection string. Usually, though, you do this in the Startup's file. • In the DB context class, the OnModelCreating() method allows you to configure the entity classes, such as providing initial seed data. • To work with EF, you need to configure the database context. To do that, you can modify your appsettings.json and Startup.cs files as described in figure 4-6 of chapter 4.
  • 5. How to configure the database You can configure your entity classes so they create the database tables you want in three ways. First, you can configure them by convention. Second, you can configure them with data attributes. Third, you can configure them by using code. When you configure by convention, you code your classes following established conventions, and EF takes it from there. Figure 12-2 starts by presenting some of these conventions. For example, if you code a property named Id (or ID) or join the name of the entity and Id (or ID), EF treats that property as the primary key of the database table. Additionally, if this property .
  • 6. Some of the conventions for configuration in EF Core • A property named Id or ClassNameId is the primary key. If the property is of the int type, the key is an identity column and its value is generated automatically by the database. • A string property has a database type of nvarchar(max) and is nullable. • An int or double property is not nullable. Some of the data attributes for configuration
  • 7. Some of the Fluent API methods for configuration
  • 8. How to manage configuration files The first code example in figure 12-3 shows the OnModelCreating() method of a context class with several statements that configure a Book entity. The first statement sets the ISBN property as the primary key. This is necessary because that property doesn’t follow the naming convention for a primary key. The second statement makes the Title property not allow nulls and to have a maximum length of 200 characters. And the third statement uses the HasData() method to provide seed data when the table is created. The statements in this example are for a single entity that has two properties. However, imagine how this code will grow as you add more properties to this
  • 9. Description • If you have a lot of Fluent API configuration code in the OnModelCreating() method of your context class, it can become difficult to understand and maintain. In that case, it’s considered a best practice to divide this code into one configuration file per entity. • To create a configuration file, you code a class that implements the IEntityTypeConfiguration<T> interface and its Configure() method. • The Configure() method accepts an EntityTypeBuilder<T> object that represents the entity being configured. • To apply the configuration code, you pass a new instance of the configuration class to the ApplyConfiguration() method of the ModelBuilder object that’s passed to the OnModelCreating() method of the context class.
  • 10. EF commands for working with a database In chapter 4, you learned how to use the NuGet Package Manager Console (PMC) to execute PowerShell commands such as Add-Migration and Update- Database to create a database from DB context and entity classes. Figure 12-4 starts by reviewing how you open the PMC window. Then, it summarizes the Add-Migration and Update-Database commands, as well as some additional commands for working with a database. For any of these commands to work, your app needs to be configured to access a database as shown in figure 4-6.
  • 11. The first command, Add-Migration, generates a migration file based on your context and entity classes. By default, this command creates the file with the name you specify in the Migrations folder. If that folder doesn’t exist, this command creates it. If you want, you can use the - OutputDir parameter to specify another folder, as you’ll see shortly. The second command, Remove-Migration, removes the last migration file from the Migrations folder. This only works, though, if the migration file hasn’t yet been applied to the database. If it has, you need to use the Update-Database command to revert the migration before you can remove the migration file. You’ll see an example of this in the next figure.
  • 12. The third command, Update-Database, applies any migration files that haven’t yet been applied to the database. You can also use the Update-Database command to revert migrations as shown in the next figure. The fourth command, Drop-Database, deletes the database. If this command doesn’t work correctly, you can use SQL Server Object Explorer or another comparable tool to delete the database. The fifth command, Script-Migration, generates a SQL script based on one or more migration files. The sixth command, Scaffold-DbContext, generates DB context and entity classes from an existing
  • 13.
  • 14. Description • The PMC executes PowerShell commands to create, apply, and revert migration files. • A migration file contains C# code for creating or updating database objects. Specifically, each migration file has an Up() method with code that runs when a migration is applied, and a Down() method with code that runs when a migration is reverted. • The first time you run the Add-Migration command, EF creates a file named DbContextNameModelSnapshot.cs. This file contains a “snapshot” of the current database schema. When you add or remove subsequent migrations, that file is updated.
  • 15. How to use EF migration commands Figure 12-5 presents some examples that use EF commands in the Package Manager Console (PMC) to work with the Bookstore database. The first scenario in this figure shows how to create and then update the database. Step 1 creates a migration file named Initial.cs in the Migrations folder. Then, step 2 updates the database. This applies the Initial migration and creates the database.
  • 16. How to create and then update a database 1. Create a migration file named Initial based on the context and entity classes presented earlier in this chapter by entering this command: PM> Add-Migration Initial 2. Create a database based on the migration file by entering this command: PM> Update-Database 3. Add a property named Discount of the double type to the Book class. 4. Create a migration file named AddDiscount by entering this command: PM> Add-Migration AddDiscount 5. Review the migration file and note that the Discount property doesn’t accept nulls. 6. Change the Discount property in the Book class to the data type of double?.
  • 17. 7. Generate another migration file by entering this command: PM> Add-Migration MakeDiscountNullable 8. Apply the migration files to the database by entering this command: PM> Update-Database
  • 18. How to revert one or more migrations 1. To revert changes to the database by running the Down() method in every migration file that comes after the AddDiscount file, enter this command: PM> Update-Database AddDiscount 2. To remove the unapplied MakeDiscountNullable migration file that was reverted in step 1 from the Migrations folder, enter this command: PM> Remove-Migration .
  • 19. How to revert all the migrations 1. To revert all changes that have been applied to the database by running the Down() method in all the migration files, enter this command: PM> Update-Database 0 2. To remove all migration files from the Migrations folder, enter the Remove- Migration command repeatedly. Or, manually delete all the migration files from the Migrations folder, including the snapshot file Note • When you run one of these commands, you might see a warning to update your tools. To do that, you can run an Install-Package command like this: PM> Install-Package Microsoft.EntityFrameworkCore.Tools -Version 3.0.1 However, you should use the version number that’s included in the warning message.
  • 20. How to work with relationships So far, you have learned how to code entity classes and use those classes to create database tables. However, entities are typically related to other entities. Similarly, the corresponding database tables are typically related to other tables. For example, a genre might have many books. An author might have a detailed bio. A book might have several authors. And an author might have several books. That’s why the next few figures show how to configure relationships between entities and their corresponding tables. How entities are related Entities can be related to other entities by values in specific properties. The two entities at the top of figure 12 - 6 illustrate this concept. Here, the Genre entity is related to the Book entity because they share a GenreId property. Typically, relationships exist between the primary key in one entity and the foreign key in another entity. A primary key (PK) uniquely identifies an instance of an entity. A foreign key (FK) refers to a primary key in another entity. In this figure, the diagram shows that the GenreId property in the Genre entity is a primary key, and the GenreId property in the Book entity is a foreign key. In other words, the foreign key in the Book entity refers to the primary key of the Genre entity.
  • 21. Description •The PMC executes PowerShell commands to create, apply, and revert migration files. •A migration file contains C# code for creating or updating database objects. Specifically, each migration file has an Up() method with code that runs when a migration is applied, and a Down() method with code that runs when a migration is reverted. •The first time you run the Add- Migration command, EF creates a file named DbContextNameModelSnapshot.cs. This file contains a “snapshot ”of the current database schema. When you add or remove subsequent migrations, that file is updated.
  • 22. How to configure a one-to- many relationship The first code example in figure 12 - 7 shows the simplest way to configure a one-to-many relationship by convention. Here, the relationship is created by nesting a Genre entity as a property of the Book entity. This is a common way of creating a one-to-many relationship. However, this approach can cause some problems. For example, you need to specifically include the nested entity and all of its data even if all you need is the ID value. . As a result, it’s typically a better practice to fully define the relationship by explicitly coding the foreign key property. The second example in this figure shows how this works. Here, the Book entity explicitly includes the foreign key In addition, you can run into problems if the nested entity has data validation requirements
  • 23. How to configure a one- to-many relationship by convention public class Book{ public int BookId { get; set; } public Genre Genre { get; set; } } public class Genre{ public int GenreId { get; set; } public string Name { get; set; } } How to fully define the one-to-many relationship by convention )recommended) public class Book{ public int BookId { get; set; } public int GenreId { get; set; } // foreign key property public Genre Genre { get; set; } // navigation property } public class Genre{ public int GenreId { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } // navigation property }
  • 24. How to configure a one-to- many relationship with attributes public class Book{ public int BookId { get; set; } public int CategoryId { get; set; } ytreporp KF // ]("dIyrogetaC")yeKngieroF[ ssalc kooB ni ytreporp van // ]("skooB")ytreporPesrevnI[ ssalc erneG ni public Genre Category { get; set; } } public class Genre{ public int GenreId { get; set; } public string Name { get; set; } van // ]("yrogetaC")ytreporPesrevnI[ ssalc kooB ni ytreporp public ICollection<Book> Books { get; set; { } How to configure a one-to-many relationship with the Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Book > )( .HasOne(b => b.Genre) // nav property in Book class .WithMany(g => g.Books); // nav property in Genre class }
  • 25. How to configure a one- to-one relationship If an entity has a one-to-one relationship with another entity, the data in the two entities could be stored in one entity, and the data in the two underlying database tables could be stored in one table. This is useful when you need to store large objects such as images, sound, and videos. Then, you can store those objects in a separate entity and underlying table and only join them when you need to retrieve the large object. A one-to-one relationship can also be useful when an entity has both essential data and data that is frequently null. For example, suppose that in most cases, the only data you need to store for an author is the name. The first example in figure 12 - 8 shows how to configure a fully defined one-to-one relationship by convention. This example works similarly to the second example in the previous figure. To start, each entity configures its primary key by convention and defines a navigation property.
  • 26. How to configure a one-to- one relationship by convention public class Author{ public int AuthorId { get; set; } // primary key property public string Name { get; set; } public AuthorBio Bio { get; set; } // navigation property } public class AuthorBio{ public int AuthorBioId { get; set; } // primary key property public int AuthorId { get; set; } // foreign key property public DateTime? DOB { get; set; } public Author Author { get; set; } // navigation property } How to configure a one-to- one relationship with attributes public class Author{ //same as above } public class AuthorBio{ [Key] public int AuthorId { get; set; } // PK and FK property public DateTime? DOB { get; set; } ytreporp KF // ]("dIrohtuA")yeKngieroF[ public Author Author { get; set; } // navigation property }
  • 27. How to configure a one-to- one relationship with the Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.Entity<Author > )( .HasOne(a => a.Bio) // nav property in Author class .WithOne(ab => ab.Author) // nav property in AuthorBio class .HasForeignKey<AuthorBio>(ab => ab.AuthorId); // FK property } How to configure a one-to- one relationship within a single table protected override void OnModelCreating(ModelBuilder modelBuilder){ modelBuilder.Entity<Author > )( .HasOne(a => a.Bio) .WithOne(ab => ab.Author) .HasForeignKey<AuthorBio>(ab => ab.AuthorId); modelBuilder.Entity<Author>().ToTable("A uthors"); modelBuilder.Entity<AuthorBio>().ToTable ("Authors"); }
  • 28. How to configure a many-to-many relationship A many-to-many relationship is configured by using an intermediate entity called a join entity or a linking entity. This creates an intermediate table in the database called a join table or a linking table. The linking entity has a one-to- many relationship with the two entities in the many-to-many relationship. In other words, a many-to-many relationship is broken down into two one-to- many relationships. Unlike the other two relationships, a many-to-many relationship can only be configured with the Fluent API. Figure 12 - 9 shows how to configure a many-to- many relationship between the Book entity and the Author entity. The first example in this figure presents the Book and Author entities that have a many-to-many relationship. Each of these entities contains a navigation property of a collection of BookAuthor entities. These navigation properties represent the many side of the two one-to-many relationships.
  • 29. The entities to be linked public class Book{ public int BookId { get; set; } public string Title { get; set; } //navigation property to linking entity public ICollection<BookAuthor> BookAuthors { get; set; } } public class Author{ public int AuthorId { get; set; } public string Name { get; set; } //navigation property to linking entity public ICollection<BookAuthor> BookAuthors { get; set; } } The linking entity public class BookAuthor{ //composite primary key public int BookId { get; set; } // foreign key for Book public int AuthorId { get; set; } // foreign key for Author //navigation properties public Book Book { get; set; } public Author Author { get; set; } }
  • 30. How to configure a many-to-many relationship with the Fluent API protected override void OnModelCreating(ModelBuilder modelBuilder){ //composite primary key for BookAuthor modelBuilder.Entity<BookAuthor > )( .HasKey(ba => new { ba.BookId, ba.AuthorId }); Description •A many-to-many relationship requires a third entity known as a join entity or linking entity. The linking entity has two one-to-many relationships with the entities to be joined. •The linking entity has a composite primary key that consists of the primary key of each linked entity.
  • 31. How to control delete behavior When an app deletes a row from a database, related rows that are dependent on that row can become corrupted. For example, if an app deletes the genre “Novel ”from the Genre table, all the rows in the Books table whose genre is “Novel ”have invalid data. You can use the OnDelete() method of the Fluent API to configure how dependent rows are handled when a parent row is deleted. This method accepts a value of the DeleteBehavior enum as an argument. The table at the top of figure 12 - 10 shows the values of this enum.
  • 32. Description •When a row is deleted from a database, related rows that are dependent on that row can become corrupted. To prevent this, most databases throw an exception when you try to delete a row that has dependent rows. •Most databases also allow you to configure cascading deletes, which cause dependent rows to be automatically deleted. •You can use the OnDelete() method of the Fluent API to configure how dependent rows are handled when a parent row is deleted.
  • 33. The Bookstore database classes Now that you know how to code entity classes that are related to each other, you’re ready to see the entity, context, and configuration classes for the Bookstore website that’s presented in the next chapter. The entity classes Figure 12 - 11 shows the classes for the Author, Book, BookAuthor, and Genre entities that are used by the Bookstore website. For the most part, these entities are configured by convention. For example, the Author, The context and configuration classes Figure 12 - 12 shows the context class for the Bookstore website. This class begins by defining the DbSet properties for the Author, Book, BookAuthor, and Genre entities presented in figure 12 - 11 .
  • 34. The Author class using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; namespace Bookstore.Models The Book class using System.Collections.Generic; using System.ComponentModel.DataAnnotations; The BookAuthor class namespace Bookstore.Models { public class BookAuthor { //composite primary key and foreign keys public int BookId { get; set; } public int AuthorId { get; set; } //navigation properties public Author Author { get; set; } public Book Book { get; set; } } }
  • 35. The Genre class using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; The BookstoreContext class using Microsoft.EntityFrameworkCore; namespace Bookstore.Models { public class BookstoreContext : DbContext { public BookstoreContext(DbContextOptions<BookstoreCont ext> options) :base(options) The SeedAuthors class using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders;
  • 36. The SeedGenres class using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; How to create code from a database So far in this book, you have learned how to use Code First development to create a database from your context and entity classes. Sometimes, though, you already have a database. In that case, you need to create context and entity classes from your database. This approach is called Database First development.
  • 37. How to generate DB context and entity classes To get started with Database First development, you can use the Scaffold- DbContext command to generate the code for the context and entity classes. The table at the top of figure
  • 38. Description •You can use the Scaffold-Database command to generate context and entity classes based on an existing database. •You can omit the flag for required parameters. •When specifying the connection string, it’s considered a best practice to use a connection string that’s stored in the appsettings.json file. How to configure a generated DB context class When you use the Scaffold-DbContext command to generate context and entity classes, the OnConfiguring() method of the context class contains the connection string and provider information. Usually, though, you need to make some adjustments before your project is ready to run.
  • 39. The OnConfiguring() method in the generated context class protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer("name=BookstoreContext "(; } } The Startup.cs file that injects the DB context into the app using Microsoft.EntityFrameworkCore; using Bookstore.Models.DataLayer; public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
  • 40. Description •When you use the Scaffold- Database command to create DB context and entity classes, it adds configuration code to the OnConfiguring() method of the context class. •To get the DB context to work, you need to edit the Startup.cs file so it injects the context into the app. •For this code to work, the appsettings.json file must store the specified connection string. If it doesn’t, you can edit this file so it contains the specified connection string. How to modify a generated entity class When you work with Database First development, it’s common to enhance the entity classes that it generates. For instance, you may want to add data validation attributes. Or, you may want to add more properties such as read-only properties that indicate the state of the entity. However, if you make changes to the database, it’s common to need to regenerate the entity classes. When this happens, though, you lose any additions you’ve made to those classes. Fortunately, you can use partial classes to avoid this problem.
  • 41. A partial class generated by EF Database First namespace Bookstore.Models.DataLayer { public partial class Books { [Key] public int BookId { get; set; } [Required] [StringLength(200)] public string Title { get; set; } public double Price { get; set; } ... } } A partial class that adds a metadata class and a read-only property using Microsoft.AspNetCore.Mvc; namespace Bookstore.Models.DataLayer { [ModelMetadataType(typeof(BooksMetadata))] public partial class Books { public bool HasTitle => !string.IsNullOrEmpty(Title); } }
  • 42. How to work with data in a database Chapter 4 introduced some basic skills for working with the data in a database. Now, you’ll review those skills and learn some new ones. How to query data You can use LINQ to Entities to query the DbSet properties of a context class. The first code example in figure 12-17 shows a context property named context that’s used by subsequent examples. This context has DbSet properties named Books and Authors. The second set of examples shows how to create and execute a query. When you do that using two statements, you create a query variable of the IQueryable type that holds a query, not data. Then, when you execute the query, the data is retrieved from the database and stored in an object of the IEnumerable type. When you do that using one statement, you can use the var keyword to declare the variable where the results are stored so the compiler will automatically determine the type. In this case, it uses the List type, which is usually what you want.
  • 43. The context class used in the following examples private BookstoreContext context { get; set; } Code that creates and executes a query In two statements IQueryable query = context.Books; // create the query IEnumerable books = query.ToList(); // execute the query In one statement var books = context.Books.ToList(); // implicit typing is common Code that sorts the results Sort by Title in ascending order (A to Z) var books = context.Books.OrderBy(b => b.Title).ToList(); Sort by Title in descending order (Z to A) var books = context.Books.OrderByDescending(b => b.Title).ToList();
  • 44. Code that filters the results Get a single book by ID var book = context.Books.Where(b => b.BookId == 1).FirstOrDefault(); var book = context.Books.Find(1); // shortcut for above Get a list of books by genre var books = context.Books.Where(b => b.Genre.Name == "Mystery").ToList(); Get a list of books in a price range var books = context.Books.Where(b => b.Price > 10 && b.Price < 20).ToList(); Conditionally filter by multiple criteria // build the query (can’t use implicit typing here) IQueryable query = context.Books; if (selectedMaxPrice != null) query = query.Where(b => b.Price < selectedMaxPrice); if (selectedGenre != null) query = query.Where(b => b.Genre.Name == selectedGenre); // execute the query var books = query.ToList();
  • 45. How to work with projections and related entities When you query data as described in the last figure, the query retrieves all of the properties in an entity, whether you need them or not. Usually, this is fine. Sometimes, though, you may have entities with so many properties that it’s inefficient to retrieve all of them. Or, you may have properties with sensitive data, like passwords, that you don’t want to retrieve. One way to limit the number of properties returned is to use table splitting as mentioned earlier in this chapter. Another way to do that is to use projections. A projection uses the LINQ Select() method to retrieve only some of an entity’s properties. One way to create a projection is to use an anonymous type like the one shown in the first example in figure 12-18. This type consists of the AuthorId property of the Authors entity and a property that’s a combination of the FirstName and LastName properties. Because the second property doesn’t match an entity property, it must be explicitly named as shown here. Anonymous types are convenient and quick, but they can be hard to work with in an MVC app. That’s because they’re hard to pass to views, as the error message below the first example shows.
  • 46. How to create a projection with an anonymous type var authors = context.Authors .Select(a => new { a.AuthorId, // can infer property name Name = a.FirstName + ' ' + a.LastName // must specify property name }) .ToList(); Error when you pass the projection to a view that expects a list of Authors InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List`1[<>f_AnonymousType0`2 [System. Int32,System.String]]', but this ViewDataDictionary instance requires a model item of type System.Collections.Generic.IEnumberable`1 [BookList. Models.Author]'.
  • 47. How to create a projection with a concrete type The concrete type public class DropdownDTO { public string Value { get; set; } public string Text { get; set; } } The projection var authors = context.Authors .Select(a => new DropdownDTO { Value = a.AuthorId.ToString(), Text = a.FirstName + ' ' + a.LastName }) .ToList(); Description • A projection allows you to retrieve a subset of the properties of an entity. • To create a projection, you can use an anonymous type or a concrete type. However, anonymous types can be hard to use in views. • You can use the Include() and ThenInclude() methods to include related entities in your query. These methods accept lambda expressions to identify the entities to include.
  • 48. How to insert, update, and delete data In addition to querying data, you need to know how to insert, update, and delete data. Fortunately, the methods of the DbSet and DbContext classes make that easy to do. You learned about these methods in chapter 4, but now figure 12-19 reviews them and explains them in more detail. The Add(), Modify(), and Delete() methods of the DbSet class only affect the DbSet. The Add() method adds a new entity to the DbSet and marks it as Added, and the Update() and Remove() methods mark an existing entity as Modified or Deleted. To mark an entity, EF uses the EntityState enum. In most cases, you can let EF mark these changes for you automatically. However, you can also set the state of an entity manually, if necessary. The SaveChanges() method of the DbContext class, by contrast, affects the underlying database. When you call this method, all the Added, Modified, and Deleted actions pending in the DbSet properties are executed against the database. The code examples show how to use these methods. The first example adds a new entity, the second and third examples update an existing entity, and the fourth example deletes an existing entity. While this code is easy to follow, you should notice a few things.
  • 49. First, although these examples all perform a single action and then call SaveChanges(), that’s not a requirement. You can make several calls to Add(), Update(), or Delete() before you call SaveChanges(). In fact, in some scenarios, it can improve efficiency to only call SaveChanges() once after all your modifications are done. Second, when it comes to updates, the second example shows how to work with a disconnected scenario because it’s commonly used with web apps. In this scenario, the Edit() action method gets the Book entity from an HTTP POST request. As a result, the code in this figure must use Update() to update the state of the entity before calling SaveChanges(). With desktop apps, it’s more common to use a connected scenario. However, a connected scenario can also occur in web apps as shown by the third example. Here, the first statement of the Edit() action method uses the Find() method to retrieve the Book entity from the database. This causes the context to track the state of this entity. As a result, you don’t need to call the Update() method. Instead, this code just sets the new price for the book and calls the SaveChanges() method. This generates SQL that’s slightly more efficient than calling Update(). However, if you wanted to make the code easier to read and understand, you could add a statement that calls Update() before calling SaveChanges(). Finally, when you delete a parent entity in EF Core, it’s common for all of the related child entities to be automatically deleted too. That’s because EF Core enables cascading deletes by default for foreign keys that are not nullable. However, if you disable cascading deletes as described earlier in this chapter, you’ll need to delete all the child entities before you can delete the parent entity.
  • 50. Code that adds a new entity [HttpPost] public IActionResult Add(Book book) { context.Books.Add(book); context.SaveChanges(); return RedirectToAction("List", "Book"); }
  • 51. Code that updates an existing entity in a disconnected scenario [HttpPost] public IActionResult Edit(Book book) // Book object is disconnected { context.Books.Update(book); // call to Update() required context.SaveChanges(); return RedirectToAction("List", "Book"); } Code that updates an existing entity in a connected scenario [HttpPost] public IActionResult Edit(int id, double price) { Book book = context.Books.Find(id); // Book object is connected book.Price = price; // call to Update() not required context.SaveChanges(); return RedirectToAction("List", "Book"); }
  • 52. How to handle concurrency conflicts Concurrency allows two or more users to work with a database at the same time. However, if two users retrieve and then attempt to update the same entity (row in a table), their updates may conflict with each other, and you need to handle this concurrency conflict. In EF Core, you have two options for concurrency control. The default option is called “last in wins”. This option doesn’t perform any checking. Instead, the last update overwrites any previous changes. In some cases, this option is adequate, but it can lead to corrupted data. The other option is called optimistic concurrency. It checks whether a row has been changed since it was retrieved. If so, EF refuses the update or deletion and throws an exception. Then, the app can handle this exception. You can use data attributes or the Fluent API to configure your apps to use optimistic concurrency.
  • 53. How to simulate a concurrency conflict var book = context.Books.Find(1); // get a book from the database book.Price = 14.99; // change price in memory context.Database.ExecuteSqlRaw( // change price in the database "UPDATE dbo.Books SET Price = Price + 1 WHERE BookId = 1"); context.SaveChanges(); Description • A concurrency conflict is when data is modified after it’s retrieved for editing or deletion. • A rowversion property lets you check all the properties in an entity for conflicts and must be an array of bytes. • The DbContext class has a Database property whose ExecuteSqlRaw() method can be used to simulate concurrency conflicts. • A concurrency token lets you check an individual property for conflicts.
  • 54. How handle a concurrency exception If you’ve enabled optimistic concurrency and an update or delete operation fails because of a concurrency problem, EF throws a concurrency exception. To handle this exception, you call the SaveChanges() method within the try block of a try-catch statement, and you include a catch block that catches the exception. Figure 12-21 shows a controller action method named Edit() that handles a concurrency exception. Here, within the catch block, this code uses the Entries property of the concurrency exception to get the current values of the entity being updated. The Entries property is a collection of DbEntityEntry objects, but it only has one entry here. As a result, the code uses the LINQ Single() method to retrieve the entry. If you want to be more cautious, you can use the SingleOrDefault() method and check for null. The DbEntityEntry class has properties named CurrentValues and OriginalValues, but those properties typically aren’t useful in a disconnected scenario. Instead, this example uses the GetDatabaseValues() method to retrieve the current values of the entity from the database. After calling the GetDatabaseValues() method, the code checks whether the return value is null. If it is, the entity has been deleted and is no longer in the database. As a result, the code adds a class-level error to the ModelState object notifying the user that the row no longer exists.
  • 55. Some of the code in the Edit view @* both primary key and row version value needed for edit *@ <input type=“hidden” asp-for=“Book ID”/> <button type=“submit” class=“btn”>submit</button> Description • The DbUpdateConcurrencyException is thrown when there’s a concurrency conflict. • The DbUpdateConcurrencyException has an Entries property that provides a way to get the new database values for the row that’s being saved. <input type=“hidden” asp-for=“RowVersion”/>
  • 56. So far, the EF Core code you’ve seen has been in the action methods of controllers. Usually, though, this code should be in its own data layer. This makes your code easier to test. In addition, it allows you to store your data access code in a separate project. Finally, it makes it possible to change from EF to another ORM framework without affecting the rest of your code. How to encapsulate your EF code A class that adds an extension method to the Iqueryable <T> interface public static class QueryExtensions { public static Iqueryable <T> <T>(this IQueryable <T> query, int pageNumber, int pageSize) { return query .Skip((pageNumber - 1) * pageSize) .Take(pageSize); } } PageBy
  • 57. How to use a generic query options class In the previous figure, you learned how to code a data access class that provides two methods for querying: one for retrieving a page of data and one for sorting and filtering data. However, these methods might cause logistical problems. For instance, what if you don’t need to sort the data? What if you don’t need to filter the data? Where does paging fit? One way to avoid these problems is to code a generic class for query options like the one presented in the first example of figure 12-23. Here, the QueryOptions class begins by defining public lambda expression properties for sorting and filtering and public int properties for paging. Then, it defines a private array for storing strings that indicate any related entities the query should include. The write-only Includes property accepts a comma-separated string, removes any spaces, splits the string at the commas, and stores the resulting string array in the private field. The method named GetIncludes() is used to return the value of the private field. Or, if the private field is null, it returns an empty string array.
  • 58. A generic query options class using System.Linq.Expressions; public class QueryOptions <T> { // public properties for sorting, filtering, and paging public Expression<Func> OrderBy { get; set; } public Expression<Func> Where { get; set; } public int PageNumber { get; set; } public int PageSize { get; set; } // public write-only property for includes private string array private string[] includes; public string Includes { set => includes = value.Replace(" ", "").Split(','); } // public method returns includes array public string[] GetIncludes() => includes ?? new string[0]; // read-only properties public bool HasWhere => Where != null; public bool HasOrderBy => OrderBy != null; public bool HasPaging => PageNumber > 0 && PageSize > 0; }
  • 59. How to use the repository pattern One popular way to implement a data layer is to use the repository pattern. This pattern encapsulates data code within a data access layer and also uses interfaces to provide a layer of abstraction. One benefit of this pattern is that it makes it easier to automate testing as shown in chapter 14. The first example in figure 12-24 presents a simple generic interface named IRepository that you can create to work with a repository. This interface has six methods. The first two query data, the next three modify data, and the last one saves changes to the data. When using the repository pattern, you should use one repository per entity. For example, a Bookstore app shouldn’t use one big Bookstore repository. Instead, it should use a Book repository, an Author repository, a Genre repository, and so on. You can use this generic repository class to create various entity collections. For example, this code creates an Author repository: var data = new Repository(ctx);
  • 60. The generic IRepository interface public interface IRepository <T>where T : class { Ienumerable <T> List (QueryOptions <T> options); T Get(int id); void Insert(T entity); void Update(T entity); void Delete(T entity); void Save(); } A controller that uses the generic Repository class public class AuthorController : Controller { private Repository data { get; set; } public AuthorController(BookstoreContext ctx) => data = new Repository(ctx); public ViewResult Index() { var authors = data.List(new QueryOptions <Auther> { OrderBy = a => a.FirstName }); return View(authors); } ... }
  • 61. How to use the unit of work pattern Although some programmers think that including a Save() method in each repository is OK, others think that a repository should never have a Save() method. Either way, if you need to coordinate between multiple repositories, including a Save() method in each repository doesn’t work. In those cases, you can combine the repository pattern with the unit of work pattern. The unit of work pattern adds a central class that has repository objects as properties. Then, the central class passes each repository its context object, so they all share the same DB context. Finally, the central class includes a Save() method that calls the SaveChanges() method of the DB context. That way, when you call the Save() method of the central class, EF executes all changes in all repositories against the database. Then, if one change fails, they all fail. The first code example in figure 12-25 shows a unit of work class for a Bookstore app. This class implements an interface that specifies all of its methods. This isn’t required, but it’s generally considered a good practice since it makes it easier to automate testing.
  • 62. A controller that uses the unit of work class to update a book public class BookController : Controller { private BookstoreUnitOfWork data { get; set; } public BookController(BookstoreContext ctx) => data = new BookstoreUnitOfWork(ctx); [HttpPost] public IActionResult Edit(BookViewModel vm) { if (ModelState.IsValid){ data.DeleteCurrentBookAuthors(vm.Book); data.AddNewBookAuthors(vm.Book, vm.SelectedAuthors); data.Books.Update(vm.Book); data.Save(); ... } ... }
  • 63. Perspective This chapter showed how to use EF Core to work with data in a database, including how to encapsulate your EF code in a data layer. This presents the most important classes of the data layer that are used by the Bookstore website presented in the next chapter. As a result, studying that app is a great way to get started with EF Core. Terms entity class database (DB) context class Entity Framework (EF) Core object relational mapping (ORM) Code First development Database First development Fluent API primary key (PK) foreign key (FK) one-to-many relationship many-to-many relationship one-to-one relationship navigation property table splitting join entity linking entity join table linking table composite primary key cascading delete LINQ to Entities projection data transfer object (DTO) disconnected scenario connected scenario concurrency concurrency conflict optimistic concurrency rowversion property concurrency token extension method repository pattern CRUD (Create, Read, Update, Delete) unit of work pattern
  • 64. Exercise 12-1 Review the data layer of an app and improve it Review the domain model and configuration files 1. Open the Ch12Ex1ClassSchedule web app in the ex_starts directory. 2. Open the Models/DomainModels folder and review the code in the three class files it contains. 3. Open the Models/Configuration folder and review the code in the three configuration files it contains. Review the migration file 4. Open the Migrations folder and review the code in the Initial migration file. 5. Note how the migration file determines the primary and foreign keys based on how the properties in the domain model are coded (configuration by convention).
  • 65. Create the database 6. Open the Package Manager Console and enter the Update-Database command to run the migration and create the database. 7. Take a moment to review the SQL code in the console after the command runs. 8. Run the app and review the data it displays for classes and teachers. Test the cascading delete behavior of the Teacher object 9. Run the app and navigate to the Show All Teachers page. Click the Add Teacher link and add your name as a teacher. 10. Navigate to the Show All Classes page. Click the Add Class link and add a new class with yourself as the teacher. 11. Review the updated class list to make sure it displays the class you just added. 12. Navigate back to the Show All Teachers page and delete your name. 13. Navigate back to the Show All Classes page, and note that the class you previously added has also been deleted.
  • 66. Restrict the cascading delete behavior of the Teacher object 14. In the Models/DomainModels folder, open the configuration file for the domain model named Class. 15. Add code to change the delete behavior for the Teacher foreign key to Restrict. 16. Open the Package Manager Console and use the Add-Migration command to create a new migration file. When you do that, use a descriptive name for the migration file. 17. Review the code in the migration file that’s generated. 18. Enter the Update-Database command to apply the migration to the database. 19. Repeat steps 9 through 13 to test the cascading delete behavior. When you attempt to delete your name from the teacher list, you should get an error message. 20. Navigate to the Show All Classes page, find the class you added, click on Edit, and change the teacher to another teacher. 21. Navigate to the Show All Teachers page and delete your name from the teacher list. This time, the app should let you.
  • 67. Update the app to use the unit of work pattern 22. Open the Controllers folder and review the three controller class files. Note that two of them initialize more than one Repository class in the constructor. 23. Open the Models/DataLayer folder, add an interface named IClassScheduleUnitOfWork and a class named ClassScheduleUnitOfWork. 24. Adjust the namespaces of the interface and class to match the namespaces of the other classes in the DataLayer folder. 25. Code the interface to have read-only properties for a Class, Teacher, and Day repository and a Save() method that returns void. 26. Code the class to implement the interface, have a private ClassScheduleContext object that it gets in its constructor, initialize and return Repository objects in its properties, and call the context object’s SaveChanges() method in the Save() method. 27. Update the Home and Class controllers to use the unit of work class. 28. Run the app and make sure it still works the same.
  • 68. Change how the list of classes is ordered 29. Open the Home controller and review the code in its Index() action method. Note that the classes are ordered by day on first load and ordered by time when filtering by day. 30. Run the app. When it displays all classes, note that it doesn’t display the classes for Monday in ascending order by time. Then, click on the filter link for Monday and note that it does display the classes in ascending order by time. 31. In the Models/DataLayer folder, open the QueryOptions class. Note that this version is shorter than the QueryOptions class presented in the chapter. That’s because it doesn’t provide for paging. 32. Add a new property named ThenOrderBy that works like the OrderBy property. 33. Add a new read-only property named HasThenOrderBy that works like the HasOrderBy property.
  • 69. 34. Open the Repository class and find the List() method. Update the code that handles ordering so it uses the ThenOrderBy property like this: if (options.HasOrderBy) { if (options.HasThenOrderBy) { query = query.OrderBy(options.OrderBy).ThenBy(options.ThenOrderBy); } else { query = query.OrderBy(options.OrderBy); } } 35. In the Home controller, update the Index() action method to use the new property to sort by time as well as day on first load. 36. Repeat step 30. The class times should be in ascending order on both pages.
  • 70. Add an overload for the Get() method in the Repository class 37. Open the Class controller and review the private helper method named GetClass(). Note that it uses the List() method of the Repository class to get an IEnumerable with one Class item. Then, it uses the LINQ FirstOrDefault() method to retrieve that item. 38. In the Models folder, open the IRepository interface and add a second Get() method that accepts a QueryOptions object. 39. Open the Repository class and implement the new Get() method. To do that, you can copy the code from the repository’s List() method that builds a query, leaving out the code for ordering the list. Then, you can return the single object by calling the FirstOrDefault() method instead of the List() method. 40. In the Class controller, update the GetClass() method to use the new Get() method. 41. Run the app and test it. It should work the same as it did before.