Showing posts with label N-Tier Applications. Show all posts
Showing posts with label N-Tier Applications. Show all posts

Saturday, January 8, 2011

Building N-Tier Applications with Entity Framework 4

Introduction

This post describes using of EF4 for building N-Tier applications. I've decided to write about this topic, after reading an excellent series of articles about N-Tier applications by Daniel Simmons:

1. N-Tier Application Patterns
2. Anti-Patterns to Avoid in N-Tier Applications
3. Building N-Tier Apps with EF 4

Those articles provide in-depth explanation about different issues and considerations we need to take into account, while building N-Tier applications.

Self-Tracking Entities

Self-tracking entities are smart objects with an ability to keep track on their own changes. The key difference between them and regular datasets is that self-tracking entities are plain-old CLR objects (POCO) and consequently are not tied to any specific persistance technology. They are relatively simple objects that represent the entities and information about changes that they went through.

T4 Templates

T4 templates are text templates that contain text blocks and control logic mixed together and can generate a text file, similar to Velocity Template Engine used in Java.

In our example we will use T4-based code generator to create our self-tracking entities.
You need to download and install it before you start.

Building N-Tier Application

The application we're going to build is based on the example from Daniel Simmons third article and uses Northwind database as a back-end.

Let's start with building our data access layer project:

New Project--->Class Library Project, name it Northwind.DAL
Add--->New Item--->ADO.NET Entity Data Model



We will generate our model from the database:



Our model will look like this:



Right-click on the model designer--->Add Code Generation Item--->ADO Self-Tracking Entity Generator



This action will add T4 template to our project. You can also notice that it will disable the default code generation for our EF model



Now we're going to move our newly generated self-tracking entities to separate project in order to decouple the DAL project from the entities.

Right-click on the solution item--->Add New Project--->Class Library project, name it Northwind.Entities.
Afterwards, you simply cut the Northwind.tt item and paste it into Northwind.Entities project.
Don't forget to reference System.Runtime.Serialization assembly in the Northwind.Entities project and add a reference to Northwind.Entities project in Northwind.DAL project.



We're ready to add WCF service layer to our application.
Right-click on the solution item--->Add New Project--->WCF Service Application project, name it Northwind.Service.
Rename the created service definition interface to INorthwindService and move it to the entities project.

Add following methods to the INorthwindService:
[ServiceContract]
public interface INorthwindService
{
[OperationContract]
IEnumerable<products> GetProducts();

[OperationContract]
Customers GetCustomer(string id);

[OperationContract]
bool SubmitOrder(Orders order);    
}


I know that in a real application we would add additional layers like Business Logic layer and Service Interface layer, but for the sake of simplicity we will leave it out.

Let's add an implementation to our service:
public class NorthwindService : INorthwindService
{
public IEnumerable<products> GetProducts()
{
using (var ctx = new NorthwindEntities())
{
return ctx.Products.ToList();
}
}

public Customers GetCustomer(string id)
{
using (var ctx = new NorthwindEntities())
{
return ctx.Customers.Include("Orders")
.Where(c => c.CustomerID == id)
.SingleOrDefault();
}
}

public bool SubmitOrder(Orders newOrder)
{
using (var ctx = new NorthwindEntities())
{
ctx.Orders.ApplyChanges(newOrder);
return ctx.SaveChanges() > 0;
}
}
}


Please note the Include method used in GetCustomer method. This method allows so-called eager loading of Orders table to reduce the number of round-trips to the database.
The detailed information about different loading data options in EF may be found here.

Instead of building some "state of art" client to test our service, let's add a real test project to our solution:

Right-click on the solution item--->Add New Project--->Test Project, name it Northwind.Test



You have to add a reference to Northwind.Service and Northwind.Entities. Don't forget to clean up all the proxies automatically generated for the entities.

Add following test methods to the test class:
[TestClass]
public class NorthwindTest
{
public NorthwindTest()
{
}

private TestContext testContextInstance;

/// 
///Gets or sets the test context which provides
///information about and functionality for the current test run.
///
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}



[TestMethod]
public void TestGetProducts()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
List<Products> products = client.GetProducts();

Assert.IsTrue(products.Count > 0);
}
}

[TestMethod]
public void TestGetCustomer()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
Customers customer = client.GetCustomer("ALFKI");
Assert.IsNotNull(customer);
}
}

[TestMethod]
public void TestSubmitOrder()
{
using (NorthwindServiceClient client = new NorthwindServiceClient())
{
var products = new List<Products>(client.GetProducts());
Assert.IsTrue(products.Count > 0);

var customer = client.GetCustomer("ALFKI");
Assert.IsNotNull(customer);               

try
{
// add a new order
var newOrder = new Orders();
newOrder.OrderDate = DateTime.Now;
newOrder.RequiredDate = DateTime.Now;

var orderedProduct = 
products.Where(p => p.ProductName ==  "Chang")
.Single();
Order_Details orderDetails = new Order_Details()
{
ProductID = orderedProduct.ProductID,
Quantity = 1

};
newOrder.Order_Details.Add(orderDetails);                                       
customer.Orders.Add(newOrder);

var submitSuccess = client.SubmitOrder(newOrder);
Assert.IsTrue(submitSuccess);
}
catch (Exception ex)
{
TestContext.WriteLine(ex.StackTrace);
Assert.Fail();
}
}
}
}


I think we're done.

To see your application in action just run all test methods from the "Test" menu.

The complete code could be downloaded here.

This is it,

Mark.