65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Entity Framework 的最佳方式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (9投票s)

2013 年 1 月 14 日

CPOL

3分钟阅读

viewsIcon

66259

downloadIcon

531

最简单、最佳和最健壮的架构。

引言

我过去 4 年一直在使用 .NET。从去年开始,我一直在使用带有 Entity Framework 和 MVC 的 4.0 框架。在阅读了大量的文章并进行了大量的研究后,我创建了一个架构,以一种健壮且简单的方式使用 Entity Framework。

背景 

此架构背后的基本思想是为开发人员提供一种健壮的方式使用 Entity Framework 数据上下文,而无需为每个单独的实体表类编写自定义的 Insert、Update 和 Delete 方法。

开始集成

让我们从集成开始,首先我们需要创建我们的实体模型,您可以使用任何方法来创建 EF 设计器类,例如 Code First、Model First 或 Database First。 但在我看来,数据库优先的方法是最好的,因为在这种方法中,开发人员可以最大限度地利用这个特定框架的好处。

因此,我创建了 edmx 文件,将其与所需的数据库连接,并通过逐步向导生成表类。 这里是生成 edmx 文件的详细描述。


生成上述 edmx 后,我们将创建两个类,名称为 Global.cs,它将以一种健壮的方式处理数据库上下文,另一个是 Helper.cs,它将包含 Insert、Update 和 Delete 操作的通用扩展方法。 Product.cs 是产品实体的部分类,我将在本文后面讨论这个类。

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

上面的类在 HttpContext 中保存 Entity Framework 数据上下文。 这件事将使 DataContext 以一种健壮的方式使用,因为数据上下文对于每个 HTTP 请求都保持相同。 在这种工作方式中,初始化和释放特定代码块上的数据上下文的噩梦被消除了。 上下文将在 HTTP 请求结束时被释放。

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

上面的类包含扩展方法,该方法将处理针对具有数据库上下文的 DB 的 Insert、Delete 和 Update 操作。 正如您所看到的,这些扩展方法用于 EntityObject 类,并且该类继承了由实体框架生成的所有数据库表类,请参阅下面的 Northwind.Designer.cs 图像

 

 

所以这意味着这个数据库实体类也可以使用上面的扩展方法,因为精彩的 OOPS 概念继承 Smile

如何使用上述架构

现在大部分事情都已完成,让我们看看使用此架构的示例。 因为我创建了 Product 的一个部分类,并且这个特定的类在设计器类中也有一个定义。

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

这只是一个简单的类,它有两个静态方法来检索产品表数据。 这里要注意的是,我正在使用我们之前创建的 Global.Context

现在这个业务逻辑项目已经完成,您可以根据需要向其中添加更多类。 您可以将此层附加到您的任何类型的项目 Web 应用程序或网站等。 在我的例子中,我在我的演示中附加了一个 web 应用程序

 

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

这是一个默认的演示页面,其中所有与 Insert、Update 和 Delete 相关的功能都以简单的方式描述。

结论 

这就是关于架构的全部内容。 简而言之,我们创建了一个带有 edmx 的业务项目,并将我们的类附加到它,以便与原始内容组合在一起而无需更改它们。 最大的好处是,如果更改 edmx 或更新其中的模型,您不必担心,因为所有内容都是分开的。

我希望你喜欢这篇文章,感谢你的阅读。

© . All rights reserved.