EF map provides easy additional helpers to work with Entity Framework Code First and make it more valuable. It allows you to be focused on configuration and mapping in a clean way.
Functionalities:
- Helps defining clean mappings with a single class mapping per entity and auto load them
- Helps defining database initialization with additional sql commands
- Helps defining database initialization through default model data
- Helps defining an identity column
- Add validation helpers (for data anotations)
- up to EF 6.*
it helps you keeping focused with your data
Let's define a simple idea suggestion system, this how our domain looks like:
public class Idea
{
public int Id { get; set; }
public string Title { get; set; }
public string Detail { get; set; }
public DateTime Published { get; set; }
public string Author { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual Vote Votes { get; set; }
public Idea()
{
Comments = new HashSet<Comment>();
Votes = new Vote();
Published = DateTime.Now;
}
}
public class Comment
{
public int Id { get; set; }
public string Content { get; set; }
public DateTime Published { get; set; }
public string Author { get; set; }
public Comment()
{
Published = DateTime.Now;
}
}
[ComplexType]
public class Vote
{
public int VoteUp { get; set; }
public int VoteDown { get; set; }
}
You have defined your domain in clean way until here, let's define now from your infrastructure perspective how you persist it with Entity Framework code first:
public class IdeaMap : EntityTypeConfiguration<Idea>
{
public IdeaMap()
: base()
{
this.WithRequiredGeneratedIdentity(p => p.Id);
Property(p => p.Title)
.IsRequired()
.HasMaxLength(100);
Property(p=>p.Votes.VoteUp)
.HasColumnType("int")
.HasColumnName("VoteUp");
Property(p => p.Votes.VoteDown)
.HasColumnType("int")
.HasColumnName("VoteDown");
ToTable("Ideas");
}
}
public class CommentMap : EntityTypeConfiguration<Comment>
{
public CommentMap() : base()
{
this.WithRequiredGeneratedIdentity(p => p.Id);
}
}
There is usually two ways to complete the database initialisation process started by entity framework code first engine. These options are complementary:
-
You can add some sql in order to add some specific commands that can not currently be generated automaticaly by Entity Framework engine during the initialization process : we call these DbInitializers. For exemple, let's define that we want to ensure at database level that the title of our ideas is unique to avoid exact duplicates
public class IdeaDbInitializer : DbInitializer { public IdeaDbInitializer() { //add constraint to avoid exact duplicate ideas this.AddCommand( "IF NOT ISNULL(OBJECT_ID('Ideas','U'),0)=0 ALTER TABLE Ideas ADD CONSTRAINT UN_Ideass_Title UNIQUE(Title)" ); } }
-
You can add some entity objects during the database initialisation to be used as default or starting values by your application just like any other data : we call these EntityInitializers For exemple, let's add a default idea with a comment to show users after the installation of our system an usage exemple:
public class IdeaEntityInitializer : EntityInitializer<IdeaContext> { public IdeaEntityInitializer() { this.AddCommand( "add presentation idea", (context) => { Idea idea = new Idea(); idea.Title = "what about creating an ideas site?"; idea.Author = "admin"; idea.Comments.Add(new Comment { Author = "admin", Content = "vote and add your comments about the idea" }); idea.Votes.VoteUp++; context.Ideas.Add(idea); }); } }
You just have to define now your context with your DbSets. The interesting option now is the auto load. Define your sets available to your context, then, when defining autoload, it will search for each set of your context for a mapping. That's a cleaner way than handling everything in the "onModelcreating" method don't you think?
public class IdeaContext : DbContext
{
public DbSet<Comment> Comments { get; set; }
public DbSet<Idea> Ideas { get; set; }
public IdeaContext()
: base("IdeasDb")
{}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
this.AutoLoadForThisContext(modelBuilder);
base.OnModelCreating(modelBuilder);
}
}
You have now all your need, here is now how you link and use this in your application layer. Let's configure it, initialize it, and get print default values in a sample console app:
static void Main(string[] args)
{
//define the data class to use to initialize database through entity
EntityMapHelper.SetEntityInitializer<IdeaContext>(new IdeaEntityInitializer());
//define the data class to use to add custom sql commands at
EntityMapHelper.SetDbInitializer<IdeaContext>(new IdeaDbInitializer());
//define the pattern structure to initialize database
EntityMapHelper.Initialize<IdeaContext>().With<DropCreateAlways<IdeaContext>>();
//that's all, you can now use it and directly get the default data as in the exemple
using (IdeaContext context = new IdeaContext())
{
Console.WriteLine("default ideas : ");
context.Ideas.ToList().ForEach(
idea => Console.WriteLine("- {0} ({1} comments)", idea.Title, idea.Comments.Count)
);
Console.WriteLine("Press enter so print sql initialization script");
Console.ReadLine();
Console.Clear();
string sql = context.GetSqlCreationScript();
Console.WriteLine(sql);
Console.WriteLine("Press enter so print generated Edmx script");
Console.ReadLine();
Console.Clear();
string edmx = context.GetGeneratedEdmx();
Console.WriteLine(edmx);
}
Project Updated for EF 5.0 Due to demand, and because there is no much alternative for this job, I still keep this project updated. Please, fill a github issue if you have more needs, fork and so on ;-) cheers!