次の方法で共有


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

Comments

  • Anonymous
    June 22, 2009
    PingBack from http://leriksen71.wordpress.com/2009/06/22/entity-framework-ctp-with-code-only-now-available/

  • Anonymous
    June 22, 2009
    Finally! Huge step in the right direction. This is ultimately what I have wished for x-mas the last couple of years. I suppose I will miss the mappings, hierarchy and complex types but hey. You will be able to make that for the release version right?

  • Anonymous
    June 24, 2009
    I agree with Mikael; this is a very nice step forward.

  • Anonymous
    June 24, 2009
    How testable is the code only approch?????

  • Anonymous
    June 25, 2009
    Very great! It will surely save us lots of time!

  • Anonymous
    June 27, 2009
    Hi Please, can add a complete sample using a generic repository and unit of work. The actual sample it is not generic. A good sample help to learn!

  • Anonymous
    June 28, 2009
    Very interesting. When did you planned to release the beta version?

  • Anonymous
    July 12, 2009
    I am not sure I like having to specifiy the DefaultContainerName in the ObjectContext.  I walked through this sample with my own object model and it seemed that their is some some coupling to the name of my ObjectContext class. If so, why can't the framework infer the name and let me go on?   I am excited to see more about this feature!

  • Anonymous
    July 14, 2009
    The comment has been removed

  • Anonymous
    July 14, 2009
    The comment has been removed

  • Anonymous
    August 06, 2009
    @cowgaR:

  1. Not in scope for this project.
  2. Personal style choice, not enforced.
  3. Coming in next CTP.
  4. Because the existing provider model does not support DDL for create/drop/exists database. If you don't need those then it could run with an existing provider.
  5. We have more stuff coming. [)amien
  • Anonymous
    August 23, 2009
    hope to support full custom mapping for entityset info just like IQToolKit, so i can support partial fields loading and different name convidence mapping.

  • Anonymous
    November 13, 2009
    still can't include both property in entity: public int Owner_ID { get; set; } public User Owner { get; set; } owner is for linq query, owner_id is for insert and data load for datatable. class and object is not the whole world. we still need table and datable.

  • Anonymous
    November 14, 2009
    Can you post a f# source code version please, whats the best way to deal with Expression<Func<T,TResult>> in F# ? Thanks

  • Anonymous
    November 15, 2009
    How do you handle Lazy/Eager loading scenarios when using the "Code Only Model" ? Roberto.-

  • Anonymous
    November 17, 2009
    Would it be possible to map the Discrimator column in TPH inheritance, i.e. make it visible in an entity of the hierarchy, similar to foreign key mapping.

  • Anonymous
    December 31, 2009
    I'm tryng to do some tests with EntityConfiguration. I have 2 questions:

  1. is it possible to map a property of an entity as complex type?
  2. how can I specify a column name for the property? Thanks
  • Anonymous
    February 05, 2010
    Shooting from the hip here. Is it possible to have like a base model with certain fixed entities and another in another assembly with customized entities? A thought: I could inherit an existing base ObjectContext in my client specific assembly?

  • Anonymous
    February 23, 2010
    Is this entry going to be updated for RC, seems like a lot of it has changed in the RC.

  • Anonymous
    February 24, 2010
    Running this on RC generates the following errow while trying to generate the database; Introducing FOREIGN KEY constraint 'Post_Author' on table 'Posts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.

  • Anonymous
    February 24, 2010
    As does this one Introducing FOREIGN KEY constraint 'Post_Poster' on table 'Posts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint. See previous errors.

  • Anonymous
    February 24, 2010
    Putting the code below in the PostConfiguration class will fix the foreign key exception: Relationship(p => p.Author).CascadeOnDelete = false; Relationship(p => p.Poster).CascadeOnDelete = false;

  • Anonymous
    March 02, 2010
    Hi there, Just wondering some question about the relationships between entities. If I want to define 1 to 0..1 relation from User and Address, I would prefere to put some nullable FK_Address on the User table instead of a FK_User on the Address table. My goal is to not touch the Address entity for dependency reasons and keep it reusable in other relationship such as with a Company entity. I know that it seems strange to define nullable foreign keys which could be a non sense but I'm wondering of the mapping definition allows some conditional expression such as e => e.FK == null || e.FK = e.SubEntity.Id And what will be the impact of that using expression translated to LinqToSQL statements? Thanks in advance,    Coyote

  • Anonymous
    April 15, 2010
    When I use relation and create database context generates columns with names like Author_PersonId. Is there a method that allows to rename column to AuthorId?

  • Anonymous
    April 30, 2010
    Great post - thanks! I'm working with the released version of 4.0/VS 2010/EF and I can't seem to find the namespace where the ContextBuilder is stored.  In your CTP example it is in 'Microsoft.Data.Objects', but I can't seem to find that reference.  I had no issues creating a Model-First example, but I'm stuck on the Code-First example I'm working with...any help is greatly appreciated! -- Danny ddouglass[at]gmail[dot]com

  • Anonymous
    May 09, 2010
    Salam Danny, download the CTP from here http://www.microsoft.com/downloads/details.aspx?familyid=AF18E652-9EA7-478B-8B41-8424B94E3F58&displaylang=en also make sure to add reference for Microsoft.Data.Entity.CTP to your project.

  • Anonymous
    May 25, 2010
    Good stuff- FYI for anyone trying to build and run: It works great against a SQL 2008 database with one exception.  The database generation script will add "ON DELETE CASCADE" in the foreign keys between Posts and Users tables.  Modify the Inverses in the PostConfiguration class: Relationship(p => p.Author).FromProperty(u => u.AuthoredPosts).CascadeOnDelete = false; Relationship(p => p.Comments).FromProperty(c => c.Post); Relationship(p => p.Poster).FromProperty(p => p.PostedPosts).CascadeOnDelete = false; ...and all will be well.

  • Anonymous
    May 28, 2010
    Recently I have gone through some beginning videos from MSDN sites on LINQ to SQL, LINQ to Entities, and Entity Framework. As I talked to a friend about how nice it is to use EF he told me there is a problem in using EF: Namely, after the application was compiled and deployed the end users may want to add some columns to a table to enhance some features. This means he had to go back to the model, make the changes, and recompile and redeploy the application. When the application is having a few hundreds tables there were some changes to some tables almost every week. He was wondering aloud as to what can be done. This scenario got me interested to look at this problem. While it seems easy to attach a button and some input boxes in the UI and get the LINQ to EF to run a stored procedure to alter the table the compiled assembly won’t refresh itself unless we go back to the design stage, refresh the model, recompile, and then redeploy. In the case of a deleted column refreshing in the model won’t even self-correct the model. The property has to be manually deleted first. A search for ready solution in the Internet led me to your site and I found some interesting posts. Looking at your posts it appears to me that you can use codes only to create the objectcontext and therefore the database. I don’t see any ready codes that can modify the database or objectcontext once created and later partially filled with data. Or is it because I don’t understand the working of your codes? Do you happen to have codes somewhere I can refer to, please? Do you know of anybody else working on such ideas? Your advice would be much appreciated.

  • Anonymous
    June 29, 2010
    We just started using EF4 and CTP3 and have problem loading object relations after storing. I downloaded the demo, and it ran perfectly. BUT: If I take away the code for recreating the database, adding and saving objects. All data are still stored in my database. Just running:            var builder = new ContextBuilder<BloggingModel>();            RegisterConfigurations(builder);            var connection = new SqlConnection(DB_CONN);            using (var ctx = builder.Create(connection))            {                Blog blog = ctx.Blogs.Single();                foreach (var entry in blog.Posts)                {                    Console.WriteLine(entry.Title);                    Console.WriteLine(entry.Author.Firstname);                }            } I still get a blog object, but blog.Post is null. This is the same problem we have in our own project. What am I missing? Any help appreciated. Magnus

  • Anonymous
    July 08, 2010
    @Magnus - From what I've been able to research, you need to do something like this:            using (var ctx = builder.Create(connection))            {                var blogs = from b in (ctx.Blogs as ObjectQuery<Blog>).Include("Owner") select b;                foreach (Blog b in blogs)                {                    Console.WriteLine(b.Owner.Firstname);                }            }            Console.ReadLine(); The important part is the .Include() and the cast to ObjectQuery. Other than this, I don't know how to do lazy-loading with Code-Only... I wish I did.

  • Anonymous
    August 03, 2010
    The comment has been removed