我想通过我创建的可在项目中重复使用的 DataHelper 类获得对集中式 DataContext 代码的意见。
注意- 这里有大量代码,对此感到抱歉,但我真的很想布局完整的方法并用于我的想法。我不是说这是正确的方法,但到目前为止它对我有用(仍在使用这种方法,还没有在生产中,但与我多年来构建的东西非常相似),我真的想变得有建设性社区对我所构建的东西的反馈,看看它是否疯狂、伟大、可以改进等等......
我对此提出了一些想法:
- 数据上下文需要存储在一个公共的内存空间中,易于访问
- 交易应采取相同的方法
- 必须妥善处理
- 允许更好地分离事务中保存和删除的业务逻辑。
以下是每个项目的代码:
1 - 首先存储在当前 HttpContext.Current.Items 集合中的数据上下文(因此它只存在于页面的生命周期中,并且只在第一次请求时被触发一次)或者如果 HttpContext 不存在则使用 ThreadSlot (在这种情况下,该代码最能自行清理它,就像使用它的控制台应用程序......):
public static class DataHelper {
/// <summary>
/// Current Data Context object in the HTTPContext or Current Thread
/// </summary>
public static TemplateProjectContext Context {
get {
TemplateProjectContext context = null;
if (HttpContext.Current == null) {
LocalDataStoreSlot threadSlot = Thread.GetNamedDataSlot("DataHelper.CurrentContext");
if (Thread.GetData(threadSlot) == null) {
context = new TemplateProjectContext();
Thread.SetData(threadSlot, context);
} else {
context = (TemplateProjectContext)Thread.GetData(threadSlot);
}
} else {
if (HttpContext.Current.Items["DataHelper.CurrentContext"] == null) {
context = new TemplateProjectContext();
HttpContext.Current.Items["DataHelper.CurrentContext"] = context;
} else {
context = (TemplateProjectContext)HttpContext.Current.Items["DataHelper.CurrentContext"];
}
}
return context;
}
set {
if (HttpContext.Current == null) {
if (value == null) {
Thread.FreeNamedDataSlot("DataHelper.CurrentContext");
} else {
LocalDataStoreSlot threadSlot = Thread.GetNamedDataSlot("DataHelper.CurrentContext");
Thread.SetData(threadSlot, value);
}
} else {
if (value == null)
HttpContext.Current.Items.Remove("DataHelper.CurrentContext");
else
HttpContext.Current.Items["DataHelper.CurrentContext"] = value;
}
}
}
...
2 - 为了支持事务,我使用了类似的方法,并且还包括了 Begin、Commit 和 Rollback 的辅助方法:
/// <summary>
/// Current Transaction object in the HTTPContext or Current Thread
/// </summary>
public static DbTransaction Transaction {
get {
if (HttpContext.Current == null) {
LocalDataStoreSlot threadSlot = Thread.GetNamedDataSlot("currentTransaction");
if (Thread.GetData(threadSlot) == null) {
return null;
} else {
return (DbTransaction)Thread.GetData(threadSlot);
}
} else {
if (HttpContext.Current.Items["currentTransaction"] == null) {
return null;
} else {
return (DbTransaction)HttpContext.Current.Items["currentTransaction"];
}
}
}
set {
if (HttpContext.Current == null) {
LocalDataStoreSlot threadSlot = Thread.GetNamedDataSlot("currentTransaction");
Thread.SetData(threadSlot, value);
} else {
HttpContext.Current.Items["currentTransaction"] = value;
}
}
}
/// <summary>
/// Begins a transaction based on the common connection and transaction
/// </summary>
public static void BeginTransaction() {
DataHelper.Transaction = DataHelper.CreateSqlTransaction();
}
/// <summary>
/// Creates a SqlTransaction object based on the current common connection
/// </summary>
/// <returns>A new SqlTransaction object for the current common connection</returns>
public static DbTransaction CreateSqlTransaction() {
return CreateSqlTransaction(DataHelper.Context.Connection);
}
/// <summary>
/// Creates a SqlTransaction object for the requested connection object
/// </summary>
/// <param name="connection">Reference to the connection object the transaction should be created for</param>
/// <returns>New transaction object for the requested connection</returns>
public static DbTransaction CreateSqlTransaction(DbConnection connection) {
if (connection.State != ConnectionState.Open) connection.Open();
return connection.BeginTransaction();
}
/// <summary>
/// Rolls back and cleans up the current common transaction
/// </summary>
public static void RollbackTransaction() {
if (DataHelper.Transaction != null) {
DataHelper.RollbackTransaction(DataHelper.Transaction);
if (HttpContext.Current == null) {
Thread.FreeNamedDataSlot("currentTransaction");
} else {
HttpContext.Current.Items.Remove("currentTransaction");
}
}
}
/// <summary>
/// Rolls back and disposes of the requested transaction
/// </summary>
/// <param name="transaction">The transaction to rollback</param>
public static void RollbackTransaction(DbTransaction transaction) {
transaction.Rollback();
transaction.Dispose();
}
/// <summary>
/// Commits and cleans up the current common transaction
/// </summary>
public static void CommitTransaction() {
if (DataHelper.Transaction != null) {
DataHelper.CommitTransaction(DataHelper.Transaction);
if (HttpContext.Current == null) {
Thread.FreeNamedDataSlot("currentTransaction");
} else {
HttpContext.Current.Items.Remove("currentTransaction");
}
}
}
/// <summary>
/// Commits and disposes of the requested transaction
/// </summary>
/// <param name="transaction">The transaction to commit</param>
public static void CommitTransaction(DbTransaction transaction) {
transaction.Commit();
transaction.Dispose();
}
3 - 清洁和易于处理
/// <summary>
/// Cleans up the currently active connection
/// </summary>
public static void Dispose() {
if (HttpContext.Current == null) {
LocalDataStoreSlot threadSlot = Thread.GetNamedDataSlot("DataHelper.CurrentContext");
if (Thread.GetData(threadSlot) != null) {
DbTransaction transaction = DataHelper.Transaction;
if (transaction != null) {
DataHelper.CommitTransaction(transaction);
Thread.FreeNamedDataSlot("currentTransaction");
}
((TemplateProjectContext)Thread.GetData(threadSlot)).Dispose();
Thread.FreeNamedDataSlot("DataHelper.CurrentContext");
}
} else {
if (HttpContext.Current.Items["DataHelper.CurrentContext"] != null) {
DbTransaction transaction = DataHelper.Transaction;
if (transaction != null) {
DataHelper.CommitTransaction(transaction);
HttpContext.Current.Items.Remove("currentTransaction");
}
((TemplateProjectContext)HttpContext.Current.Items["DataHelper.CurrentContext"]).Dispose();
HttpContext.Current.Items.Remove("DataHelper.CurrentContext");
}
}
}
3b - 我在 MVC 中构建它,所以我有一个“基础”控制器类,我的所有控制器都继承自它——这样,上下文只在第一次被请求访问时才存在,直到页面被释放,这样它不要太“长时间运行”
using System.Web.Mvc;
using Core.ClassLibrary;
using TemplateProject.Business;
using TemplateProject.ClassLibrary;
namespace TemplateProject.Web.Mvc {
public class SiteController : Controller {
protected override void Dispose(bool disposing) {
DataHelper.Dispose();
base.Dispose(disposing);
}
}
}
4 - 所以我很喜欢业务类、关注点分离、可重用代码,以及所有这些美妙的东西。我有一种我称之为“实体通用”的方法,它可以应用于我系统中的任何实体——例如,地址和电话
一个客户可以有一个或多个,以及一个商店、个人或任何东西——所以当你可以构建一个地址实体时,为什么要添加街道、城市、州等到每一个需要它的东西,这需要一个外国类型和键(我称之为 EntityType 和 EntityId)——然后你有一个可重用的业务对象,支持 UI 控制等——所以你构建它一次并在任何地方重用它。
这就是我在这里推动的集中式方法真正派上用场的地方,我认为使代码比必须将当前数据上下文/事务传递到每个方法中要干净得多。
例如,您有一个客户页面,该模型包括客户数据、联系人、地址和一些电话号码(主要、传真或手机等)
在获取页面的客户编辑模型时,这是我整理的一些代码(请参阅我如何在 LINQ 中使用 DataHelper.Context):
public static CustomerEditModel FetchEditModel(int customerId) {
if (customerId == 0) {
CustomerEditModel model = new CustomerEditModel();
model.MainContact = new CustomerContactEditModel();
model.MainAddress = new AddressEditModel();
model.ShippingAddress = new AddressEditModel();
model.Phone = new PhoneEditModel();
model.Cell = new PhoneEditModel();
model.Fax = new PhoneEditModel();
return model;
} else {
var output = (from c in DataHelper.Context.Customers
where c.CustomerId == customerId
select new CustomerEditModel {
CustomerId = c.CustomerId,
CompanyName = c.CompanyName
}).SingleOrDefault();
if (output != null) {
output.MainContact = CustomerContact.FetchEditModelByPrimary(customerId) ?? new CustomerContactEditModel();
output.MainAddress = Address.FetchEditModelByType(BusinessEntityTypes.Customer, customerId, AddressTypes.Main) ?? new AddressEditModel();
output.ShippingAddress = Address.FetchEditModelByType(BusinessEntityTypes.Customer, customerId, AddressTypes.Shipping) ?? new AddressEditModel();
output.Phone = Phone.FetchEditModelByType(BusinessEntityTypes.Customer, customerId, PhoneTypes.Main) ?? new PhoneEditModel();
output.Cell = Phone.FetchEditModelByType(BusinessEntityTypes.Customer, customerId, PhoneTypes.Cell) ?? new PhoneEditModel();
output.Fax = Phone.FetchEditModelByType(BusinessEntityTypes.Customer, customerId, PhoneTypes.Fax) ?? new PhoneEditModel();
}
return output;
}
}
这是返回要使用的编辑模型的手机示例:
public static PhoneEditModel FetchEditModelByType(byte entityType, int entityId, byte phoneType) {
return (from p in DataHelper.Context.Phones
where p.EntityType == entityType
&& p.EntityId == entityId
&& p.PhoneType == phoneType
select new PhoneEditModel {
PhoneId = p.PhoneId,
PhoneNumber = p.PhoneNumber,
Extension = p.Extension
}).FirstOrDefault();
}
现在页面已经回发,这一切都需要保存,所以我控件中的 Save 方法只是让业务对象处理这一切:
[Authorize(Roles = SiteRoles.SiteAdministrator + ", " + SiteRoles.Customers_Edit)]
[HttpPost]
public ActionResult Create(CustomerEditModel customer) {
return CreateOrEdit(customer);
}
[Authorize(Roles = SiteRoles.SiteAdministrator + ", " + SiteRoles.Customers_Edit)]
[HttpPost]
public ActionResult Edit(CustomerEditModel customer) {
return CreateOrEdit(customer);
}
private ActionResult CreateOrEdit(CustomerEditModel customer) {
if (ModelState.IsValid) {
SaveResult result = Customer.SaveEditModel(customer);
if (result.Success) {
return RedirectToAction("Index");
} else {
foreach (KeyValuePair<string, string> error in result.ErrorMessages) ModelState.AddModelError(error.Key, error.Value);
}
}
return View(customer);
}
在 Customer 业务对象内部——它集中处理事务,让 Contact、Address 和 Phone 业务类做他们的事情,而不用担心事务:
public static SaveResult SaveEditModel(CustomerEditModel model) {
SaveResult output = new SaveResult();
DataHelper.BeginTransaction();
try {
Customer customer = null;
if (model.CustomerId == 0) customer = new Customer();
else customer = DataHelper.Context.Customers.Single(c => c.CustomerId == model.CustomerId);
if (customer == null) {
output.Success = false;
output.ErrorMessages.Add("CustomerNotFound", "Unable to find the requested Customer record to update");
} else {
customer.CompanyName = model.CompanyName;
if (model.CustomerId == 0) {
customer.SiteGroup = CoreSession.CoreSettings.CurrentSiteGroup;
customer.CreatedDate = DateTime.Now;
customer.CreatedBy = SiteLogin.Session.ActiveUser;
DataHelper.Context.Customers.AddObject(customer);
} else {
customer.ModifiedDate = DateTime.Now;
customer.ModifiedBy = SiteLogin.Session.ActiveUser;
}
DataHelper.Context.SaveChanges();
SaveResult result = Address.SaveEditModel(model.MainAddress, BusinessEntityTypes.Customer, customer.CustomerId, AddressTypes.Main, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
result = Address.SaveEditModel(model.ShippingAddress, BusinessEntityTypes.Customer, customer.CustomerId, AddressTypes.Shipping, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
result = Phone.SaveEditModel(model.Phone, BusinessEntityTypes.Customer, customer.CustomerId, PhoneTypes.Main, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
result = Phone.SaveEditModel(model.Fax, BusinessEntityTypes.Customer, customer.CustomerId, PhoneTypes.Fax, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
result = Phone.SaveEditModel(model.Cell, BusinessEntityTypes.Customer, customer.CustomerId, PhoneTypes.Cell, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
result = CustomerContact.SaveEditModel(model.MainContact, customer.CustomerId, false);
if (!result.Success) {
output.Success = false;
output.ErrorMessages.Concat(result.ErrorMessages);
}
if (output.Success) {
DataHelper.Context.SaveChanges();
DataHelper.CommitTransaction();
} else {
DataHelper.RollbackTransaction();
}
}
} catch (Exception exp) {
DataHelper.RollbackTransaction();
ErrorHandler.Handle(exp, true);
output.Success = false;
output.ErrorMessages.Add(exp.GetType().ToString(), exp.Message);
output.Exceptions.Add(exp);
}
return output;
}
请注意每个地址、电话等是如何由其自己的业务类处理的,这是电话的保存方法 - 请注意,除非您告诉它,否则它实际上不会在此处进行保存(保存在客户的方法中处理,因此保存是只为上下文调用一次)
public static SaveResult SaveEditModel(PhoneEditModel model, byte entityType, int entityId, byte phoneType, bool saveChanges) {
SaveResult output = new SaveResult();
try {
if (model != null) {
Phone phone = null;
if (model.PhoneId != 0) {
phone = DataHelper.Context.Phones.Single(x => x.PhoneId == model.PhoneId);
if (phone == null) {
output.Success = false;
output.ErrorMessages.Add("PhoneNotFound", "Unable to find the requested Phone record to update");
}
}
if (string.IsNullOrEmpty(model.PhoneNumber)) {
if (model.PhoneId != 0 && phone != null) {
DataHelper.Context.Phones.DeleteObject(phone);
if (saveChanges) DataHelper.Context.SaveChanges();
}
} else {
if (model.PhoneId == 0) phone = new Phone();
if (phone != null) {
phone.EntityType = entityType;
phone.EntityId = entityId;
phone.PhoneType = phoneType;
phone.PhoneNumber = model.PhoneNumber;
phone.Extension = model.Extension;
if (model.PhoneId == 0) {
phone.CreatedDate = DateTime.Now;
phone.CreatedBy = SiteLogin.Session.ActiveUser;
DataHelper.Context.Phones.AddObject(phone);
} else {
phone.ModifiedDate = DateTime.Now;
phone.ModifiedBy = SiteLogin.Session.ActiveUser;
}
if (saveChanges) DataHelper.Context.SaveChanges();
}
}
}
} catch (Exception exp) {
ErrorHandler.Handle(exp, true);
output.Success = false;
output.ErrorMessages.Add(exp.GetType().ToString(), exp.Message);
output.Exceptions.Add(exp);
}
return output;
}
仅供参考 - SaveResult 只是一个小容器类,如果保存失败,我用来获取详细信息:
public class SaveResult {
private bool _success = true;
public bool Success {
get { return _success; }
set { _success = value; }
}
private Dictionary<string, string> _errorMessages = new Dictionary<string, string>();
public Dictionary<string, string> ErrorMessages {
get { return _errorMessages; }
set { _errorMessages = value; }
}
private List<Exception> _exceptions = new List<Exception>();
public List<Exception> Exceptions {
get { return _exceptions; }
set { _exceptions = value; }
}
}
另一部分是电话、地址等的可重用 UI 代码 - 它也仅在一个位置处理所有验证等。
因此,让您的想法畅通无阻,感谢您抽出宝贵时间阅读/审阅这篇巨大的帖子!