Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Dot Net Rules

Yes, to dance beneath the diamond sky with one hand waving free

Ιστορικό Δημοσιεύσεων

Looking into Fluent API in an ASP.Net MVC 4.0 Code First application

In this post I will demonstrate with a hands-on example how to use the Fluent API to map  POCO classes (set configurations) to SQL Server tables without using the set of conventions Entity Framework Code First expects.

I will also give a short introduction to ASP.Net MVC and its main components. I will talk briefly about Entity Framework Code First, Database First and Model First.

Also I will show you how to architecture properly your ASP.Net MVC 4.0 EF Code first application.

We should not have our domain classes, data access classes and views,controllers in the same project.

We should have them in 3 different projects.

That way our code is more extensible, maintainable and easy to reuse in other non web applications that might need to consume or use EF Code first data access layer.We have a clear separation of concerns.

We can configure our POCO classes with Data Annotations. By using Data Annotations we can configure our domain-entity classes so that they can take best advantage of the EF.We can decorate our entity classes with declarative attributes.

If you want to find out how you can use Data Annotations have a look at this post.

Fluent API supports all the configurations that Data Annotations support and more.Data Annotations support only a subset of the configurations that Fluent API supports.

You can configure your classes not in a declarative way (Data Annotations) but you can create a new class and configure the entity classes through pure C# code and lambda expressions. This is the Fluent API way.Code purists prefer this way since we keep our domain classes without any other declarations.

Before I can go on I will explain that the EF Code First Model works with conventions.

  • The table names are derived from the entity class names
  • If have a entity key in entity e.f Customer (CustomerID) , this will be the Primary Key in the Customer table.How does EF do that? By default Code First will look either for a property with the name id to create the primary key.This is the default behavior. We can change that behavior with Fluent API.
  • All the string properties in my EF classes became nvarchar(max,null) .This is default behavior. We can change that behavior with Fluent API.

Through Fluent API, we can configure entities and properties in various ways.We can configure things like

  • Relationships between entities
  • The column name of an entity property
  • The column order of an entity property
  • The column type of an entity property
  • The precision of an entity property
  • If an entity property is required

We can also configure things like Inheritance Hierarchies, map an entity to multiple tables and a table to multiple entities.

I will demonstrate most of these configurations.

Let me give you a short introduction to ASP.Net MVC first. I suggest that you go through some of the videos and other learning resources from the official ASP.Net MVC site.

I will say a few words on how I understand ASP.Net MVC and what its main benefits/design goals are.

Obviously the first paradigm on building web applications on the web is Web Forms.

Web forms was the first and only way to build web application when ASP.Net was introduced to the world, nine years ago.

It replaced the classic ASP model by providing us with a strongly typed code that replaced scripting.We had/have languages that are compiled.Web forms feels like a form that you programmed with VB 6.0 for the desktop.

The main idea was to abstract the WEB. By that I mean HTML is abstracted in a way. Click events replaced "Post" operations.Since that time, web standards have strengthened and client side programming is on the rise. Developers wanted to have more control on the HTML.Web forms , as I said before handles HTML in an abstract way and is not the best paradigm for allowing full control on the HTML rendering.

ASP.Net MVC provide us with a new way of writing ASP.Net applications.It does not replace web forms. It is just an alternative project type.It still runs on ASP.Net and supports caching,sesions and master pages. In ASP.Net MVC applications we have no viewstate or page lifecycle. For more information on understanding the MVC application execution process have a look at this link .It is a highly extensible and testable model.

In order to see what features of ASP.Net are compatible in both Models have a look here.

MVC pattern has been around for decades and it has been used across many technologies as a design pattern to use when building UI. It is based on an architecture model that embraces the so called "seperation of concern pattern".

There are three main building blocks in the MVC pattern. The View talks to the Model. The Model has the data that the View needs to display.The View does not have much logic in them at all.

The Controller orchestrates everything.When we have an HTTP request coming in, that request is routed to the Controller. It is up to the Controller to talk to the file system,database and build the model.The routing mechanism in MVC is implemented through the System.Web.Routing assembly. Routes are defined during application startup.Have a look at the Global.asax file,when building an MVC application.

The Controller will select which View to use to display the Model to the client.It is clear that we have now a model that fully supports "separation of concerns".The Controller is responsible for building the Model and selecting the View.

The Controller does not save any data or state. The Model is responsible for that.The Controller selects the View but has nothing to do with displaying data to the client.This is the View's job.

The Controller component is basically a class file that we can write VB.Net or C# code. We do not have Page_Load event handler routines, just simple methods which are easy to test.No code behind files are associated with the Controller classes.All these classes should be under the Controllers folder in your application.Controller type name must end with Controller (e.g ProductController).

In the Views folder we should place the files responsible for displaying content to the client.Create subfolders for every Controller. Shared folder contains views used by multiple controllers.

In this post I will use the Razor View engine rather than the WebForms View. Razor View engine is designed with MVC in mind and it is the way (as far as I am concerned) to work with ASP.Net MVC.

Before we start I will give again a short introduction to Entity Framework. The stable version of Entity Framework as we speak is EF 5.0.It is available through Nuget and it is an open source project.

The .Net framework provides support for Object Relational Mapping through EF. So EF is a an ORM tool and it is now the main data access technology that microsoft works on. I use it quite extensively in my projects. Through EF we have many things out of the box provided for us. We have the automatic generation of SQL code.It maps relational data to strongly types objects.All the changes made to the objects in the memory are persisted in a transactional way back to the data store. 

You can search in my blog, because I have posted many posts regarding ASP.Net and EF. 

There are different approaches (paradigms) available using the Entity Framework, namely Database First, Code First, Model First. 

You can find in this post an example on how to use the Entity Framework to retrieve data from an SQL Server Database using the "Database/Schema First" approach.

In this approach we make all the changes at the database level and then we update the model with those changes. 

In this post you can see an example on how to use the "Model First" approach when working with ASP.Net and the Entity Framework.

This model was firstly introduced in EF version 4.0 and we could start with a blank model and then create a database from that model.When we made changes to the model , we could recreate the database from the new model. 

The Code First approach is the more code-centric than the other two. Basically we write POCO classes and then we persist to a database using something called DBContext.

In this application we will us the Code First approach when building our data-centric application with EF. 

Code First relies on DbContext. We create classes with properties and then these classes interact with the DbContext class.Then we can create a new database based upon our POCOS classes and have tables generated from those classes.We do not have an .edmx file in this approach.By using this approach we can write much easier unit tests.

DbContext is a new context class and is smaller,lightweight wrapper for the main context class which is ObjectContext (Schema First and Model First).

I am running VS Studio 2012 Ultimate edition but you can use Visual Studio Express 2012 for Web. You can install Visual Studio Express 2012 for Web if you download Web Platform Installer.You can download this tool from this link.

I will create an ASP.Net MVC application that has two entities, Department and Courses. They have one to many relationships between them.

1)  I am launching VS 2012 and I will Visual C# as the programming language. I will also select ASP.NET MVC 4 Web Application from the available templates. Choose C# as the development language and Internet Application. I will name my application FluentAPIMVC. This will be the startup project.

2) Now that we have our MVC project structure we will create another project in our solution. This will be a Windows Library project. I will name it FluentAPIMVC.DomainClasses. In this project I will add only the definitions of the two domain classes Course and Department. I delete the class1.cs.

I create a new class, Department.cs. The code follows

 

    public class Department
    {
        public int DepartmentId { getset; }
        public string Name { getset; }
        public decimal Budget { getset; }
        public DateTime StartDate { getset; }
        public virtual ICollection<Course> Courses { getset; }
    }

 I will add another class, Course.cs

public class Course
    {
        public int CourseID { getset; }
        public string Title { getset; }
        public int Credits { getset; }
        public int DepartmentID { getset; }
        public virtual Department Department { getset; }
    }

 

These are just two plain POCO classes that know nothing at all about Entity Framework.

 

3) I will add another project in this solution. This will be a Windows Library project. I will name it FluentAPIMVC.DomainAccess.

In this class I will add classes that act at the data access layer that will interact with the ASP.Net MVC application and the SQL Server database that will be created.

I will add references to System.Data.Entity

I will add a reference to the FluentAPIMVC.DomainClasses project as well.

Then we need to install Entity Framework 5.0. We will do that through Nuget packages. Make sure you do that.

I will add a class DepartmentDBContext to the project.

The contents of this class are:

 public class DepartmentDBContext:DbContext

    {
   
        public DbSet<Department> Departments { getset; }
        public DbSet<Course> Courses { getset; }
 
    }

This class inherits from DbContext. Now that we have the entity classes created, we must let the model know.I will have to use the DbSet<T> property.

I will add more code to this class later on.

4) We must take care of the connection string. It is very easy to create one in the web.config.It does not matter that we do not have a database yet.When we run the DbContext and query against it , it will use a connection string in the web.config and will create the database based on the classes.I will use the LocalDb

      <add name="DepartmentDBContext"
 connectionString="Data Source=(LocalDb)\v11.0;
 AttachDbFilename=|DataDirectory|\DepartmentCourses.mdf;
 Integrated Security=True"
 providerName="System.Data.SqlClient" />

 

 5) Now we can build our application to make sure everything works ok. Now I want to insert data , seed the database, to the tables.

I will add another class to the FluentAPIMVC.DomainAccess. I name it DepartmentCoursesInsert.

The contents of this class follow

 

 public class DepartmentCoursesInsert:
    DropCreateDatabaseAlways<DepartmentDBContext> {
        protected override void Seed(DepartmentDBContext context)
        {
            var departments = new List<Department>
            {
            new Department { 
                
     Name = "Physics",Budget=35500StartDate=DateTime.Parse("12/12/1999"),
     Courses = new List<Course>             
                { 
                
    new Course {Title = "Nuclear Physics"Credits= 45},
    new Course {Title = "Quantum Physics"Credits= 35}
 
                }
 
                            },
            
             new Department { 
                 
     Name = "Maths",Budget=45500StartDate=DateTime.Parse("12/12/2009"),
     Courses = new List<Course>             
                { 
                
     new Course {Title = "Trigonometry"Credits= 35},
     new Course {Title = "Analytic Geometry"Credits= 45}
 
                }
 
                            },
        
            };
 
     departments.ForEach(dep => context.Departments.Add(dep));
     base.Seed(context);
        }
    }

 

We need to add entries for the System.Data.Entity and the FluentAPIMVC.DomainClasses namespace for this class to compile.

In this class I inherit from the  DropCreateDatabaseAlways<DepartmentDBContext>

class and I will override the default behavior of that class with my class.

I will ovverride the Seed method with some data.Then I create 2 instances of the Department entity and 4 entities of the Course entity.

Then through a simple lambda expression I add the data to the database using these last lines of code.

   departments.ForEach(dep => context.Departments.Add(dep));
     base.Seed(context);

This is just a way to populate initially the database with some data. You could leave this step out and populate the database through the views.

There is another way to populate the database and you should not use DropCreateDatabaseAlways  in production databases.

We have Code First Migrations to insert data but I will not show that here. You can read about Code First Migration in this post.

Before the addition of Code First Migrations (4.1,4.2 versions), Code First database initialisation meant that Code First would create the database if it does not exist (the default behaviour - CreateDatabaseIfNotExists).

Another pattern is DropCreateDatabaseAlways which means that Code First will recreate the database every time one runs the application.

The other pattern we could use is DropCreateDatabaseIfModelChanges which means that Entity Framework, will drop the database if it realises that model has changes since the last time it created the database.

That is of course fine for the development database but totally unacceptable and catastrophic when you have a production database. We cannot lose our data because of the way that Code First works.

 

Now we need to make one more change.in the Global.asax.cs file to add a line of code


 Database.SetInitializer(new DepartmentCoursesInsert());

 

 We add this line of code to the Application_Start() method.

 

I will add references to System.Data.Entity

I will add a reference to the FluentAPIMVC.DomainAccess project as well.

 

6) Now I will create a new class in the in the FluentAPIMVC.DomainAccess project. I will name it CourseMap.

This is a configuration class that uses Fluent API to configure the type.It defines the table the entity maps to. It defines-matches the columns of the table with the entity properties. Also defines any relationships and things like Primary key e.t.c. Its contents follow.

public class CourseMap : EntityTypeConfiguration<Course>
    {
        public CourseMap()
        {
            // Primary Key
            this.HasKey(t => t.CourseID);
 
            // Properties
            this.Property(t => t.CourseID)
 .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
 
            this.Property(t => t.Title)
                .IsRequired()
                .HasMaxLength(70);
 
            this.Property(t => t.Credits)
               .IsRequired();
              
 
            // Table & Column Mappings
   this.ToTable("Course");
   this.Property(t => t.CourseID).HasColumnName("ID");
   this.Property(t => t.Title).HasColumnName("Title");
   this.Property(t => t.Credits).HasColumnName("CreditsForCourse");
   this.Property(t => t.DepartmentID).HasColumnName("DepID");
 
            // Relationships
      this.HasRequired(t => t.Department)
      .WithMany(t => t.Courses)
      .HasForeignKey(d => d.DepartmentID);
 
        }
    }

In the code above I define the primary key. I define that the primary key as an identity. Then I set that the Title property is non nullable and has a max length of 70. The entity property Credits is required.

Then I define the Table and Column mappings. The entity maps to a table called Course.Then I define the names for the column names.So I can change the entity name properties to different table column names.

Finally I define the one to many relationship.

7) Now I will create a new class in the in the FluentAPIMVC.DomainAccess project. I will name it DepartmentMap.

This is a configuration class that uses Fluent API to configure the type.It defines the table the entity maps to. It defines-matches the columns of the table with the entity properties. Also defines any relationships and things like Primary key e.t.c. Its contents follow.

public class DepartmentMap : EntityTypeConfiguration<Department>
    {
        public DepartmentMap()
        {
            // Primary Key
            this.HasKey(t => t.DepartmentId);
 
            // Properties
            this.Property(t => t.DepartmentId)
  .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
 
            this.Property(t => t.Name)
                .IsRequired()
                .HasMaxLength(40);
 
            this.Property(t => t.Budget)
               .IsRequired()
               .HasColumnType("decimal")
               .HasPrecision(204)
               ;
 
            this.Property(t => t.StartDate)
           .IsOptional();
 
            // Table & Column Mappings
 this.ToTable("Department");
 this.Property(t => t.DepartmentId).HasColumnOrder(1).HasColumnName("ID");
 this.Property(t => t.Name).HasColumnOrder(2).HasColumnName("Dep Name");
 this.Property(t => t.Budget).HasColumnOrder(4).HasColumnName("Budget");
 this.Property(t=>t.StartDate).HasColumnOrder(3).HasColumnName("Start Date");
 
        }
    }

In the code above I define the primary key. I define that the primary key as an identity. Then I set that the Name property is non nullable and has a max length of 40. The entity property Budget is required (not null in the respective column) and its datatype will be decimal with a precision of (20,4).

The StartDate entity property is not required (allow null in the respective column in the table).

Then I define the Table and Column mappings. The entity maps to a table called Department.Then I define the names for the column names.So I can change the entity name properties to different table column names.I also change the order of the columns.

I am sure you have realised what Fluent API is and how we can use it to define mappings. I have just demonstrated some of the most common settings and configurations used in Fluent API.

8) Now I need to add some more code in the DepartmentDBContext class.

I need to override the OnModelCreating method so I can give the builder useful information before it goes on and builds the model.

 protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
   modelBuilder.Configurations.Add(new CourseMap());
   modelBuilder.Configurations.Add(new DepartmentMap());
   }

 The complete code for this class follows

    public class DepartmentDBContext:DbContext
    {
 
   
    public DbSet<Department> Departments { getset; }
   public DbSet<Course> Courses { getset; }
 
  
    
 
   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
   modelBuilder.Configurations.Add(new CourseMap());
   modelBuilder.Configurations.Add(new DepartmentMap());
   }
 
    }

 9) Now I need to make some changes to the FluentAPIMVC project.

I will add a reference to the FluentAPIMVC.DomainClasses project.

I need to add a controller. I will name it DepartmentController

Have a look at the picture below.

 

Now we need to make a small change to the _Layout.cshtml file. We will add this line of code to add another item in the menu.

  <li>@Html.ActionLink("Departments""Index""Department")</li>

 

10) Build and run your application.

Navigate to the  your localhost/port/department, in my case (http://localhost:57958/Department).

Have a look at App_Data folder. You will see the DepartmentCourses.mdf localdb database.Τhis database was created from EF Code First.

Have a look at the picture below to see the table's definitions. If we go back to your configuration classes where we did the initial mapping you will see that all Fluent API configurations were honored.Have a look at the column names,types and order. Check which types allow nulls and which not.

 

Have a look at the picture below to see the department page.You can see the seeded data. You can edit existing departments,create new departments and delete them.

 

 

Hope it helps!!!

Share
Posted: Κυριακή, 29 Σεπτεμβρίου 2013 11:38 μμ από το μέλος nikolaosk

Σχόλια:

Χωρίς Σχόλια

Έχει απενεργοποιηθεί η προσθήκη σχολίων από ανώνυμα μέλη