使用 Entity Framework 的最佳方式






4.65/5 (9投票s)
最简单、最佳和最健壮的架构。
引言
我过去 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 概念继承
如何使用上述架构
现在大部分事情都已完成,让我们看看使用此架构的示例。 因为我创建了 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> <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 </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 </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 </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 </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 </th> <td><asp:CheckBox ID="chkDiscontinued" runat="server" /></td> </tr> <tr> <td> </td> <td> <asp:Button ID="btnSave" runat="server" Text="Save" onclick="btnSave_Click" ValidationGroup="Save" /> <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 或更新其中的模型,您不必担心,因为所有内容都是分开的。
我希望你喜欢这篇文章,感谢你的阅读。