Entity Framework Code First Helpers–Efmaps

I am always astonished with the way Microsoft and some people communicate around some good products they produce…

Code first, is for me the first evolution of Entity Framework that is really usable and nice to use. There is still some lacks and pain points, but globally it’s far better than before. You can now have clean code and simple configuration. Don’t look too much at non-standard scenarios, but on the other side you can do a majority of things simply. This combined with a really cool linq engine, you have a great and efficient tool that fits most usages.

But finally, there isn’t not so much buzz around all of this, isn’t it? Most people blog about new features during the ctps and not so much after. There is good posts about how to handle relations or start working with it.

Mainly two things bothered me:

  • the lack of good explanations on how to resolve current limitations (can’t define default values for the sql side for exemple)
  • the mappings are ALWAYS expressed inside the ‘OnModelCreating’ of the user DbContext. Do you really like 100+ lines tricky configuration and untestable methods?

That was the starting point of this project. This is an humble project to only help some tricky usage issues of the current version of Entity framework code first, it doesn’t change in any way how it works.

 

This is a small vision based on my experience, you should read the following introduction as a sample usage and how should be the way we use EF code first ;).

You will find the sources of the project on my github here:

https://github.com/rhwy/efmap

Nuget pack with this library will be available soon.

 

Let’s present the project

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)
  • more to come …

it helps you keeping focused with your data

Sample usage

 

Build your domain

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;
    }
}

public class Vote
{
    public int VoteUp { get; set; }
    public int VoteDown { get; set; }
}
 

Define your persistance

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);
    }
} 
 

Add some default data

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);
                });
        }
    }
  •  
 
 
 
 
 

Finish defining your context

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);
        }
    } 

create your app

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);
        }

 

Foreword

This is not supposed to be revolutionary, but these should help you working better with your EF code first projects by removing some plumbing practices and kept more focused on your business. This is aim to evolve with time, so share your thoughts and contribute with your additions.

Hope it helps ;-)

Posted in Projects Tagged with: , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>