您的想法:通过DataHelper类实现的实体框架数据上下文(集中式上下文和事务)

时间:2012-07-08 17:37:47

标签: c# asp.net-mvc entity-framework

我希望通过我创建的DataHelper类来集中DataContext的代码,以便在项目上重复使用。

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

我对此提出了一些想法:

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

    1 - 首先将数据上下文存储在当前的HttpContext.Current.Items集合中(因此它仅存在于页面的生命周期中,并且仅在第一次请求时被触发一次)或者如果HttpContext不存在。 t存在使用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中构建这个,所以我有一个&#34; base&#34;我的所有控制器继承的控制器类 - 这样Context只存在于首次访问请求时,直到页面被处理,这样它就不会“#34;长时间运行&#34;

    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 - 所以我很擅长商务课程,关注点分离,可重复使用的代码,以及所有精彩内容。我有一种方法,我称之为&#34;实体通用&#34;可以应用于我系统中的任何实体 - 例如,地址和电话

    一个客户可以拥有一个或多个,以及一个商店,人员或任何其他东西 - 所以为什么在你可以建立一个地址实体时,将街道,城市,州等添加到需要它的每个东西,采用外来类型和密钥(我称之为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业务对象中 - 它集中处理事务,让联系人,地址和电话业务类做他们的事情,并且真的不担心事务:

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

    所以,让你的想法流动,并感谢花时间阅读/审查这个巨大的帖子!

0 个答案:

没有答案