Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Best way to use Entity Framework

0.00/5 (No votes)
14 Jan 2013 1  
Simplest, best and most robust architecture.

Introduction

I have been working in .NET the last 4 years, Since last year I have been using 4.0 framework with Entity Framework and MVC. After reading so many articles and doing a lot of research I made an architecture to use Entity Framework in a robust and easy manner.

Background 

The basic idea behind this architecture is to provide the facility to the developer to use entity framework data context in a robust way and without writing custom Insert, Update and Delete methods for each individual entity table classes.

Start Integration   

Let's start with the integration first we need to create our Entity Model, you can use any approach to create the EF designer class like Code First, Model First or Database First. But in my opinion the database first approach is the best one among all of them because in this approcah developer can use maximum benefits of this particular framework.   

So in my way create the edmx file, connect them with the desired database and generate the table classes by step by step wizard. Here is the detailed description to generate edmx file.   


After generating the above edmx we will create two classes name as Global.cs which will handle the Database Context in a robust way and another one will be Helper.cs which will consist the generic extension methods of Insert, Update and Delete operations.  The Product.cs is a partial class of the product entity and I'll consider this class later in this article.

Global.cs    

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
 
namespace Business
{
    public sealed class Global
    {
        public static NorthwindEntities Context
        {
            get
            {
                string ocKey = "key_" + HttpContext.Current.GetHashCode().ToString("x");
                if (!HttpContext.Current.Items.Contains(ocKey))
                    HttpContext.Current.Items.Add(ocKey, new NorthwindEntities());
                return HttpContext.Current.Items[ocKey] as NorthwindEntities;
            }
        }
    }
}   

The above class holds the Entity Framework Data Context in HttpContext.  This thing will make the DataContext to use in a robust manner,  because the data context will be remain same for each HTTP request. In this working style the nightmare to initialize and dispose the data context on certain code block gets removed. The context will be disposed when HTTP request ends. 

Helper.cs    

using System.Linq;
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data;
 
namespace Business
{
    public static class Helper
    {
        /// <summary>
        /// Save the reocrd to related table, if the primary key passed update it else add it.
        /// </summary>
        /// <param name="objEntity">Entity Object</param>
        public static void Save(this EntityObject objEntity)
        {
            try                                       // Update record.
            {
                Global.Context.DetachObject(objEntity);
                Global.Context.AttachTo(Global.Context.GetEntitySetName(objEntity), objEntity);
                Global.Context.ObjectStateManager.ChangeObjectState(objEntity, EntityState.Modified);
                Global.Context.SaveChanges();
 
            }
            catch (OptimisticConcurrencyException)   // Insert if found the record is already exists.
            {
                Global.Context.DetachObject(objEntity);
                Global.Context.AddObject(Global.Context.GetEntitySetName(objEntity), objEntity);
                Global.Context.SaveChanges();
            }
        }
 
        /// <summary>
        /// Delete the record from related table. The primary key value must be filled.
        /// </summary>
        /// <param name="objEntity">Entity Object</param>
        public static void Delete(this EntityObject objEntity)
        {
            if (objEntity != null)
            {
                Global.Context.DetachObject(objEntity);
                Global.Context.Attach(objEntity);
                Global.Context.ObjectStateManager.ChangeObjectState(objEntity, EntityState.Deleted);
                Global.Context.SaveChanges();
            }
        }
 
        private static string GetEntitySetName(this ObjectContext Context, EntityObject entity)
        {
            string entityTypeName = entity.GetType().Name;
            var container = Context.MetadataWorkspace.GetEntityContainer(Context.DefaultContainerName, DataSpace.CSpace);
            return container.BaseEntitySets.FirstOrDefault(meta => meta.ElementType.Name == entityTypeName).Name;
        }
 
        private static void DetachObject(this ObjectContext Context, EntityObject entity)
        {
            if (entity.EntityKey != null)
            {
                object objentity = null;
                var exist = Context.TryGetObjectByKey(entity.EntityKey, out objentity);
                if (exist) { Context.Detach(entity); }
            }
        }
    }
}

The above class consists extension methods which will take care of Insert, Delete and Update operations against the DB with database context. As you can see, these extension methods are for EntityObject class and this class inherits all the database table classes which is generated by entity framework see image below of Northwind.Designer.cs

 

 

So it means that this database entity classes can use the above extension methods as well because of the wonderful OOPS concept Inheritance Smile  

How to use above architecture

Now most the things are done so lets take a look of an example of using this architecture. As i created a partial class of Product and this particular class has also has a definition in the designer class as well.

Product.cs     

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Business
{
    public partial class Product
    {
        public static List<Product> GetProducts()
        {
            return Global.Context.Products.ToList();
        }
 
        public static Product GetProduct(int ProductId)
        {
            return Global.Context.Products.SingleOrDefault(P => P.ProductID == ProductId);
        }
    }
}

This is just a simple class which has two static methods to retrive the product table data. The thing to be note here that i am using our Global.Context that we creted earlier.

Now this business logic project is completed you can add many more classes into it as per requirement.  You can attach this layer with any kind of your project Web Application or Website etc. In my case i have attached a web application in my demo

 

Default.aspx

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApp._Default" %>
 
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"></asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>Products</h2><br /><hr /><br />
 
    <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0">
        <asp:View ID="View1" runat="server">
            <div style="text-align:right;margin-bottom:5px;"><asp:Button ID="btnAdd" 
                    runat="server" Text="Add Product" onclick="btnAdd_Click" /></div>
            <asp:GridView ID="GV" runat="server" AutoGenerateColumns="false" Width="100%" 
                onrowcommand="GV_RowCommand">
                <Columns>
                    <asp:BoundField HeaderText="Product Id" DataField="ProductID" 
                      HeaderStyle-HorizontalAlign="Left" HeaderStyle-Width="80" />
                    <asp:BoundField HeaderText="Product Name" DataField="ProductName" HeaderStyle-HorizontalAlign="Left" />
                    <asp:BoundField HeaderText="Qty / Unit" DataField="QuantityPerUnit" HeaderStyle-HorizontalAlign="Left" />
                    <asp:BoundField HeaderText="Price" DataField="UnitPrice" ItemStyle-HorizontalAlign="Right" />
                    <asp:BoundField HeaderText="Stock" DataField="UnitsInStock" ItemStyle-HorizontalAlign="Center" />
                    <asp:BoundField HeaderText="Discontinued" DataField="Discontinued" ItemStyle-HorizontalAlign="Center" />
                    <asp:TemplateField ItemStyle-HorizontalAlign="Center">
                        <ItemTemplate>
                            <asp:LinkButton ID="LinkButton1" runat="server" 
                              CommandName="IsEdit" CommandArgument='<%#Eval("ProductID") %>'>Edit</asp:LinkButton>&nbsp;
                            <asp:LinkButton ID="LinkButton2" runat="server" 
                              CommandName="IsDelete" CommandArgument='<%#Eval("ProductID") %>' 
                              OnClientClick="javascript:return confirm('Are you sure want to delete?')">Delete</asp:LinkButton>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>    
        </asp:View>
        <asp:View ID="View2" runat="server">
            <table width="100%">
                <tr>
                    <th width="100" align="right">Name&nbsp;&nbsp;</th>
                    <td>
                        <asp:HiddenField ID="hfProductId" runat="server" Value="0" />
                        <asp:TextBox ID="txtName" runat="server" Width="300"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="RF1" runat="server" 
                          ControlToValidate="txtName" ErrorMessage="Enter Product Name" 
                          ValidationGroup="Save"></asp:RequiredFieldValidator>
                    </td>
                </tr>
                <tr>
                    <th align="right">Qty per unit&nbsp;&nbsp;</th>
                    <td><asp:TextBox ID="txtQty" runat="server" Width="300"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="RF2" runat="server" 
                          ControlToValidate="txtQty" ErrorMessage="Enter Qty" 
                          ValidationGroup="Save"></asp:RequiredFieldValidator>
                    </td>
                </tr>
                <tr>
                    <th align="right">Price&nbsp;&nbsp;</th>
                    <td><asp:TextBox ID="txtPrice" runat="server" Width="300"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="RF3" runat="server" 
                          ControlToValidate="txtPrice" ErrorMessage="Enter Price" 
                          ValidationGroup="Save"></asp:RequiredFieldValidator>
                    </td>
                </tr>
                <tr>
                    <th align="right">Stock&nbsp;&nbsp;</th>
                    <td><asp:TextBox ID="txtStock" runat="server" Width="300"></asp:TextBox>
                        <asp:RequiredFieldValidator ID="RF4" runat="server" 
                          ControlToValidate="txtStock" ErrorMessage="Enter Stock" 
                          ValidationGroup="Save"></asp:RequiredFieldValidator>
                    </td>
                </tr>
                <tr>
                    <th align="right">Discontinued&nbsp;&nbsp;</th>
                    <td><asp:CheckBox ID="chkDiscontinued" runat="server" /></td>
                </tr>
                <tr>
                    <td>&nbsp;</td>
                    <td>
                        <asp:Button ID="btnSave" runat="server" Text="Save" 
                          onclick="btnSave_Click" ValidationGroup="Save" />&nbsp;
                        <asp:Button ID="btnCancel" runat="server" 
                          Text="Cancel" onclick="btnCancel_Click" />
                    </td>
                </tr>
            </table>
        </asp:View>
    </asp:MultiView>
</asp:Content>

Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Business;
 
namespace WebApp
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                BindGrid();
        }
 
        private void BindGrid()
        {
            GV.DataSource = Product.GetProducts();
            GV.DataBind();
        }
 
        protected void btnAdd_Click(object sender, EventArgs e)
        {
            MultiView1.ActiveViewIndex = 1;
            hfProductId.Value = "0";
            txtName.Text = txtQty.Text = txtPrice.Text = txtStock.Text = String.Empty;
            chkDiscontinued.Checked = false;
        }
 
        protected void btnSave_Click(object sender, EventArgs e)
        {
            Product objProduct = new Product
            {
                ProductName = txtName.Text,
                QuantityPerUnit = txtQty.Text,
                UnitPrice = Convert.ToDecimal(txtPrice.Text),
                UnitsInStock = Convert.ToByte(txtStock.Text),
                Discontinued = chkDiscontinued.Checked
            };
 
            if (hfProductId.Value != "0")
                objProduct.ProductID = Convert.ToInt32(hfProductId.Value);
 
            objProduct.Save();
            MultiView1.ActiveViewIndex = 0;
            BindGrid();
        }
 
        protected void btnCancel_Click(object sender, EventArgs e)
        {
            MultiView1.ActiveViewIndex = 0;
        }
 
        protected void GV_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            if (!String.IsNullOrEmpty(Convert.ToString(e.CommandArgument)))
            {
                Product objProduct = Product.GetProduct(Convert.ToInt32(e.CommandArgument));
 
                switch (e.CommandName)
                {
                    case "IsEdit":
                        hfProductId.Value = objProduct.ProductID.ToString();
                        txtName.Text = objProduct.ProductName;
                        txtQty.Text = objProduct.QuantityPerUnit;
                        txtPrice.Text = (objProduct.UnitPrice ?? 0).ToString();
                        txtStock.Text = (objProduct.UnitsInStock ?? 0).ToString();
                        chkDiscontinued.Checked = objProduct.Discontinued;
                        MultiView1.ActiveViewIndex = 1;
                        break;
                    case "IsDelete":
                        objProduct.Delete();
                        BindGrid();
                        break;
                }
            }
        }
    }
}

This is a default demo page where all the functionality related Insert, Update and Delete described in easy manner. 

Conclusion 

This is all about the architecture. In short we have created a business project with our edmx and attach our classes so that combine with the orginal stuff without changing them. The biggest benefit that you do not need to worry if you change your edmx or update models in it because all things are separate.

I hope you enjoyed the article and thanks for reading this.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here