Sei sulla pagina 1di 22

User

Tutorial : EF Code First VF

Part 0 Introduction:
Our main goal is to develop a MVC website for a hypermarket using .NET framework and
following this architecture:

DataBase

Front-Office
Back-Office

Part 1 - Setting up a solution:


Create these projects in one solution named MyFinance

MyFinance.Domain, MyFinance.Service, MyFinance.Data ( 3 Class


Library Project)
MyFinance.Web (ASP.NET Web Application (mvc))

Add the references following this schemas.


MyFinance.Service

MyFinance.Web

Reference

MyFinance.Data

MyFinance.Domainn

W
(MyF

BLL : Busi
(MyFinanc

DAL : Da
(MyFinan

To add a reference follow these steps:


1. Right click on project that needs the reference
2. Click Add
3. Click Reference

4. In the Reference Manager Message box, check referenced projects

5. Click OK

Part 2 Entities and Context Implementation:


Step 1 :
In the MyFinance.Domain, you should create a new folder named Entities
that includes all the classes of the following classes diagram, dont forget
navigation properties.

The following code expose the different entities:


public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
//navigation properties
virtual public ICollection<Product> Products { get; set; }
}
public class Provider
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public string Email { get; set; }
public bool IsApproved { get; set; }
public DateTime? DateCreated { get; set; } // ? nullable
//navigation properties
virtual public ICollection<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
public DateTime DateProd { get; set; }
//foreign Key properties

public int? CategoryId { get; set; }


//navigation properties
public virtual Category Category { get; set; }
public virtual ICollection<Provider> Providers { get; set; }

public class Biological : Product


{
public string Herbs { get; set; }
}
public class Chemical : Product
{
public string LabName { get; set; }
public string City { get; set; }
public string StreetAddress { get; set; }
}

We've made the navigation properties virtual so that we get lazy loading.
DateTime? Type is the nullable DateTime
We used ICollection interface so we can use later any collection implementation.

Step 2 : Entity Framework Installation


Now, lets move to MyFinance.Data project and add a reference to the Entity-Framework.
So lets open Manage NuGet package.
We need this reference so we can implement the context.
Do the same for MyFinance.Web project. Because entity framework wont be deployed
unless its referenced by the web project

We install the Entity Framework package

Step 3 : Context Implmentation


A Context is a class inherits DbContext, and manage entities and database.
Add a new Context by following these steps:
1. Create a class MyFinanceContext in MyFinance.Data project
2. Add an inheritance from DbContext
3. Add a default constructor that calls the parent one, and give it the named connection
string DefaultConnection that exists in the web.Config file in MyFinance.web
project
4. Add sets (Collection for entities management)
5. Add necessary using.
using MyFinance.Domain.Entities;
using System.Data.Entity;
public class MyFinanceContext : DbContext
{
public MyFinanceContext()
: base("Name=DefaultConnection")
{
}

Web.config

public DbSet<Category> Categories { get; set; }


public DbSet<Provider> Providers { get; set; }
public DbSet<Product> Products { get; set; }

Step 4 : Scaffold a Controller


1.
2.
3.
4.
5.
6.
7.

In MyFinance.Web Project add a reference to MyFinance.Data project (Context)


Build the solution
Right click on Controllers folder in the MyFinance.Web Project
click Add
click controller
in the message box choose : MVC5 Controller with views, using entity framework
Fill the message box like this :

8. Click Add
9. Explore the generated Controller and Views
Step 5 : Run and enjoy
Follow these steps:
1. Right click the MyFinance.Web project
2. Click Set as start-up project

3. Click the button


4. Add /products to the url and visit the page Ex : http://localhost:{your
port}/products
5. Insert some data

Step 6 explore database


Select App_Data folder in
MyFinance.Web and click on show all
files icon
Open App_Data folder and double click on
the mdf File

By default, Entity framework code first


uses Table Per Hierarchy (TPH) method to
handle inheritance when creating tables
from entities. in TPH, All Data in the
hierarchy will be saved in a single database
table and it uses a Discriminator column to
identify which record belongs to which sub
type. The value of this column will be the
name of the subtype.

Part 3 Update the model:


Step 1 (Optional):-Lets try to add a property public string Image { get; set; }
to the entity Product.
1. Run the application. Try to list products. What happen?
The application throws this exception:

-That exception is thrown because


the model changed and the the
Entity Framework Code First doesnt
know how it will behave. Therefore
we have to tell the framework how
it must behave.

2. Lets add another class MyFinanceContextInitialize in the same file of


This class inherit from
DropCreateDatabaseIfModelChanges<MyFinanceContext>, which give
the Entity Framework the strategy of initializing the application. In other
word, this class informs the Entity Framework to drop and create the database
if the model changes. We can replace this class by
DropCreateDatabaseAlways or CreateDatabaseIfNotExists.
the Context.
3. Override the Seed method inherited from the base class.
This method serves to seed the database in case on dropping and creating the
database, so we can seed some data for test instead of adding data manually in
the database each time the database is created.
public class MyFinanceContextInitializer :
DropCreateDatabaseIfModelChanges<MyFinanceContext>
{
protected override void Seed(MyFinanceContext context)
{
var listCategories = new List<Category>{
new Category{Name="Medicament" },
new Category{Name="Vetement" },
new Category{Name="Meuble" },
};
context.Categories.AddRange(listCategories);
context.SaveChanges();
}

4. In order to set the initializer we have to add this code to the


constructor of the class MyFinanceContext.
Database.SetInitializer<MyFinanceContext>(new MyFinanceContextInitializer());

5. In MyFinance.Web project, delete the controller ProductsController and


the Products folder under the Views Folder and scaffold
ProductsController. (same steps as Part2, Step4) to refresh the views
with the new added property
6. Go to the server explorer panel, right click on your database and close
connection.
This Step is necessary to close all connection to the database, you delete
the database in the SQL Server Object Explorer or Restart the visual studio
7. Run the application another time and check the database

Step 2 : Assuming that we are using a production environment and we have important
data. Then we are not allowed to lose this data. In this case the initialisation
strategy doesn't seem to be a good choice. So lets try something else,
Migration !!!! Lets do it.
1. The first step: we have to enable the Migration: Open the Package
Manager Console the choose the project MyFinance.Data which
contains the class Context. Then execute this command EnableMigrations

Entity Framework Code First generate a new folder Migration which contains
two classes xxx_InitialCreate and Configuration.
-The first class contains an Up () method that contains instructions to create the
table with its original definition, and another method Down () to delete the table.
-The second class define the behavior of the migration for DbContext used. This is
where you can specify the data to insert, enter the providers of other databases

2. The second step: we will change the name of the property public string
Image { get; set; } to public string ImageName { get; set; }.
3. Execute the command Add-Migration ModifyNameImage in the
Package Manager Console.
4. The Add-migration command checks for changes since your last
migration and scaffold a new migration with any changes found. We can
give a name for our migration, in this case, we call migration
"ModifyNameImage".

5. Go to the Configuration class in Migrations folder and add the following


code in the seed method of the migration :
context.Categories.AddOrUpdate(
p => p.Name, //Uniqueness property
new Category { Name = "Medicament" },
new Category { Name = "Vetement" },
new Category { Name = "Meuble" }
);
context.SaveChanges();

6. Execute the command Update-Database


-TargetMigration:"ModifyNameImage"
in the Package Manager Console. This command will apply any pending
migration to the database and then check the database.

Part 4 Complex Type:


Step 1 : Lets add a Complex Type Address in entities folder in
MyFinance.Domain project
public class Address {
public string StreetAddress { get; set; }
public string City { get; set; }
}

To create a new Complex Type we have to follow those rules otherwise we have to
configure a complex type using either the data annotations or the fluent API.

Step 2 : Lets update Chemical entity with the complex type as the following :
public class Chemical : Product
{
public string LabName { get; set; }
public Address LabAddress { get; set; }
}

Part 5 Configuration using annotations:


Step 1 : Add a reference to System.ComponentModel.DataAnnotions
assembly to the MyFinance.Model project

Step 2 : Now , we will add different data annotations to configure those entities.

In class Product : properties should be configured as the following :


the property Name should be
required
The user input string have the length 25 (max)
The property have length 50 (max)
An error message will be displayed if the rules are not
respected.
The property Description should be
Multiline
The property Price should be
Currency
The property Quantity should be
Positive integer
The property DateProd should be
Displayed as Production Date
Valid Date
The property CategoryId should be
The foreign Key property to the Category entity.
public class Product
{
public int ProductId { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required(ErrorMessage = "Name Required")]
[StringLength(25, ErrorMessage = "Must be less than 25 characters")]
[MaxLength(50)]
public string Name { get; set; }
[Range(0,double.MaxValue)]

[DataType(DataType.Currency)]
public double Price { get; set; }
[Range(0, int.MaxValue)]
public int Quantity { get; set; }
[DataType(DataType.ImageUrl),Display(Name = "Image")]
public string ImageName { get; set; }
[Display(Name = "Production Date")]
[DataType(DataType.DateTime)]
public DateTime DateProd { get; set; }
//foreign Key properties
public int? CategoryId { get; set; }
//navigation properties
[ForeignKey("CategoryId ")]
//useless in this case
public virtual Category Category { get; set; }
public virtual ICollection<Provider> Providers { get; set; }
}

MaxLength is used for the Entity Framework to decide how large to make a string
value field when it creates the database.
StringLength is a data annotation that will be used for validation of user input.
DataType : is used to specify a specific type : email, currency, card number
Display is used to change the displayed name of the property
Range is used to limit valid inputs between min and max values.
Foreignkey : To configure the foreign key

In class Provider : properties should be configured as the following :


the property Id should be
Key (Id is already a primary key By Convention)
The property Password should be
Password (hidden characters in the input)
Minimum length 8 characters
Required
The property ConfirmPassword should be
Required
Not mapped in the database
Password
Same value as Password property
The property Email should be
Email
Required

public class Provider


{
[Key] // optional !
public int Id { get; set; }
public string UserName { get; set; }
[Required(ErrorMessage = "Password is required")]
[DataType(DataType.Password)]
[MinLength(8)]

public string Password { get; set; }


[NotMapped]
[Required(ErrorMessage = "Confirm Password is required")]
[DataType(DataType.Password)]
[Compare("Password")]
public string ConfirmPassword { get; set; }
[Required,EmailAddress]
public string Email { get; set; }
public bool IsApproved { get; set; }
public DateTime? DateCreated { get; set; }
virtual public List<Product> Products { get; set; }
}

Key Define the Key column in the database..


NotMapped : no column would be generated in the database
Comapre : compare two properties
MinLength the opposite of MaxLength

Step 3 : Lets Test


1.
2.
3.
4.
5.

Update the database using migration


Delete ProductsController and products folder under Views folder
scaffold a new ProductsController
scaffold ProvidersController
Run and test

Part 6 Configuration using fluent API:


Note that the data annotations and the fluent API configuration could
coexist.

Step 1 : Create new Folder named Configurations in MyFinance.Data


project

Step 2 : Add CategoryConfiguration classes in the Configurations folder


public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration()
{
ToTable("MyCategories");
HasKey(c => c.CategoryId);
Property(c => c.Name).HasMaxLength(50).IsRequired();
}
}

-This class inherit from the generic class EntityTypeConfiguration , defined in


System.Data.Entity.ModelConfiguration , then we pass the type that we want to
configure
-In fact, Following the convention Entity Framework names the table created in
the database with the name of the entity in plural. In this case the table
generated will be named Categories. Therefore, when we want to change the
name of the table we use the method ToTable(MyCategories).
-We will apply some rules on the Name and the Description proprieties

Step 3 : Add ProductConfiguration classes in the Configurations folder : This


step contains 3 main parts. The first part refers to the many-to-many
association between products and providers, the second part configure the
inheritance, the third part configure the one-to-many association between the
Product and the Category.
public class ProductConfiguration : EntityTypeConfiguration<Product>
{
public ProductConfiguration()
{
//properties configuration
Property(e => e.Description).HasMaxLength(200).IsOptional();
//Many to Many configuration
HasMany(p => p.Providers)
.WithMany(v => v.Products)
.Map(m =>
{
m.ToTable("Providings"); //Table d'association
m.MapLeftKey("Product");
m.MapRightKey("Provider");
});
//Inheritance
Map<Biological>(c =>
{
c.Requires("IsBiological").HasValue(1); //isBiological is the descreminator
});
Map<Chemical>(c =>
{
c.Requires("IsBiological").HasValue(0);
});
//One To Many
HasOptional(p => p.Category) // 0,1..* //if you need 1..* use HasRequired()
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId)
.WillCascadeOnDelete(false);
} }

The Many to many part configure the association between the Product class and
The Provider class

Those classes have a many to many association. Entity Framework uses


the convention to create an association table following this pattern. The
name of the table will be the name of the first entity concatenated with the
name of the second one. This table will contain two columns, those names

will follow the next pattern:


[The_name_of_the_navigation_propriety]
[The_name_of_the_primary_Key]. This example
explains better.
In this case we want to alter the name of the
association table as well as the names of the
columns.
The inheritance part customizes the TPH inheritance:

Indeed Entity Framework create a flag column named


Discriminator, so we want to change the name of
the column to IsBiological. This column will contain
the value 1 in the case of adding a Biological object.
Otherwise it will contain the value 0.

The final part customizes the one-to-many association


between the Product class and the Category class.

The Product can belong to a Category, which explains the optional


relationship. In the HasOptional method, we pass the navigation
propriety Category of the Product Class. The method WithMany
specifies that the Category contains many Products. As well as the other
method, we pass the navigation navigation property Products of the
Category class.
We also want to disable the Cascade On Delete to conserve products after
deleting an associated category. For this aim the foreign key CategoryId
should be nullable

Step 4 :Finally we configure the complex type Address by creating this class:
public class AddressConfiguration : ComplexTypeConfiguration<Address>
{
public AddressConfiguration()
{
Property(p => p.City).IsRequired();
Property(p => p.StreetAddress).HasMaxLength(50)
.IsOptional();
}
}

Step 5 : Update the Context to apply these configurations so, in the


MyFinanceContext class override the OnModelCreating method like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//If you want to remove all Convetions and work only with configuration :
// modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Configurations.Add(new CategoryConfiguration());
modelBuilder.Configurations.Add(new ProductConfiguration());
modelBuilder.Configurations.Add(new AddressConfiguration());
}

Step 6 : Update the database by migration and explore it to see what the
configurations have done.

Part 7 Create of custom convention:


Step 1 : Add a new folder in MyFinance.Data project called
CustomConventions

Step 2 : We will create a class called DateTime2Convention in order to change


to type of the columns generated by DateTime proprieties.
public class DateTime2Convention : Convention
{
public DateTime2Convention()
{
this.Properties<DateTime>().Configure(c =>
c.HasColumnType("datetime2"));
}
}

Step 3 : Update the Context to apply these custom convention so, in the
MyFinanceContext class override the OnModelCreating method like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//If you want to remove all Convetions and work only with configuration :
// modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Configurations.Add(new CategoryConfiguration());
modelBuilder.Configurations.Add(new ProductConfiguration());
modelBuilder.Configurations.Add(new AddressConfiguration());
modelBuilder.Conventions.Add(new DateTime2Convention());
}

Part 8 Design Patterns:


Step 1 : Add a new folder in MyFinance.Data project called Infrastructure
Step 2 :-Lets move now to the implementation of CRUD methods. No , we wont
use the DAO classes. Why?
-In fact, the pattern DAO is an anti-pattern rather than a design pattern.
Supposing that we have 100 pocos, then we have to create 100 DAO classes
where we will replicate the same code 100 times.
-Moreover , when we want to modify the behavior of an update method for
example , then we have to do the same thing 100 times.
-Therefore , we will use the Repository pattern. The repository pattern is an
abstraction layer which provides a well-organised approach to maintaining a
separation between an application's data access and business logic layers. This
gives us the important advantages of making code more maintainable and
readable and improving the testability of our code. It also works great with
dependency injection!

-Lets add first an interface IRepositoryBase.


public interface IRepositoryBase<T>
where T : class
{
void Add(T entity);
void Delete(Expression<Func<T, bool>> where);
void Delete(T entity);
T Get(Expression<Func<T, bool>> where);
T GetById(long id);
T GetById(string id);
IEnumerable<T> GetMany(Expression<Func<T, bool>> where = null,
Expression<Func<T, bool>> orderBy = null);
void Update(T entity);
}

-Lets add first an interface IRepositoryBaseAsync.


public interface IRepositoryBaseAsync<T> : IRepositoryBase<T>
where T : class
{
Task<int> CountAsync();
Task<List<T>> FindAllAsync(Expression<Func<T, bool>> match);
Task<T> FindAsync(Expression<Func<T, bool>> match);
Task<List<T>> GetAllAsync();
}

-Then we will add a RepositoryBase class. In the first hand, we create this
abstract class is a generic class which contain generics CRUD methods. In the
second hand, we will add the different repositories in which we will extend
IRepositoryBaseAsync<T> of a specific entity.
-As we see in this class, we pass a DatabaseFactory object through the
constructor and we create a generic Set since this Set will be used by the
different entities.
-After getting the Context object created by the DatabaseFactory we add some
generics methods.
-Note that in somes methods like public virtual void
Delete(Expression<Func<T, bool>> where)

we can pass a lambda expression to add a criteria instead of creating


several methods to delete by criteria.
public class RepositoryBase<T> : IRepositoryBaseAsync<T> where T : class
{
private MyFinanceContext dataContext;
private readonly IDbSet<T> dbset;

public RepositoryBase(MyFinanceContext dataContext)


{
this.dataContext = dataContext;
dbset = dataContext.Set<T>();
}
#region Synch Methods
public virtual void Add(T entity)
{
dbset.Add(entity);
}
public virtual void Update(T entity)
{
dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
dbset.Remove(entity);
}
public virtual void Delete(Expression<Func<T, bool>> where)
{
IEnumerable<T> objects = dbset.Where<T>(where).AsEnumerable();
foreach (T obj in objects)
dbset.Remove(obj);
}
public virtual T GetById(long id)
{
return dbset.Find(id);
}
public virtual T GetById(string id)
{
return dbset.Find(id);
}
//public virtual IEnumerable<T> GetAll()
//{
// return dbset.ToList();
//}
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where = null,
Expression<Func<T, bool>> orderBy = null)
{
IQueryable<T> Query = dbset;
if (where != null)
{
Query = Query.Where(where);
}
if (orderBy != null)
{
Query = Query.OrderBy(orderBy);
}
return Query;
}
public T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
#endregion

#region Async methos

public async Task<int> CountAsync()


{
return await dbset.CountAsync();
}
public async Task<List<T>> GetAllAsync()
{
return await dbset.ToListAsync();
}
public async Task<T> FindAsync(Expression<Func<T, bool>> match)
{
return await dbset.SingleOrDefaultAsync(match);
}

public async Task<List<T>> FindAllAsync(Expression<Func<T, bool>> match)


{
return await dbset.Where(match).ToListAsync();
}
#endregion

Now, we create a Repositories Folder to add the different repositories. Then


we add the following repositories CategoryRepository.
Thus, we do not need to replicate CRUD methods; we can just add some specific
methods in those repositories.
public static class CategoryRepository
{
public static IEnumerable<Product> ProductByCategory(this
IRepositoryBaseAsync<Category> repository, int id)
{
return repository.GetById(id).Products.AsEnumerable();
}
public static IEnumerable<Product> MostExpensiveProductByCategory(this
IRepositoryBaseAsync<Category> repository,int id)
{
return repository.ProductByCategory(id)
.OrderByDescending(b => b.Price)
.Take(5)
.AsEnumerable();
}
public static IEnumerable<Product> GetProductsByCategory(this
IRepositoryBaseAsync<Product> repository,string categoryName)
{
return repository.GetMany(x => x.Category.Name == categoryName);
}

Step 3 :-Now we will see the last design pattern in our project The Unit Of
Work.

-The idea is that we can use the Unit of Work to group a set of related operations
the Unit of Work keeps track of the changes that we are interested in until we
are ready to save them to the database.
-The pattern Unit Of Work consists in making one or more transactions database
committed or rolled back together.
-The pattern "Unit of work" is used to:

-Group several "repositories" so they share a context of unique database


(DbContext).
-Apply the SaveChanges () method on the instance of the context defined and
ensure that any changes connected to each other will be made in a coordinated
manner.
-Lets add the next interface IUnitOfWork in the Infrastructure folder:
public interface IUnitOfWork : IDisposable
{
IRepositoryBaseAsync<T> getRepository<T>() where T : class;
void CommitAsync();
void Commit();
void Dispose();

-Then add The UnitOfWork class:


{

public class UnitOfWork : IUnitOfWork

private MyFinanceContext dataContext;


public UnitOfWork( MyFinanceContext dataContext)
{
this.dataContext = dataContext;
}
public void Commit()
{
dataContext.SaveChanges();
}
public async Task CommitAsync()
{
await dataContext.SaveChangesAsync();

}
public void Dispose()
{
dataContext.Dispose();
}
public IRepositoryBaseAsync<T> getRepository<T>() where T : class
{
IRepositoryBaseAsync<T> repo = new RepositoryBase<T>(dataContext);
return repo;
}
}

Part 8 Business Layer


Step 1:-We move to the business layer (MyFinance.Service), Lets add an
interface and a service IEBuyService and EBuyService:
public interface IEBuyService
{
void CreateProduct(Product p);
void Commit();
void Dispose();
}

public class EBuyService : IEBuyService


{
MyFinanceContext dataContext = new MyFinanceContext();
IUnitOfWork utOfWork = null;
public EBuyService()
{

utOfWork = new UnitOfWork(dataContext);

public void CreateProduct(Product p)


{
utOfWork.getRepository<Product>().Add(p);
}
public void Commit()
{
utOfWork.Commit();
}
public void Dispose()
{
utOfWork.Dispose();
}
}

Potrebbero piacerti anche