The latest EF4 CTP released on November 12th includes updated support for POCO’s (Plain Ol’ CLR Objects). Although we could use POCO’s in the previous CTP, we still had to create an EDMX artifact to model our entities, thus leading to a bit of duplication.
In this post I want to take a look a simple mapping scenario, and how that looks using the new ‘Code Only’ API. By using the code only API I can have complete control over my entities, giving me the most flexibility in my mapping strategies, better testability and extensibility, and ultimately greater maintainability. No generated code. No XML files. Sweet 😉
Since I am a sports fanatic I am going to use one of my favorite models, the sports team. (Hey, I could be using Orders!)
The Database Table
I will start by defining a simple table with 2 columns, Name and ID:
CREATE TABLE [dbo].[Teams](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Team] PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
The Entity
When creating an Entity the default convention is that the Entity property names should match the database column names exactly. I will create a Team entity which does that like so:
public class Team
{
public long Id { get; set; }
public string Name { get; set; }
}
The Mapping
Mapping the Entity to the table is very easy. To do so, implement a Generic class EntityConfiguration<T> and setup the map in the ctor. For the Team entity, that will look like this:
public class TeamConfiguration : EntityConfiguration<Team>
{
public TeamConfiguration()
{
Property(c => c.Id).IsIdentity();
Property(c => c.Name).HasMaxLength(50).IsRequired();
}
}
The EntityConfiguration methods contain many overloads which provide a variety of mapping options. For now, I will leave it simple.
The Infrastructure
Now that I have the entity and table mapped together, I need to provide the EF infrastructure with the configuration, and then I can begin working with the Entity(ies).
Connection
A simple connection string. I felt this was worth pointing out for those that have used EF3.5. Note that this is just a simple DB connection, and does not contain the metadata parts.
<configuration>
<connectionStrings>
<add name="Test" connectionString="Data Source=.;Initial Catalog=Test;
Integrated Security=True;MultipleActiveResultSets=True"/>
</connectionStrings>
</configuration>
The Object Context
If you look at the CTP walkthrough you will notice the sample includes a class called BloggingModel which derives from ObjectContext. BloggingModel has properties for each ObjectSet<T>. I believe this is done so that the ContextBuilder can infer the EntitySets and build the correct metadata. To me this is an unnecessary class that I would rather not maintain. Instead, before I create the context, I will handle the metadata registration by calling “builder.RegisterSet<T>("SetName");” (For now… see future post). If I did not do this, the context would use the default naming convention for our EntitySet, and would expect a table named “TeamSet”.
//create builder
var builder = new ContextBuilder<ObjectContext>();
//add our Team set configuration
builder.Configurations.Add(new TeamConfiguration());
//keep in mind this call is to avoid custom object context
builder.RegisterSet<Team>("Teams");
//setup connection
var cnxString = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
var connection = new SqlConnection(cnxString);
//create the context
var context = builder.Create(connection);
//now we have a good context, and can go to work
var teams = context.CreateObjectSet<Team>();
teams.AddObject(new Team { Name = "New Orleans Saints" });
context.SaveChanges();
context.Dispose();
Aside from some of the context setup code, it’s fairly straight forward. Simple data access with a POCO entity. check.
Extending the Entity with a One-to-Many Relationship
Now that I have the basic mapping down, I want to go one step further and add a one-to-many relationship. What’s a team without players?
Player Table
CREATE TABLE [dbo].[Players](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[Team_ID] [bigint] NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Position] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Players] PRIMARY KEY CLUSTERED ([ID] ASC)
)
GO
ALTER TABLE [dbo].[Players] WITH CHECK ADD CONSTRAINT [FK_Players_Teams] FOREIGN KEY([Team_ID])
REFERENCES [dbo].[Teams] ([ID])
GO
ALTER TABLE [dbo].[Players] CHECK CONSTRAINT [FK_Players_Teams]
GO
Entities and Mappings
I have added an ICollection to Team for the Players, and each Player has a 1-1 Team property. Mapping the collection is straight forward by using the “Relationship()” method which takes my expression for the properties I am mapping too.
public class Team
{
public Team()
{
Players = new Collection<Player>();
}
public long ID { get; set; }
public string Name { get; set; }
public ICollection<Player> Players { get; set; }
}
public class Player
{
public long ID { get; set; }
public string Name { get; set; }
public string Position { get; set; }
public Team Team { get; set; }
}
public class TeamConfiguration : EntityConfiguration<Team>
{
public TeamConfiguration()
{
Property(c => c.ID).IsIdentity();
Property(c => c.Name).HasMaxLength(50).IsRequired();
// 1 to * relationships
Relationship(c => c.Players).IsOptional();
//set up inverse
Relationship(c => c.Players).FromProperty(x => x.Team);
}
}
public class PlayerConfiguration : EntityConfiguration<Player>
{
public PlayerConfiguration()
{
Property(c => c.ID).IsIdentity();
Property(c => c.Name).HasMaxLength(50).IsRequired();
Property(c => c.Position).HasMaxLength(50).IsRequired();
Relationship(c => c.Team).IsRequired();
Relationship(c => c.Team).FromProperty(x => x.Players);
}
}
var teams = context.CreateObjectSet<Team>();
var team = new Team { Name = "Indianapolis Colts" };
var player1 = new Player { Name = "Peyton Manning", Position = "QB"};
var player2 = new Player { Name = "Reggie Wayne", Position = "WR" };
team.Players.Add(player1);
team.Players.Add(player2);
teams.AddObject(team);
context.SaveChanges();
//create new player set to show we are pulling back from db
var players = context.CreateObjectSet<Player>().Include("Team")
.Where(x => x.Team.Name == "Indianapolis Colts")
.ToList();
players.ForEach(x => Console.WriteLine(x .Name + " - " + x.Team.Name));
context.Dispose();
And that’s it.
While I was working on this sample I bumped into quite a few common scenarios which are not yet supported. Seeing how this is an early CTP, I suppose that is to be expected. That said, I am excited to see the direction it is taking and look forward to learning more as I have time to work with it.
For more information take a look at the related EF posts on the ADO.NET team blog.
Looks promising. I’m liking the fluent configuration. It would be good to see model-first though – I understand this is possible with EF4, although unfortunately from the looks of things only via the designer.
If I wasn’t looking very closely, I could mistake this for Fluent NHibernate. Finally! Exciting stuff.
@Grant Though I have not tried to work with them other that running the walk-through, there are some extension methods which help facilitate object first development with code only.
public static void CreateDatabase(this ObjectContext context);
public static void DeleteDatabase(this ObjectContext context);
public static bool DatabaseExists(this ObjectContext context);
public static string CreateDatabaseScript(this ObjectContext context);
Not quite nHibernate w/ Tarantino, but I think there is potential.
Good Post. POCOS work well for updating and inserting it seems. For doing read only type of transactions for going to a view, I would go through a projection to a DTO(specific for the view) to improve performance. At all the companies that I have worked at with an EDM/ORM model (NHibernate/Entity Framework), there have been problems with performance by hydrating the objects, attaching to the context, and then translating them across multiple layers. There is no reason to bring back those entities and attach them to the context just to get a player name and the team name that he/she is on.
Agreed Tony, looks similar to fluent NH, which is great!
@Mike Projections are definitely outside the scope of this post, but I hear you. A projection into a ViewModel or DTO is good stuff, maybe like so:
context.CreateObjectSet()
.Where(x => x.Team.Name == “Indianapolis Colts”)
.Select(x => {new PlayerViewModel(x.Name, x.Team.Name);})
Or using a mapper:()
context.CreateObjectSet
.Where(x => x.Team.Name == “Indianapolis Colts”)
.Select(Mapper.Map)
Nice introduction to the new POCO feature of EF4.
Could you name some of the ‘quite a few common scenarios which are not yet supported’?
@isamux – A few issues I encountered:
– I wanted to specify the name (or convention) of FK Id’s. , e.g TeamId. I had to change the name in the Database to “Team_ID”. In FNH you could just specify the Key name when mapping the relationship.
– I wanted to have a private collection for the team Players, exposing only a getter for the collection, but I was only able to map using an AutoMatic property, therefore losing encapsulation on the collection.
Perhaps these are supported, but I could figure it out.
I knew that the new VS2010 and EF4 come with a template to generate POCO objects. so what is the advantage that code-only option can offer me?
Great intro.
I agree with Tony that it looks very Fluent NHibernate-esque. Given my experience there I can’t help but wonder if there are naming conventions that can be defined at a global level that would avoid an override for every aggregate root.
T
Very interesting post.
I have a question:
can I map an entity property using a different column names with EntityConfiguration?
and if I have a complex type in the entity (like Team.Address wich has different properties), how can I map it?
@mcsean – it gives you total control over your entities: Persistence ingnorance, greater testability, finer control over mapping strategies, no code generation (so it works much better with source control), and no GUI desginer so it works better with large models (There is nothing like rebuilding a model with 50-100 entities because the designer failed).
@jason not sure I compeletely follow? Do you mean a convention so it automatically maps the properties to the columns if they are the same name? I remember seeing something that did that.
@emanuele Yes, it handles both of those scenarios. Check out this post here: http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx
thanks Jarod! Does the code-only work with WCF-RIA-Services? I once read a post from a forum and someone said it doesn’t and the work around is to use presentation model. Can you please clarify that?
@mcsean
I have been meaning to give this a try and get back with you, but I have not been able to make the time yet.
I think RIA services is supposed to support POCOs ala NHibernate, so I imagine if you followed that track instead of the straight EF approach it could work. (If it doesn’t already)
There are supposedly new RIA services drops coming soon for the 2010RC, so look for improvements there.