2

我想通过我创建的可在项目中重复使用的 DataHelper 类获得对集中式 DataContext 代码的意见。

注意- 这里有大量代码,对此感到抱歉,但我真的很想布局完整的方法并用于我的想法。我不是说这是正确的方法,但到目前为止它对我有用(仍在使用这种方法,还没有在生产中,但与我多年来构建的东西非常相似),我真的想变得有建设性社区对我所构建的东西的反馈,看看它是否疯狂、伟大、可以改进等等......

我对此提出了一些想法:

  1. 数据上下文需要存储在一个公共的内存空间中,易于访问
  2. 交易应采取相同的方法
  3. 必须妥善处理
  4. 允许更好地分离事务中保存和删除的业务逻辑。

以下是每个项目的代码:

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 代码 - 它也仅在一个位置处理所有验证等。

因此,让您的想法畅通无阻,感谢您抽出宝贵时间阅读/审阅这篇巨大的帖子!

4

0 回答 0