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":
2) Add a new Project to the "CodeOnlyWalkThru" solution:
3) Choose 'Class Library' and call the library "Blogging":
Our Blogging library will consist of the following classes
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:
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.
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 removedAnonymous
July 14, 2009
The comment has been removedAnonymous
August 06, 2009
@cowgaR:
- Not in scope for this project.
- Personal style choice, not enforced.
- Coming in next CTP.
- 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.
- 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# ? ThanksAnonymous
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:
- is it possible to map a property of an entity as complex type?
- 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, CoyoteAnonymous
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]comAnonymous
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. MagnusAnonymous
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