Thursday, November 25, 2010

Model First Development using POCOs in Entity Framework

Entity framework supports the concept of model first development and then generating a database based on the model created. Along with the model created the entity framework generates the classes for the model entities which are heavily dependent on the plumbing of the framework. These auto generated classes know too much information about the persistence mechanism and are tied closely to the concerns of models and mappings. For a developer who wants to create a commonly accepted and easily understandable framework of objects, these classes are not very helpful. In this post I’ll explain how to create a customer – order scenario with model first development and POCO support.
1.       In your model project add a new ADO.NET entity data model and select ‘Empty model’ from the options and click finish. I have named my model ‘ShoppingCartModel.edmx’.
2.       In my model I have created the entities like given in the figure below.

3.       Every entity has a Timestamp field which is used to manage concurrency. To generate Timestamp fields in the DB you can follow the steps mentioned in this post.
4.       Right click the model and select Properties. Set the code generation strategy to ‘None’. This will prevent the auto generation of the classes from the model. 
5.       Right click the model and select ‘Generate Database from model’. Complete the connection string specifications and click Finish. This will generate the .SQL file with the DB scripts and save in the current project. You can use this script to generate the DB and the tables.
6.       Next we’ll create our POCO classes for the entity model. 
public class BaseEntity
{
    public Guid Id { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime ChangedDate { get; set; }
    public byte[] Timestamp { get; set; }
}

public class Customer : BaseEntity
{
    public Customer()
    {
        Orders = new HashSet<Order>();
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }

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

public class Order : BaseEntity
{
    public Order()
    {
        OrderProducts = new HashSet<OrderProduct>();
    }

    public DateTime OrderDate { get; set; }
    public string OrderNumber { get; set; }
    public Guid CustomerId {get; set; }

    public Customer Customer { get; set; }

    public virtual ICollection<OrderProduct> OrderProducts { get; set; }
}

public class Product : BaseEntity
{
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }

    public ICollection<OrderProduct> OrderProducts { get; set; }
}

public class OrderProduct
{
    public Guid OrderId { get; set; }
    public Guid ProductId { get; set; }
    public Order Order { get; set; }
    public Product Product { get; set; }
    public int Quantity { get; set; }
    public decimal TotalPrice { get; set; }
}

7.       Next we need to create the ObjectContext implementation.
public class ShoppingCartContext : ObjectContext
{
    public ShoppingCartContext() : base("name=ShoppingCartModelContainer", "ShoppingCartModelContainer") { }

    public ObjectSet<Customer> Customers
    {
        get { return _customers ?? CreateObjectSet<Customer>(); }
    }

    public ObjectSet<Order> Orders
    {
        get { return _orders ?? CreateObjectSet<Order>(); }
    }

    public ObjectSet<Product> Products
    {
        get { return _products ?? CreateObjectSet<Product>(); }
    }      

    ObjectSet<Customer> _customers;
    ObjectSet<Order> _orders;
    ObjectSet<Product> _products;
}

You can use this context to perform CRUD operations on the model like.
[TestMethod()]
public void lazy_loading_should_be_able_to_retrieve_data_from_the_db()
{
    var customer = SetupCustomerWithOrderAndProduct();

    using (var context = new ShoppingCartContext())
    {
        context.ContextOptions.LazyLoadingEnabled = true;

        var customerFromDb = (from c in context.Customers
                        where c.Id == customer.Id
                        select c).FirstOrDefault();

        var order = (from o in customerFromDb.Orders
                        where o.OrderNumber.Contains("ORD")
                        select o).FirstOrDefault();

        Assert.IsTrue(order.OrderProducts.Count == 1);
    }
}

This approach can be used to create a repository and unit of work pattern efficiently.

4 comments:

Necromantici said...

Nice post!!!

I have a couple of questions, hope You can and have the time to answer.

First, assume We have built the model and do everything You laid out. Now a requirement to add a new column to a table arrives. How could You incorporate those changes to the model and the database?

Second, What about indexes and keys?

Thanks in advance.

Prajeesh Prathap said...

Hi,
There are two approaches for updating the model based on changes in the table schema. First you make changes in the schema and regenerate your SQL scripts to update the DB, but this approach will drop and recreate the db resulting in data loss.
Second approach will be to make changes to the DB schema and then update the model from the DB. Once the model is updated you can update the POCO classes accordingly.

For indexes and keys, the tool is intelligent to generate FK's based on the associations in the model. PK's can be specified when you create the model.

Let me know if you need more info.

Thanks
Prajeesh

Necromantici said...

Hi there!!! Many thanks for answering. Ill try as soon as I can everything You said.

I really hoped that only adding data to my POCO class and making a manual ALTER would sufice. This seems odd to me but until I really try it I could not think anything else.

Many thanks!!!

Wangbu said...

Hello! You have a wonderful blog! I am happy to visit here!