プロジェクトで再利用するために作成したDataHelperクラスを介して、一元化されたDataContext用にまとめたコードについて意見を聞きたいと思います。
注-ここには大量のコードがあります。申し訳ありませんが、完全なアプローチとアイデアの使用法をレイアウトしたかったのです。これが正しいアプローチだと言っているわけではありませんが、これまでのところうまくいきます(まだアプローチで遊んでいて、まだ本番環境ではありませんが、長年にわたって構築したものと非常に似ています)、本当に建設的になりたいですそれが正気でない、素晴らしい、改善できるかどうかなどを確認するために私が構築したものに関するコミュニティからのフィードバック...
私がこれに入れたいくつかの考え:
- データコンテキストは、簡単にアクセスできる共通のメモリスペースに保存する必要があります
- トランザクションは同じアプローチを取る必要があります
- 適切に廃棄する必要があります
- トランザクションでの保存と削除のためのビジネスロジックのより良い分離を可能にします。
各アイテムのコードは次のとおりです。
1-最初に、現在のHttpContext.Current.Itemsコレクションに格納されているデータコンテキスト(つまり、ページの存続期間中のみ存続し、最初に要求されたときに1回だけ起動されます)、または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-トランザクションをサポートするために、私は同様のアプローチを使用し、開始、コミット、およびロールバックするためのヘルパーメソッドも含めます。
/// <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-だから私はビジネスクラス、関心の分離、再利用可能なコード、すべての素晴らしいものに夢中です。私は「エンティティジェネリック」と呼んでいるアプローチを持っています。これは、システム内の任意のエンティティ(たとえば、住所や電話)に適用できます。
顧客は、店舗、人、または実際に何かと一緒に、それぞれ1つ以上を持つことができます。したがって、外国人を受け入れるアドレスエンティティを構築できるのに、それを必要とするすべてのものに通り、都市、州などを追加するのはなぜですか。タイプとキー(私が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;
}
各住所、電話などが独自のビジネスクラスによってどのように処理されるかに注意してください。これが電話のsaveメソッドです-指示がない限り、実際にはここで保存が行われないことに注意してください(保存は顧客のメソッドで処理されるため、saveはコンテキストのために一度だけ呼び出されます)
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; }
}
}
これに対するもう1つの要素は、電話や住所などの再利用可能なUIコードです。これは、すべての検証などを1か所で処理します。
だから、あなたの考えを流し、この巨大な投稿を読んだりレビューしたりするために時間を割いてくれてありがとう!