Click here to Skip to main content
Click here to Skip to main content
Go to top

Entity Framework Code First Migrations with Seed and IdeaBlade DevForce

, 6 Dec 2012
Rate this:
Please Sign up or sign in to vote.
Entity Framework Code First migrations with Seed and IdeaBlade DevForce.

Introduction

I recently began a new project migrating an older application written in delphi using IntraWeb to Silverlight 5.  The application has a large database model behind it consisting of a large number of tables, views, functions and stored procedures. 

After reviewing the options available.  It was decided to use Entity Framework and Code First with migrations.  After getting this setup and working, DevForce was added to the formula.  Below I will detail how I got all the pieces working well together as I quickly found out that you had to do a little bit more work, to keep EF happy even though DevForce is going to re-pipe the data access during runtime in order to use EF Migrations.

Summary

OK, here we go.  There are quite a few things that we need to do  to get this working.

  1. Create our EntityBase class
  2. Create our Code First POCO Classes
  3. Create a DbContext so we can use the Fluent API
  4. Create an EntityManager
  5. Generate a Migration (and apply)
  6. Create Seed implementation

Yes, I do know that some of these steps are not explicitly required.  I include them as part of good practice and to align more closely with what you would have in a production project.

Getting Started

Ok, so lets start with a blank Silverlight Project.  I will call it DevForceCodeFirstSeeder for now.

New Project Window

Right away I am going to make a solution folder called DevForceCodeFirstDb and add a RIA Services Class Library to it.  I always keep the database abstracted out into its own class library rather than gunk up our main project.  This brings us to this point with our project.

InitialProjectSetup

Now all we need to do before we can dive into the code, is add a few NuGet Packages.  Specifically we will add IdeaBlade.DevForce.Core and IdeaBlade.DevForce.Aop to the DevForceCodeFirstDb.Web project.

Note: You can just add the IdeaBlade.DevForce.Aop package since it automatically downloads and adds the IdeaBlade.DevForce.Core package.

Model Time

So now we have our solution started, lets get down to some code. First, lets add our EntityBase and related classes to the DevFroceCodeFirstDb.Web project.  I generally break this out into four classes.

  1. EntityFacts
  2. This is a class that exposes the commonly used properties of the EntityAspect since I do not expose the full EntityAspect as a rule.

  3. EntityActions
  4. This class is much like EntityFacts, except that it exposes methods that can be performed on the entity using the EntityAspect.

  5. EntityBase
  6. This is my base class for all my DevForce Entities.  It exposes my EntityFacts and EntityActions as well as has a few common virtual methods and a PropertySet Interceptor to trip all strings.

    (I just hate excess whitespace).  This also has a RowVersion property that is created in the database as a timestamp column to enforce concurrency on all entities.

  7. AuditEntityBase
  8. This class derives from EntityBase and adds common fields for audit logging of an entity, such as create and modify audit information.

Here is my code:

EntityFacts

using System.ComponentModel;
using IdeaBlade.EntityModel;

namespace DevForceCodeFirstDb.Web.Entities
{
    /// <summary>
    /// Helper class to encapsulate several common properties of the EntityAspect
    /// </summary>
    public class EntityFacts : INotifyPropertyChanged
    {
        private readonly EntityAspect _entityAspect;

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityFacts" /> class.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public EntityFacts(object entity)
        {
            _entityAspect = EntityAspect.Wrap(entity);
            _entityAspect.PropertyChanged +=
                (s, args) => RaiseEntityFactsPropertyChanged(string.Empty);
            _entityAspect.EntityPropertyChanged += EntityAspectOnEntityPropertyChanged;
        }

        private void EntityAspectOnEntityPropertyChanged(object sender, 
                PropertyChangedEventArgs propertyChangedEventArgs)
        {
            RaisePropertyChanged("HasChanges");
        }

        /// <summary>
        /// Gets a value indicating whether this instance has changes.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has changes; otherwise, <c>false</c>.
        /// </value>
        public bool HasChanges
        {
            get { return _entityAspect.HasChanges(); }
        }

        /// <summary>
        /// Gets the state of the entity.
        /// </summary>
        /// <value>
        /// The state of the entity.
        /// </value>
        public EntityState EntityState
        {
            get { return _entityAspect.EntityState; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is null entity.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is null entity; otherwise, <c>false</c>.
        /// </value>
        public bool IsNullEntity
        {
            get { return _entityAspect.IsNullEntity; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is a pending entity.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is a pending entity; otherwise, <c>false</c>.
        /// </value>
        public bool IsPendingEntity
        {
            get { return _entityAspect.IsPendingEntity; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is a null or pending entity.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is a null or pending entity; otherwise, <c>false</c>.
        /// </value>
        public bool IsNullOrPendingEntity
        {
            get { return _entityAspect.IsNullOrPendingEntity; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has errors.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has errors; otherwise, <c>false</c>.
        /// </value>
        public bool HasErrors
        {
            get { return _entityAspect.ValidationErrors.HasErrors; }
        }

        /// <summary>
        /// Gets the validation errors.
        /// </summary>
        /// <value>
        /// The validation errors.
        /// </value>
        public EntityAspect.VerifierErrorsCollection ValidationErrors
        {
            get { return _entityAspect.ValidationErrors; }
        }

        /// <summary>
        /// Gets the entity aspect.
        /// </summary>
        /// <value>
        /// The entity aspect.
        /// </value>
        protected internal EntityAspect EntityAspect
        {
            get { return _entityAspect; }
        }

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        public void RaisePropertyChanged(string propertyName)
        {
            _entityAspect.ForcePropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Occurs when [entity property changed].
        /// </summary>
        public event PropertyChangedEventHandler EntityPropertyChanged
        {
            add { _entityAspect.EntityPropertyChanged += value; }
            remove { _entityAspect.EntityPropertyChanged -= value; }
        }

        /// <summary>
        /// Occurs when [property changed].
        /// </summary>
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add { EntityFactsPropertyChanged += value; }
            remove { EntityFactsPropertyChanged -= value; }
        }
        /// <summary>
        /// Occurs when [entity facts property changed].
        /// </summary>
        protected event PropertyChangedEventHandler EntityFactsPropertyChanged;

        /// <summary>
        /// Raises the entity facts property changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected void RaiseEntityFactsPropertyChanged(string propertyName)
        {
            if (null == EntityFactsPropertyChanged) return;
            EntityFactsPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

EntityActions

using IdeaBlade.EntityModel;

namespace DevForceCodeFirstDb.Web.Entities
{
    /// <summary>
    /// Helper class to encapsulate actions that can be performed
    /// on a DevForce Entity through its EntityAspect
    /// </summary>
    public class EntityActions
    {
        private readonly EntityAspect _entityAspect;

        /// <summary>
        /// Initializes a new instance of the <see cref="EntityActions" /> class.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public EntityActions(object entity)
        {
            _entityAspect = EntityAspect.Wrap(entity);
        }

        /// <summary>
        /// Rejects the changes.
        /// </summary>
        public void RejectChanges()
        {
            _entityAspect.RejectChanges();
        }

        /// <summary>
        /// Deletes this instance.
        /// </summary>
        public void Delete()
        {
            _entityAspect.Delete();
        }
    }
}

EntityBase

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.Serialization;
using IdeaBlade.Aop;
using IdeaBlade.Core;
using IdeaBlade.EntityModel;
using IdeaBlade.Validation;

namespace DevForceCodeFirstDb.Web.Entities
{
    /// <summary>
    /// Base class for Code First DevForce Entities
    /// </summary>
    [ProvideEntityAspect]
    [DataContract(IsReference = true)]
    public abstract class EntityBase
    {
        private EntityFacts _entityFacts;
        private EntityActions _entityActions;

        /// <summary>
        /// Get facts about this entity's current state.
        /// </summary>
        [NotMapped]
        [Bindable(false), Editable(false), Display(AutoGenerateField = false)]
        public EntityFacts EntityFacts
        {
            get { return _entityFacts ?? (_entityFacts = new EntityFacts(this)); }
        }

        [NotMapped]
        [Bindable(false), Editable(false), Display(AutoGenerateField = false)]
        public EntityActions EntityActions
        {
            get { return _entityActions ?? (_entityActions = new EntityActions(this)); }
        }

        /// <summary>
        /// Gets the row version.
        /// </summary>
        /// <value>
        /// The row version.
        /// </value>
        [DataMember]
        [Timestamp]
        [ConcurrencyCheck]
        [ConcurrencyStrategy(ConcurrencyStrategy.None)]
        [Display(AutoGenerateField = false)]
        public Byte[] RowVersion { get; internal set; }

        /// <summary>
        /// Perform custom validation of this entity and
        /// add errors to the <see cref="validationErrors"/>
        /// </summary>
        public virtual void Validate(VerifierResultCollection validationErrors) { }

        public virtual dynamic Copy()
        {
            throw new NotImplementedException(
              "The Copy method is not implemented on type " + GetType());
        }

        public virtual dynamic CopyAttached()
        {
            EntityBase copiedEntity = this.Copy();
            EntityManager entityManager = EntityAspect.Wrap(this).EntityManager;
            if (entityManager == null)
                throw new NullReferenceException("EntityAspect.EntityManager cannot be null.");
            entityManager.AddEntity(copiedEntity);
            return copiedEntity;
        }

        /// <summary>
        /// Removes the white space from any string entity property on set.
        /// </summary>
        /// <param name="args">The args.</param>
        [BeforeSet]
        internal void TrimStrings(IEntityPropertySetInterceptorArgs args)
        {
            if (args.EntityProperty.DataType != typeof(string) || args.Value == null)
                return;

            args.Value = ((string)args.Value).Trim();
        }
    }
}

AuditEntityBase

using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using IdeaBlade.EntityModel;

namespace DevForceCodeFirstDb.Web.Entities
{
    [DataContract(IsReference = true)]
    [ClientCanSave(true)]
    public abstract class AuditEntityBase : EntityBase
    {
        /// <summary>
        /// Gets or sets the created date/time. 
        /// </summary>
        /// <value>
        /// The created date/time.
        /// </value>
        [DataMember]
        [Editable(false)]
        public DateTime Created { get; set; }

        /// <summary>
        /// Gets or sets the create user.
        /// </summary>
        /// <value>
        /// The create user.
        /// </value>
        [DataMember]
        [StringLength(30)]
        [Editable(false)]
        public string CreatedUser { get; set; }

        /// <summary>
        /// Gets or sets the modified date/time.
        /// </summary>
        /// <value>
        /// The modified date/time.
        /// </value>
        [DataMember]
        [Editable(false)]
        public DateTime Modified { get; set; }

        /// <summary>
        /// Gets or sets the modify user.
        /// </summary>
        /// <value>
        /// The modify user.
        /// </value>
        [DataMember]
        [StringLength(30)]
        [Editable(false)]
        public string ModifyUser { get; set; }
    }
}

POCO Entities

Now let's create a few actual entities.  For this example I will create User, Customer and Person entities. For now, lets just follow the guidance we find on the Entity Framework site and create our models.

Person

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace DevForceCodeFirstDb.Web.Entities.General
{
    public class Person : AuditEntityBase
    {
        [Key]
        [Editable(false)]
        [Display(AutoGenerateField = false)]
        public int PersonKey { get; set; }

        [Required]
        [StringLength(50)]
        public string FirstName { get; set; }

        [Required]
        [StringLength(50)]
        public string LastName { get; set; }

        [StringLength(1)]
        public string MiddleInitial { get; set; }

        [NotMapped]
        public string FullName
        {
            get { return (FirstName + " " + LastName).Trim(); }
        }
    }
}

Notice that I left the FullName string as NotMapped instead of DatabaseGenerated.  Since I am using migrations to control the database, I will need to customize the migration to add this column the write way anyways.  This way, even before you save the Entity, FullName will update as FirstName and LastName change.

User

using System.ComponentModel.DataAnnotations;

namespace DevForceCodeFirstDb.Web.Entities.General
{
    public class User: AuditEntityBase
    {
        [Key]
        [Editable(false)]
        [Display(AutoGenerateField = false)]
        public int UserKey { get; set; }

        [StringLength(30)]
        public string UserId { get; set; }

        public int PersonKey { get; set; }
        public virtual Person Person { get; set; }
    }
}

User is a very simple class.  I would add properties here to track my own information about a user in addition to what ASP.NET Membership does. Or you could add properties and control your own logins using this entity.  Also not that the Person property is virtual, just as Entity Framework tells us to.  (We will come back to this later.)

Customer

using System.ComponentModel.DataAnnotations;

namespace DevForceCodeFirstDb.Web.Entities.General
{
    public class Customer: AuditEntityBase
    {
        [Key]
        [Editable(false)]
        [Display(AutoGenerateField = false)]
        public int CustomerKey { get; set; }

        [StringLength(12)]
        public string CustomerId { get; set; }

        public virtual Person Contact { get; set; }
    }
}

Another very simple class with the virtual link back to Person.

Database Time (well, almost)

Now we need to link this up to a database via EF Code first Migrations. To do that, we need to define a DbContext.  This is fairly straightforward, right?  We just need a few DbSet properties and we are off the the races, right? Like this?

using System.Data.Entity;
using DevForceCodeFirstDb.Web.Entities.General;
using IdeaBlade.EntityModel;

namespace DevForceCodeFirstDb.Web.Context
{
    public class DevForceCodeFirstContext : DbContext
    {
        public DbSet<Person> People { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<Customer> Customers { get; set; }
    }
}

Not quite.  You can see this by trying to build your DevForceCodeFirstDb.Web project. You will get this error message.

BaseContextBuiltError

We need to tell the context to ignore the EntityAspect type.  We do this by overriding the OnModelCreating method and adding ‘modelBuilder.Ignore<EntityAspect>();’.  Now we can build, and our entity model is created by DevForce (our .ibmmx file appears).

For completeness I will create my own EntityManager as well even though it is not strictly required, but makes the used much simpler and easier to read.

Now its time to create our migration and get this thing in the database already?  Well, lets have a go at it.  I will be setting up my own connection string since I run a full copy of SQL on my development machine.  You can let your default if you use localDb.  For reference, I added the following to my app.config and web.config files.

<connectionStrings>
    <clear/>
    <add name="DevForceCodeFirstContext" 
       connectionString="Server=localhost;Database=DevForceCodeFirstDb;Integrated 
         Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>

So now I can run ‘Enable-Migrations’ via the Package Manager Console.

Migrations

So now that we have migrations enabled, lets add a migration called initial by running ‘Add-Migration Initial’ in the Package Manager Console. This will add the first migration to the project.

Once this completes, lets run the migration against the database by running the Update-Database command in the package manager console.  This works as expected, but I should note that EF will set cascade delete on all relationships by default and will cause an error on Update-Database with a circular reference in your entity model.  This can be resolved by removing that convention from the DbContext.

Seed

So now we have our database structure.  Lets add some data.  We can use the seed method as shown here.

using DevForceCodeFirstDb.Web.Entities.General;

namespace DevForceCodeFirstDb.Web.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : 
       DbMigrationsConfiguration<DevForceCodeFirstDb.Web.Context.DevForceCodeFirstContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(DevForceCodeFirstDb.Web.Context.DevForceCodeFirstContext context)
        {
            context.People.AddOrUpdate(
                p => p.FirstName,
                new Person
                    {
                        FirstName = "John",
                        LastName = "Doe"
                    },
                new Person
                    {
                        FirstName = "Jane",
                        LastName = "Doe"
                    });
            //If i dont save changes, these people will not be available for the other adds
            context.SaveChanges();

            context.Users.AddOrUpdate(
                u => u.UserId,
                new User
                    {
                        UserId = "admin",
                        Person = context.People.FirstOrDefault(p => p.FirstName == "John")
                    });
            context.Customers.AddOrUpdate(
                c => c.CustomerId,
                new Customer
                    {
                        CustomerId = "000000000001",
                        Contact = context.People.FirstOrDefault(p => p.FirstName == "Jane")
                    });
        }
    }
}

Let's run Update-Database one more time to run the seed.  Oops, we get an error.

Unable to get metadata for System.Data.Entity.DynamicProxies.User_594F3E628E53E142335CF3A345FC826C85C3C9FE57383659293F68488FF15C73. Make sure it is a valid entity type or POCO type with a KeyAttribute

Let's go back and remove the virtual tags on the entities and try again.  The virtual declaration affects how PostSharp affects the class and breaks any attempted read from the database during the seed method that returns data.

Note: The error only appears if you actually return data from SQL.  So a single AddOrUpdate will work the first time it is run, however when you update your database and run the seed again.  Boom!

And success.  Now you can add your application logic and develop with test data automatically added for you any time you wipe the database.

  • Download source code here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

KitKat31337
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 PinmemberMember 955263226-Jan-13 3:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140905.1 | Last Updated 6 Dec 2012
Article Copyright 2012 by KitKat31337
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid