Skip to main content

Entity Framework 4.1 and N-Tier Applications

Rate this Video 

Julie Lerman

http://thedatafarm.com

Published: June 2011

Download the code for this article


This article will provide you with a quick look at using Entity Framework 4.1 DbContext and Code First to provide data access in an n-tier application. I'll use WCF Data Services as my service tier around a context and then consume the service from a simple console app so that you can see it in action.

Building the Model

I’ll use code first to define the model but you can also use model first or database first Entity Framework models with the DbContext.

I have two classes in my domain: Blog and Post.

namespace Domain
{
  public class Blog
    {
      public int Id { get; set; }
      public string Title { get; set; }
      public string BloggerName { get; set;}
      public virtual ICollection<Post> Posts { get; set; }
    }
    public class Post
    {
      public int Id { get; set; }
      public string Title { get; set; }
      public DateTime DateCreated { get; set; }
      public string Content { get; set; }
      public int BlogId { get; set; }
      public ICollection<Comment> Comments { get; set; }
    }
}

The BlogContext class inherits from EF 4.1’s DbContext class and exposes DbSets of the two domain classes. In the same file, I’ve included database initialization code, the BlogContextInitializer class, to create my database as needed and seed it with some data.

using System.Data.Entity;
using Domain;
using System.Collections.Generic;

namespace DataLayer
{
  public class BlogContext : DbContext  
  {
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

  }

  public class BlogContextInitializer : DropCreateDatabaseIfModelChanges<BlogContext>
  {
    protected override void Seed(BlogContext context)
    {
      new List<Blog>{
        new Blog { BloggerName = "Julie", Title = "My Code First Blog",
                   DateCreated=System.DateTime.Now,
                   Posts=new List<Post>{
                           new Post{
                             Title="ForeignKeyAttribute Annotation",
                             DateCreated=new System.DateTime(2011,3,15), 
                             Content="Mark navigation property with ForeignKey"},
                           new Post{
                             Title="Working with the ChangeTracker",
                             DateCreated=System.DateTime.Now, 
                             Content="You can use db.Entry to get to state for a "+
                                     "single entry or db.ChangeTracker.Entries "+
                                     "to work with all of the tracked entries."}
                         }
                  },
        new Blog { BloggerName = "Ingemaar", Title = "My Life as a Blog",
                   DateCreated=System.DateTime.Now.AddDays(1)},
        new Blog { BloggerName = "Sampson", Title = "Tweeting for Dogs",
                   DateCreated=System.DateTime.Now.AddDays(2)}
        }.ForEach(b => context.Blogs.Add(b));
            base.Seed(context);
      }
   }
}

In my solution, the domain classes are in one project and the context is in another, DataLayer, as you can see in Figure 1. Notice that the DataLayer project has references to the DomainClasses project and to the Entity Framework 4.1 assembly, EntityFramework.dll

Figure 1: Initial Solution with Domain Classes and Context


In previous videos in this series, I set the database initializer in the application startup. But since the appliation is spread out across tiers, in this solution, I’ll be setting the initializer in the service’s web.config file.

With this in place, I can add a service to my solution that will serve up the data from the context to any application that wishes to consume it.

Building the Service

I’ll add a new ASP.NET Empty Web Application project named DataService to my solution to host my Data Service.

To this project I can then add a new item, WCF Data Service, which I’ll name BlogDataService. Once I’ve created the new service, Visual Studio will open the code file for the data service which contains a placeholder for inserting the appropriate context and comments that provide examples of how to expose your data through the service.

Before modifying the code, I need to make two changes to the project.

The first is to add a reference to the DataAccess project.

The second is to provide updated APIs so that WCF Data Services can work with the DbContext.  WCF Data Services is being refreshed so that it can leverage Entity Framework 4.1’s DbContext. At the time of writing this article, you can download Microsoft WCF Data Services March 2011 CTP 2 at http://www.microsoft.com/downloads/en/details.aspx?FamilyID=60fb0117-8cea-4359-b392-6b04cdc821be.

After installing the CTP, you’ll need to replace the default APIs that were added to the DataService project. Start by deleting System.Data.Services and System.Data.Services.Client from the project references. Replace these with references  to their updated versions, Microsoft.Data.Services and Microsoft.Data.Services.Client. The default location where those files were placed when installing the CTP is C:\Program Files\WCF Data Services Mar 2011 CTP2\bin\.NETFramework.

Now it’s time to work on the Data Service code.

The service will be serving up whatever is provided in our DataLayer.BlogContext class. Therefore you must replace the placeholder in the class declaration. That’s the comment that begins with TODO.

public class BlogDataService : 
   DataService< /* TODO: put your data source class name here */ >

 The data source class is DataAccess.BlogContext. Therefore the declaration should look like this:

public class BlogDataService : DataService<BlogContext>

By default, WCF Data Services are locked down. In other words, they won’t expose any data unless you explicitly tell them to using the SetEntitySetAccessRule method. You can use this method once to set common access rules for all of the entity sets available from the context or you can set a number of rules to define more granular access. For example, you might want users to read Blog information but to write (insert, update and delete) Post information. For demo purposes, I’ll  tell the service to expose all of my data for reading and I’ll use the shortcut “*” to express that I want all of the entity sets to be covered in this rule.

config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

There’s only one more change to make to the service, and this is particular to the fact that I’m using the newer version of the Data Services APIs. Change the code that sets the MaxProtocolVersion from V2 to V3 as shown in this line of code:

config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;

Earlier, I mentioned that I’ll set the database initiazlier in the service’s web.config file. This is done within appsettings tags in the config file.

Here’s the setting:

<appSettings>
    <add key="DatabaseInitializerForType HowDoIEF41.DataAccess.BlogContext, HowDoIDataAccess"
         value="HowDoIEF41.DataAccess.BlogContextInitializer, HowDoIDataAccess" />
  </appSettings>

The way to build this is as follows. The key must always begin with “DatabaseInitializerForType” followed by the strongly typed name of the context and then the assembly name. Notice that this assembly name is not the same as the project name, which is simply DataAccess. That’s because I changed it in the project properties. The value attribute contains a string with the strongly typed name of my initializer class and again, the name of its assembly. In this case my initializer and my context are both in the same assembly,HowDoIDataAccess.

Now you have enough to test the service. Since this example is using Code First, you don’t have to worry about setting up a database. Code First will create the database automatically. As I haven’t provided a connection string and Code First will see that the database does not yet exist. Then the database initialization code will populate the new database with the seed data I specified in the override to the Seed method.

It’s time to test out the service! Right click on the BlogDataService.svc file in Solution Explorer and choose View in Browser to see the raw output of the service. First you’ll see a list of all sets available from the service, Blogs, Posts and one other, EdmMetadatas that is specific to Code First.

Figure 2: Viewing the Service Directly in a Browse


A quick way to eliminate EdmMetadatas is to add a rule into the service code that specifies no access at all for that set:

config.SetEntitySetAccessRule("EdmMetadatas",EntitySetRights.None);

Here is the service after the new rule has been implemented.

Figure 3: The service no longer exposing EdmMetadatas


 The output is based on the OData Specification which you can learn more about at www.odata.org.

If you want to see actual data, you can drill into the Blogs, for example, by adding /Blogs to the end of the default Uri. Figure 1 shows the result of the request for Blogs. The three Blogs created in the seed method are there, although only the first is expanded. The two entry items at the end of the feed contain the other two Blogs that were seeded into the database.

Figure 4: Viewing the Blogs data in raw OData format


This request caused the following query to be executed in the database:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[BloggerName] AS [BloggerName], 
[Extent1].[DateCreated] AS [DateCreated]
FROM [dbo].[Blogs] AS [Extent1]

Consuming the Service

Now that you can see that the data service is working, let’s use the .NET Client Services in a little console application to work with the service.

I’ve created another project in my solution, this time a Console Application project.

I’ll add a Service Reference to the new console application and let the Add Service Reference Wizard discover the service that’s in the solution as you can see in Figure 4. I’ve provided the name, BlogService, to the namespace.

Figure 5: Adding a Service Reference to the Console Application


This not only causes the reference to the service to be added, but three additional references are added to the project, including Microsoft.Data.Services.Client (see Figure 6) which will allow you to programmatically interact with the service, even use LINQ to query the service.

Figure 6: References added to the new Console project


I’ll add the following GetBlogs method to the program.cs file in the Console app.

private static void GetBlogs()
{
    var svcContext = new BlogService.BlogContext(
      new Uri("http://localhost:7576/BlogDataService.svc/"));
    foreach (var blog in svcContext.Blogs)
    {
        Console.WriteLine("Title: {0}, Blogger: {1}", blog.Title, blog.BloggerName);
        svcContext.LoadProperty(blog, "Posts");
        Console.WriteLine("   Post Count:{0}", blog.Posts.Count());
    }
    Console.WriteLine("");
    Console.WriteLine("Press any key to continue....");
    Console.WriteLine("");
    Console.ReadKey();
}

This method instantiates a new BlogContext that’s exposed through the service, passing in the value of the Uri where the service can be found. With the context in hand, I then retrieve the Blogs and display some information about each one.

 As I iterate through the Blogs, I use the Load method to retrieve that Blog’s Posts — causing the context to make another request to the service — and then display the number of Posts for that Blog.

Each call to Load causes the service to send a query to the database. Here is the first of the three queries, requesting the Posts for the first Blog, which has the BlogId value, 1.

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Title] AS [Title], 
[Extent1].[DateCreated] AS [DateCreated], 
[Extent1].[Content] AS [Content], 
[Extent1].[BlogId] AS [BlogId]
FROM [dbo].[Posts] AS [Extent1]
WHERE 1 = [Extent1].[BlogId]

The next method AddPost,  performs a different type of operation on the service.

private static void AddPost()
{
    var svcContext = new BlogService.BlogContext(
     new Uri("http://localhost:7576/BlogDataService.svc/"));
    var post = new Post { BlogId = 2, Title = "A New Post", DateCreated = DateTime.Now };
    svcContext.AddToPosts(post);
    svcContext.SaveChanges();
}

In this method, I again instantiate the context. Then I create a new Post object and with the AddtoPosts method, I make the context aware of the new post. The context will understand that the new post should be inserted into the database. The call to SaveChanges will force the service to persist that post into the database.

However, the service does not yet provide an access rule for inserting Posts. Currently there is only read access granted to both Blogs and Posts.

You can return to the service and add a rule for the Posts which allows the Posts to be inserted (WriteAppend) and read (AllRead). Below is the complete set of access rules now in the service.

config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("EdmMetadatas",EntitySetRights.None);
config.SetEntitySetAccessRule(
  "Posts", EntitySetRights.WriteAppend | EntitySetRights.AllRead);

Running the AddPost method causes the following command to be executed in the database:

exec sp_executesql N'insert [dbo].[Posts]([Title], [DateCreated], [Content], [BlogId])
values (@0, @1, null, @2)
select [Id]
from [dbo].[Posts]
where @@ROWCOUNT > 0 and [Id] = scope_identity()',N'@0 nvarchar(max) ,@1 datetime2(7),@2 int',@0=N'A New Post',@1='2011-05-05 10:37:57.5705577',@2=2

Thanks to the changes to the Data Services APIs, WCF Data Services works seamlessly with Entity Framework 4.1’s DbContext and Code First to quickly create applications that you can distribute across tiers.