Friday, November 19, 2010

Code First Development in Entity Framework 4

The Entity Framework CTP allows code first development approach along with the core features. Using code first approach you can now create your model classes as POCO objects and the EF4 will generate the DB for you. In this post I’ll show a walkthrough of this feature and build a Customer Order sample application using the code first feature of the CTP.
You can download the new CTP from here.
·         Create a new class library project in VS 2010 and add reference to the assemblies. This class library serves as our model project
Ø  System.Data.Entity
Ø  Microsoft.Data.Entity.CTP
Ø  System.ComponentModel.DataAnnotations
·         Add 3 new classes for entities User, Order and Product as given in the sample below
public class User
{
    public Guid Id { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? ChangedDate { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }

    public ICollection<Order> Orders { get; set; }

    [Timestamp]
    public Byte[] Version { get; set; }
}

public class Product
{
    public Guid Id { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? ChangedDate { get; set; }
    public string Name { get; set; }
    public decimal Amount { get; set; }

    public ICollection<Order> Orders { get; set; }

    [Timestamp]
    public Byte[] Version { get; set; }
}

public class Order
{
    public Guid Id { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? ChangedDate { get; set; }
    public string OrderNumber { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }

    public User User { get; set; }

    public ICollection<Product> Products { get; set; }

    [Timestamp]
    public Byte[] Version { get; set; }
}

·         As you can see every entity has a Version property which is used for concurrency management in our application.
·         Once the entities are created we can now create our context class.
·         Add a new class and name it OrderContext
public class OrderDbContext : DbContext
{

    public OrderDbContext() : base("OrderDb") { }

    protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {
        SetPrimaryKeysOnEntities(modelBuilder);
        SetConcurrencyFieldsOnEntities(modelBuilder);

        SetValidationsOnUser(modelBuilder);           
    }

    private void SetPrimaryKeysOnEntities(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {

        modelBuilder.Entity<User>().HasKey(u => u.Id);
        modelBuilder.Entity<Order>().HasKey(o => o.Id);
        modelBuilder.Entity<Product>().HasKey(p => p.Id);
    }

    private static void SetConcurrencyFieldsOnEntities(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {

        modelBuilder.Entity<User>().Property(u => u.Version).IsConcurrencyToken();
        modelBuilder.Entity<Order>().Property(u => u.Version).IsConcurrencyToken();
        modelBuilder.Entity<Product>().Property(u => u.Version).IsConcurrencyToken();
    }

    private void SetValidationsOnUser(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {

        modelBuilder.Entity<User>().Property(u => u.FirstName).IsRequired().HasMaxLength(100);
        modelBuilder.Entity<User>().Property(u => u.LastName).IsRequired().HasMaxLength(100);
        modelBuilder.Entity<User>().Property(u => u.UserName).IsRequired().HasMaxLength(200);
        modelBuilder.Entity<User>().Property(u => u.Password).IsRequired();
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<Product> Products { get; set; }
}

·         The default constructor of our class calls the overloaded constructor of the DbContext class by passing the DB name to be created. When the code is executed EF creates a new DB named OrderDB in the local SQLExpress instance.
·         I have overridden the OnModelCreating method for adding property validations and constraints for our entities. There are different ways to do this. You can use attributes in your entities or create a separate configuration class for entities as an alternative approach. We’ll see the different approaches in another post.
·         The HasKey method specifies the primary key property for an entity. For our entities it’s the Id property.
·         IsConcurrencyToken method identifies the column specified as concurrency column.
·         We’ll test this model using a test project. For that add a new unit test project and add a project reference to our model class library.
·         I have created a test case that creates a user, products and adds an order with products for the user.
[TestClass]
public class OrderDbContextFixture
{
    public TestContext TestContext { get; set; }

    [ClassInitialize]
    public static void Initialize(TestContext context)
    {           
        Database.SetInitializer<OrderDbContext>(new RecreateDatabaseIfModelChanges<OrderDbContext>());
    }

    [TestMethod]
    public void Using_orderdbContext_should_be_able_to_retrieve_orders_for_user()
    {
        using (var context = new OrderDbContext())
        {
            var user = GetUserToInsert();
            var product = GetProductToInsert();
            var order = CreateOrderForUser(user);
            List<Product> products = GetListOfProductsForOrder(product);
            AddProductsToOrder(order, products);

            context.Users.Add(user);
            context.Products.Add(product);
            context.Orders.Add(order);

            context.SaveChanges();

            var orderFromDb = context.Orders.First<Order>(o => o.Id == order.Id);
            Assert.IsTrue(orderFromDb.User.Id == user.Id);
        }
    }

    private User GetUserToInsert()
    {
        return new User
        {
            Id = Guid.NewGuid(),
            FirstName = "Prajeesh",
            LastName = "Prathap",
            UserName = "prajeesh.prathap",
            Password = "password"
        };
    }

    private Product GetProductToInsert()
    {
        return new Product
        {
            Id = Guid.NewGuid(),               
            Name = "Microsoft Visual Studio 2010 Ultimate",
            Amount = 20775m
        };
    }

    public Order CreateOrderForUser(User user)
    {
        return new Order
        {
            Id = Guid.NewGuid(),
            User = user,
            OrderNumber = "1",
            OrderDate = DateTime.Now,              
        };
    }

    public void AddProductsToOrder(Order order, IEnumerable<Product> products)
    {
        foreach(var product in products)
        {
            if (order.Products == null) order.Products = new List<Product>();
            order.Products.Add(product);
            order.TotalAmount += product.Amount;
        }
    }

    private List<Product> GetListOfProductsForOrder(Product product)
    {
        List<Product> products = new List<Product>();
        products.Add(product);
        return products;
    }
}

·         Once the test case is execute you can open you SQLExpress instance and see the new OrderDb created with the tables and having values for the user, order and product inserted in it.