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.

15 comments:

  1. Great article & great references!!

    Thank you,
    Diego

    ReplyDelete
  2. Thanks for giving that types of article. It helps me.

    ReplyDelete
  3. thank you so much, could I please ask you for some direction on this part: "additional layers like Business Logic layer and Service Interface layer,"

    I would like to learn how and where I should implement this acording to this framework you have out line it here. I am unsure where would i put it, would it be its own project, Northwin.BusinesLayer, what about the Service Interface layer.

    thank you for any information

    ReplyDelete
  4. I was glad to see the code for download, however I unzip it, build, and i got this error
    Error 1 The type or namespace name 'IObjectWithChangeTracker' could not be found (are you missing a using directive or an assembly reference?) F:\Downloads\NorthwindNTierWithEntityFramework\NorthwindNTierWithEntityFramework\Northwind.DAL\Northwind.Context.Extensions.cs 33 124 Northwind.DAL

    ReplyDelete
  5. Hi John,
    First of all, let me give you a brief explanation of Service Interface(SI) layer.
    You can think of a service interface as a mediator between Northwind.Service layer and Northwind.BusinessLogic layer you've mentioned. Its main role is to expose the business logic implemented in the application. It's an ideal place to implement cross-cutting tasks for your services such as: logging, auditing, exception handling, security checks and so forth.
    Practically, just add another Class Library project to your solution, reference it from Northwind.Service project and transfer all inbound messages from Northwind.Service to Northwind.BusinessLogic.

    Northwind.Service-->Northwind.ServiceInterface-->Northwind.BusinessLogic-->Northwind.DAL

    I would advice you to read the excellent "Microsft Application Architecture Guide" for more details.

    ReplyDelete
  6. The is answer to anonymous guest regarding the compilation error: please try to using for the unrecognized objects (right-click-->resolve).
    Let me know if you still have problem.
    Mark.

    ReplyDelete
  7. I just have to say thank you so much for taking the time to answer my question this article was of great help. I read several other articles but this was the best one explaining how to actually do it!.

    ReplyDelete
  8. You create a WCF Service Application project, in the samples I have seen, they always create a WCF Service Library, I can then setup endpoints, and edit the app.config, but with the WCF Service Application I just get a web.config. So I am just a confused as to when use the one and when use the other. And how do I run a Service Application in a real world situation

    thank you for your help

    ReplyDelete
  9. Sorry, I should not have said always create a WCF Service Library... just in the samples I have seen so far.

    ReplyDelete
  10. I found some answers and I didnt want to make use of your valuable time answering. The differences between the two, and for what it seems it is in fact the WCF Service Application is used more often. ( in case someone has the same question)

    http://www.itscodingtime.com/post/The-difference-between-Visual-Studios-WCF-Service-Application-and-WCF-Service-Library-project-templates.aspx

    ReplyDelete
  11. I tried to create a client Win Form application consuming the wcf service created in this article, I didnt make a reference to Northwind.Entities, since my services will be consumed by people "around the world" and I cant send them the dll ....

    I tried to run the same kind of test that where in Northwind.Test. And everything works but this part.

    newOrder.Order_Details.Add(orderDetails);

    is at line 88 in the TestSubmitOrder() method
    it says Object reference not set to an instance of an object.

    if I instanciated it like so:
    oOrders.Order_Details = new List();
    then I dont have any problems.

    Do you have any idea why I have to instantiation it first if I am using the service without referencing the Northwind.Entities in my client. What can I do so it would behave like the Northwind.Test? where no instantiation was required ?

    thank you

    P

    ReplyDelete
  12. Hi Peter,

    There are several consideration you need to take into account when working with self-tracking entities.
    One of them is related to using of STE on the client side.
    When you don't include the real entities located in the Northwind.Entities class library and add a proxy by using "Add Service Reference" option, the proxy will include "fake" entities, meaning that you have totally different objects that have nothing to do with STE. That's why you've encountered different behavior.
    You can read about it here:
    http://msdn.microsoft.com/en-us/library/ff407090.aspx
    In your case, I would consider using of DTOs instead of STEs.

    Hope it helps,
    Mark.

    ReplyDelete
  13. Hi Peter,

    One problem i cant seem to get over is when u need to make a change to your model, you need to recreate the code generation and then re-drag it to the entities class. It would be great if the code generation item would ask if you would like these entities in a separate assembly.

    Cheers
    Barry

    ReplyDelete
  14. Hey Dieg. Great article. Is there much difference between how EF4.0 is used (like above) and EF4.1?

    ReplyDelete
  15. "I know that in a real application we would add additional layers like Business Logic layer and Service Interface layer"

    It would be nice to extend this article to describe how to define the BLL and the rest, maybe with the use of IoC. Or could you point us to some relevant resources/samples? Thanks a lot

    ReplyDelete