Feature CTP Walkthrough: Code Only for the Entity Framework (Updated)

This post covers Code Only improvements in the CTP3 release for VS 2010 Beta2. This walkthrough shows how you can change the default model like specifying property facets and navigation property inverses as well change the default mapping by changing the default inheritance strategy and table and column names. You can learn more about Code-Only improvements from our blog post.

Steps 1 to 9 cover getting started with the project. If you are familiar with the earlier CTP of Code Only and want to see some of the improvements in CTP2, please jump to Step 10.

1) Create a Console Application called "CodeOnlyWalkthru":

Untitled

2) Add a new Project to the "CodeOnlyWalkThru" solution:

Untitled

 

3) Choose 'Class Library' and call the library "Blogging":

Untitled

Our Blogging library will consist of the following classes

Untitled

 

4) Add the required classes in the Blogging project:

Right click on the project and add a class called “Blog” and then paste this code into the class

 public class Blog
 {
     public Blog(){}
 
     public int ID { get; set; }
     public string Name { get; set; }
     public string Url { get; set; }      
     public User Owner { get; set;}
     public ICollection<Post> Posts { get; set; }         
    
 }

In the Blog class right click on the reference to the non-existent Post and class choose “Generate > Class” from the context menu. Paste the code below into the generated class

 public class Post
    {
        public Post() { }
        public int ID { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string PermaUrl { get; set; }
        public DateTime Created { get; set; }
        public DateTime? Posted { get; set; }
        public User Author { get; set; }
        public User Poster { get; set; }
        public Blog Blog { get; set; }
        public ICollection<Comment> Comments { get; set; }
        public int BlogID { get; set; }
    }

 

In a similar fashion, add the Comment class and paste this code into the class

 public class Comment
{
    public Comment() {}         
    public int ID { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public Person Author { get; set; }
    public Post Post { get; set; }
    public DateTime Created { get; set; }
    public DateTime? Posted { get; set; }    
}

Add the Person class and then paste this code into the class

 public class Person
    {
        public int ID { get; set; }
        public string Firstname { get; set; }
        public string Surname { get; set; }
        public string EmailAddress { get; set; }
        public ICollection<Comment> Comments { get;set ;}       
    }

Right click on the project and add a class called “User” and then paste this code into the class

 public class User : Person
    {
        public string Password { get; set; }
        public ICollection<Blog> Blogs { get; set; }    
        public ICollection<Post> AuthoredPosts { get; set;}
        public ICollection<Post> PostedPosts { get; set; }           
    }

Your project should now look like this:

Untitled

We are keeping these blogging classes in a separate project so they are compiled into an assembly that has no dependencies on the Entity Framework. The Blogging assembly is therefore persistence ignorant, which is very important for some developers. The persistence aware code lives in a separate assembly which references the persistence ignorant assembly.

6) In the "CodeOnlyWalkThru" Project add references to the Blogging Project, System.Data.Entity and Microsoft.Data.Entity.Ctp.

7) In the "CodeOnlyWalkThru" project add a new class called " BloggingModel" and paste this code in the class:

 

 public class BloggingModel : ObjectContext
{
    public BloggingModel(EntityConnection connection)
        : base(connection)
    {
        DefaultContainerName = "BloggingModel";
    }

    public IObjectSet<Blog> Blogs
    {
        get { return base.CreateObjectSet<Blog>(); }
    }
    public IObjectSet<Person> People
    {
        get { return base.CreateObjectSet<Person>(); }
    }
    public IObjectSet<Comment> Comments
    {
        get { return base.CreateObjectSet<Comment>(); }
    }
    public IObjectSet<Post> Posts
    {
        get { return base.CreateObjectSet<Post>(); }
    }
}
}

 

 

 

 

Since this class extends ObjectContext, it represents the shape of your model and acts as the gateway to your database. We added a constructor that takes an EntityConnection, which is a wrapper around the real database connection and the Entity Framework metadata, and pass it to this constructor when we ask it to create a new instance of our BloggingModel.

8) Now create BloggingDemo class and paste the code below:

 public static void Run()
  {

      var builder = new ContextBuilder<BloggingModel>();

      RegisterConfigurations(builder);
     

      var connection = new SqlConnection(DB_CONN);

      using (var ctx = builder.Create(connection))
      {
          if (ctx.DatabaseExists())
              ctx.DeleteDatabase();
          ctx.CreateDatabase();

          var EfDesign = new Blog
          {
              Name = "EF Design",
              Url = "https://blogs.msdn.com/efdesign/",
              Owner = new User
              {
                  ID = 1,
                  Firstname = "Johnny",
                  Surname = "Miller",
                  EmailAddress = "someone@example.com",
                  Password = "Viking"
              }
          };

          ctx.Blogs.AddObject(EfDesign);

          var post = new Post
          {
              Title = "Hello",
              Blog = EfDesign,
              PermaUrl = EfDesign.Url + "/2009/08/Hello",
              Body = "....",
              Author = EfDesign.Owner,
              Poster = EfDesign.Owner,
              Created = DateTime.Today,
              Posted = DateTime.Today,
          };

          ctx.Posts.AddObject(post);

          var comment = new Comment
          {
              Title = "RE:" + post.Title,
              Body = "Welcome to the world of blogging Johnny...",
              Created = DateTime.Now,
              Posted = DateTime.Now,
              Post = post,
              Author = new Person
              {
                  ID = 2,
                  Firstname = "Vincent",
                  Surname = "Chase",
                  EmailAddress = "someone@example.com",
              }
          };

          ctx.Comments.AddObject(comment);
          ctx.SaveChanges();

          Blog blog = ctx.Blogs.Single();
          foreach (var entry in blog.Posts)
          {
              Console.WriteLine(entry.Title);
              Console.WriteLine(entry.Author.Firstname);
          }
      }
      

  }

This code creates “Blogging” database and also creates a blog entry, a post and a comment. We are also creating a ContextBuilder which infers the Conceptual Model, Storage Model and Mapping. It uses that metadata plus the SqlConnection you passed in to create an EntityConnection, and finally it constructs the context(BloggingModel) by passing in the EntityConnection to the constructor.

The interesting method is RegisterConfigurations. This method uses the improvements in the CTP which allows us to customize the model as well as the mapping.

9) Paste the code for the RegisterConfigurations method:

 static void RegisterConfigurations(ContextBuilder<BloggingModel> builder)
{
    builder.Configurations.Add(new CommentConfiguration());
    builder.Configurations.Add(new BlogConfiguration());
    builder.Configurations.Add(new PostConfiguration());
    builder.Configurations.Add(new PersonConfiguration());
    builder.Configurations.Add(new UserConfiguration());

}

Note that we have created classes to encapsulate the configuration for each type. Each configuration class derives from EntityConfiguration<TEntity> and the constructor contains all the configuration logic. This is the preferred approach as it encapsulates and makes the code easier to read.

10) Add CommentConfiguration class to the project and paste the below code:

 

 

 class CommentConfiguration : EntityConfiguration<Comment>
    {
        public CommentConfiguration()
        {
            Property(c => c.ID).IsIdentity();
            Property(c => c.Title).HasMaxLength(103).IsRequired();
            Property(c => c.Body).IsRequired();
            // 1 to * relationships
            Relationship(c => c.Author).IsRequired();
            Relationship(c => c.Post).IsRequired();

            //Register some inverses
            Relationship(c => c.Post).FromProperty(p => p.Comments);
            Relationship(c => c.Author).FromProperty(u => u.Comments);
        }
    }

We have defined this class to contain the entire configuration for the comment class. We want to ensure the Primary Key is store generated which we do using IsIdentity(). We also specify additional property facets like specifying the Maxlength on the title and Body, Author and Post are required.

We also register inverses here. We are indicating that Comment.Post is the other end of the Post.Comments relationship. Adding comment1 to the post1.Comments collection has the same effect as setting the comment1.Post to post1.

We specify a similar inverse relationship between Comment.Author and Author.comments.

11) Add BlogConfiguration class to the project and paste the code below:

 public BlogConfiguration()
        {
            Property(b => b.ID).IsIdentity();
            Property(b => b.Name).HasMaxLength(100).IsRequired(); 

            //Register some inverses
            Relationship(b => b.Owner).FromProperty(u => u.Blogs);
            Relationship(b => b.Posts).FromProperty(p => p.Blog);
        }
    }

We are indicating that the ID is an identity column and other property facets.

We are also specifying inverse relationships between the Blog.Owner and Owner.Blogs and between Blog.Posts and Post.Blog.

12)Add PostConfiguration class and paste the code below:

 public PostConfiguration()
        {
            // Make the PK store generated
            Property(p => p.ID).IsIdentity();
            // Convert some '0..1 to *' relationships into '1 to *'
            Relationship(p => p.Author).IsRequired();
            Relationship(p => p.Blog).IsRequired();
            Relationship(p => p.Poster).IsRequired();
            // Setup some facets
            Property(p => p.Body).IsRequired();
            Property(p => p.PermaUrl).HasMaxLength(200);
            Property(p => p.Title).HasMaxLength(100);
            // Register some Inverses
            Relationship(p => p.Author).FromProperty(u => u.AuthoredPosts);
            Relationship(p => p.Comments).FromProperty(c => c.Post);
            Relationship(p => p.Poster).FromProperty(p => p.PostedPosts);

            //BlogID is a FK property and Blog is a navigation property backed by this FK
     Relationship(p => p.Blog).FromProperty(b => b.Posts).HasConstraint((p, b) => p.BlogID == b.ID);


        }
    }

 

Code-Only in CTP2 allows assigning Foreign Key property with a navigation property, which is what we are doing at the end of the constructor. This says Post.Blog and Blog.Posts are inverses and that Post.BlogID and Blog.ID must be the same , which implies Post.BlogID is a FK property.

13) Add the PersonConfiguration Class and paste the code below:

 public PersonConfiguration()
        {
            Property(p => p.ID).IsIdentity();
            Property(p => p.Firstname).HasMaxLength(100);
            Property(p => p.Surname).HasMaxLength(100);
            Property(p => p.EmailAddress).HasMaxLength(200);

            MapHierarchy(
                p => new
                {
                    pid = p.ID,
                    email = p.EmailAddress,
                    fn = p.Firstname,
                    ln = p.Surname,
                }
            ).ToTable("People");

        }

    }

Apart from specifying some property facets, we are also specifying the inheritance hierarchy. Code Only by convention follows TPH which generates a table per hierarchy. This would have resulted in both Person and User being stored in the same table.

If we want to instead have Person and User stored in different tables, we can specify that using MapHierarchy. We are also changing the default column names to email, fn, ln instead of EmailAddress, FirstName and SurName. Finally we indicate that we want all of this to be stored in a “People” table.

14) Add the UserConfiguration class and paste the code below:

 class UserConfiguration : EntityConfiguration<User>
    {
        public UserConfiguration()
        {
         Property(u => u.Password).HasMaxLength(15).IsRequired();

         Relationship(u => u.AuthoredPosts).FromProperty(p => p.Author);
         Relationship(u => u.PostedPosts).FromProperty(p => p.Poster);

         MapHierarchy( 
            u => EntityMap.Row( 
                EntityMap.Column(u.ID, " u i d"),
                EntityMap.Column(u.Password)
            )
         ).ToTable("Users");
        }
    }

Here we want the specify a column name with spaces in it , so we are using the EntityMap to specify the “u i d” column name. EntityMap provides an alternate mapping syntax when we need to override CLR limitations.

We are also specifying some property facets for Password and some inverse relationships.

15) Call BloggingDemo.Run(). Paste the code into Program.cs

 class Program
    {
        static void Main(string[] args)
        {
            BloggingDemo.Run();
        }
    }

Summary:

In this walkthrough we have covered some of the improvements in Code Only that allow us to make more fine grained control over our model and also customize our mappings. We looked at how to specify property facets and also relationship inverses and Foreign Keys.

We changed the default inheritance strategy of TPH to TPT also specified the Table and Column names instead of the default generated names.

Some of the features that are in the CTP2 but not covered by the WalkThrough include Complex Types, Join Table Mapping and Entity Splitting. Please refer to the blog post on how to use these features.

We are looking forward to hearing your feedback on new Code Only Improvements.

CodeOnlyWalkThrough.zip