[英]Generic Repository with Data Access Layer
我正在使用業務對象(Employee,Product)創建一個新項目。 由於約束,我沒有使用LINQ to SQL或任何ORM Mapper。
我必須手動編碼數據訪問層。 我有興趣使用'存儲庫模式'。
根據我的理解,我必須創建一個通用存儲庫IRepository
,它由所有存儲庫ProductRepository, EmployeeRepository
。
令我困惑的是,不同的業務對象有不同的要求。 例如:
ProductRepository
GetAllProducts ();
GetProductById (int id);
GetProductByMaxPrice (double price);
GetProductByNamePrice (string name, double Price);
Get... (...);
EmployeeRepository
GetEmployeeByAge ();
GetEmployeeByJob (string description);
GetEmployeeBySalary (double salary);
Get... (...); //and so on
如何創建滿足不同對象的不同數據訪問要求的通用存儲庫 ?
我已經閱讀了很多關於Repository Pattern的理論,但我真的很欣賞一個有效的例子。
此外,如果我可以使用通用存儲庫創建所有存儲庫,則使用工廠模式也變得容易。 例如:
interface IRepository
{
....
}
ProductRepository : IRepository
{
....
}
EmployeeRepository : IRepository
{
....
}
然后我們可以有效地使用工廠模式:
IRepository repository;
repository = new ProductRepository ();
repository.Call_Product_Methods ();
repository = new EmployeeRepository ();
repository.Call_Employee_Methods ();
存儲庫模式是一個很好用的模式,但是如果它沒有正確完成,而不是讓你的生活更輕松,那將是一個巨大的痛苦!
因此,最好的方法(因為你不想使用EF或其他ORM)是通過創建一個通用接口,然后是一個基本的抽象實現。 這樣您就不需要對每個存儲庫進行編碼,您只需按類型對它們進行實例化即可!
在此之后,如果您有某些特定於某些實體的特定方法,則可以從Repository繼承並覆蓋或添加方法和屬性為nedded。
如果您想使用存儲庫模式,我還建議您使用IUnitOfWork模式,並將其與存儲庫分開。
這兩個接口看起來應該是這樣的:
非常簡單的IUnitOfWork:
Public interface IUnitOfWork
{
bool Save();
}
而他們,Repository接口,使用泛型:
public interface IRepository<TEntity> : IDisposable where TEntity : class
IUnitOfWork Session { get;}
IList<TEntity> GetAll();
IList<TEntity> GetAll(string[] include);
IList<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate);
bool Add(TEntity entity);
bool Delete(TEntity entity);
bool Update(TEntity entity);
bool IsValid(TEntity entity);
}
方法.Add(),. Delale()不應該向數據庫發送任何內容,但是它們應該始終將更改發送到IUnitOfWork(您可以在DAL類中實現),並且只有在您調用.Save()時IUnitOfWork的方法,你將東西保存到數據庫。
我已經使用EntityFramework實現了我的Repository類,這使事情變得更容易,但您可以按照您想要的任何方式執行。
您將使用的代碼將是這樣的:
void SomeMethod()
{
using (IUnitOfWork session = new YourUnitOfWorkImplementation())
{
using (var rep = new Repository<Client>(session))
{
var client1 = new Client("Bob");
var client2 = new Cliente("John");
rep.Add(client1);
rep.Add(client2);
var clientToDelete = rep.GetAll(c=> c.Name == "Frank").FirstOrDefaut();
rep.Delete(clientToDelete);
//Now persist the changes to the database
session.Save();
{
{
}
就像我說的,使用EF和DbContext,這要容易得多,這里只是我的Repository類的一小部分:
public class Repository : Component, IRepository
{
protected DbContext session;
{
get
{
if (session == null)
throw new InvalidOperationException("A session IUnitOfWork do repositório não está instanciada.");
return (session as IUnitOfWork);
}
}
public virtual DbContext Context
{
get
{
return session;
}
}
public Repository()
: base()
{
}
public Repository(DbContext instance)
: this(instance as IUnitOfWork)
{
#endregion
public IList<TEntity> GetAll<TEntity>() where TEntity : class
{
return session.Set<TEntity>().ToList();
}
public bool Add<TEntity>(TEntity entity) where TEntity : class
{
if (!IsValid(entity))
return false;
try
{
session.Set(typeof(TEntity)).Add(entity);
return session.Entry(entity).GetValidationResult().IsValid;
}
catch (Exception ex)
{
if (ex.InnerException != null)
throw new Exception(ex.InnerException.Message, ex);
throw new Exception(ex.Message, ex);
}
} ...
這樣你就不需要構建一個GetEmployeeByAge,你只需寫:
IEnumerable<Employee> GetEmployee(int age)
{
return rep.GetAll<Employee>(e=> e.Age == age);
}
或者你可以直接調用(不需要創建方法)
一般而言,在我看來,通用存儲庫“基礎”接口並沒有真正解決這么多問題。 有人提到它理論上可以提供一個獲取整數並返回記錄的get屬性。 是的,這很方便 - 根據您的使用情況,甚至可能需要。
我個人畫線的地方是Insert
, Update
和Delete
方法。 在所有,但最簡單的情況下,我們應該確定我們在做什么 。 是的,創建新的Supplier
可能僅僅意味着調用Insert
操作。 但是大多數非平凡的案例,你都會做其他事情。
因此,在設計存儲庫時,我認為最好確定您要執行的操作,並確定具有以下命名的方法:
CreateClient(); // Might well just be a single Insert.... might involve other operations
MoveClientToCompany(); // several updates right here
GetContractsForClient(); // explicitly returns contracts belonging to a client
我們現在定義我們用數據做什么。 通用插入,更新和刪除方法不推斷我們的代碼庫的使用,並通過開發誰不明白其他附屬事情需要發生,當我們真正去, 做一些可能導致誤操作。
那么什么是基礎存儲庫的一個很好的例子呢? 那么,實現緩存的存儲庫呢? 基本存儲庫可以具有某種緩存,並且我們的派生存儲庫可以使用該緩存來返回陳舊數據(如果有人希望的話)。
即使是this[int]
當我們需要回答什么 ,我們要恢復默認的屬性有復雜的問題。 如果它是一個有很多引用的大對象,我們是否會將所有部分返回整個事物,或者我們將返回一個非常簡單的POCO,需要進一步查詢以填補空白。 通用this[int]
不回答這個問題,但是:
GetBareBonesClient(int id);
GetClientAndProductDetail(int id);
GetClientAndContracts(int id);
在我看來,相當明確。 在intellisense的這些日子里,編寫針對您的存儲庫的開發人員將知道他/她需要調用什么來獲得他們想要的東西。 您如何確定存在多少這些方法? 好吧,你看看你實際開發的產品。 你有什么案例可以獲取數據......誰來獲取數據,以及他們為什么要獲取數據? 大多數時候,這些都是容易回答的問題。
然而,一個常見問題是當我們想要允許用戶以表格形式“瀏覽”數據時。 “給我'x'記錄的數量,按'x'字段排序,以分頁的方式......哦,我可能會或可能不會在某些列上包含某種搜索”。 這種代碼是您真正不希望為每個存儲庫實現的代碼。 因此,在假設的IRepositoryThatSupportsPagination
可能存在一些樣板查詢構造的情況。 我相信你能想到一個更好的名字。
顯然,可能會有更多的案例。 但我永遠不會將默認的CRUD
操作拋到基本存儲庫接口/類中,因為除了不重要的,無關緊要的情況之外,它並不意味着什么。
[根據MikeSW的意見編輯]我的觀點(在這里加入Moo-Juice)是你需要選擇最適合你的實現。 存儲庫模式很好(Gabriel的答案描述了一個很好的實現),但是如果以純粹的形式實現它可能會有很多工作。 ORM自動化了許多繁重的工作。
無論您選擇哪種方法,都需要以下組件:
您的業務接口 - 客戶端程序員需要調用的方法,例如GetAllEmployees(條件),UpdateEmployee(員工雇員)等。如果您有客戶端/服務器體系結構,這些將對應於具有數據協定的服務調用。
您的內部邏輯創建適當的輸出以滿足您的合同。 這將是組成查詢或執行多個數據庫更新的層,例如,UpdateEmployee可能必須驗證員工是否存在,更新者是否有權更新,然后更新多個表,並將審核記錄或記錄插入審核隊列。 這將涉及查詢和更新,並將成為一個工作單元。
您的數據訪問體系結構,由您的內部邏輯調用。 這就是存儲庫模式的用武之地。無論您使用什么,這都需要以下內容:
3.1實施工作單元的課程。 在存儲庫模式中,這只有Save() - 但這需要內存狀態管理。 我更喜歡使用以下接口進行sql驅動的實現:
public interface ITransactionContext : IDisposable
{
IDbTransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted);
void CommitTransaction();
void RollbackTransaction();
int ExecuteSqlCommand(string sql, params object[] parameters);
IEnumerable<T> SqlQuery<T>(string sql, params object[] parameters);
IEnumerable<T> SqlQuery<T>(string sql, object[] parameters, IDictionary<string, string> mappings);
bool Exists(string sql, params object[] parameters);
}
public interface ITransactionDbContext : ITransactionContext
{
int SaveChanges();
}
我使用EF但我們有一個舊的數據庫,我們需要編寫SQL,這看起來和操作很像EF DbContext。 注意interace ITransactionDbContext,它添加了SaveChanges() - 這是ORM需要的唯一一個。 但如果你不做ORM,你需要其他人。
這是實施。 請注意,它完全基於接口。 您可以通過工廠方法提供具體的數據庫連接。
public class TransactionContext : ITransactionContext
{
protected IDbTransaction Transaction;
protected IDbConnection Connection;
protected readonly Func<IDbConnection> CreateConnection;
public TransactionContext(Func<IDbConnection> createConnection)
{
this.CreateConnection = createConnection;
}
public virtual IDbConnection Open()
{
if (this.Connection == null)
{
this.Connection = this.CreateConnection();
}
if (this.Connection.State == ConnectionState.Closed)
{
this.Connection.Open();
}
return this.Connection;
}
public virtual IDbTransaction BeginTransaction(IsolationLevel isolationLevel)
{
Open();
return this.Transaction ?? (this.Transaction = this.Connection.BeginTransaction(isolationLevel));
}
public virtual void CommitTransaction()
{
if (this.Transaction != null)
{
this.Transaction.Commit();
}
this.Transaction = null;
}
public virtual void RollbackTransaction()
{
if (this.Transaction != null)
{
this.Transaction.Rollback();
}
this.Transaction = null;
}
public virtual int ExecuteSqlCommand(string sql, params object[] parameters)
{
Open();
using (var cmd = CreateCommand(sql, parameters))
{
return cmd.ExecuteNonQuery();
}
}
public virtual IEnumerable<T> SqlQuery<T>(string sql, object[] parameters )
{
return SqlQuery<T>(sql, parameters, null);
}
public IEnumerable<T> SqlQuery<T>(string sql, object[] parameters, IDictionary<string, string> mappings)
{
var list = new List<T>();
var converter = new DataConverter();
Open();
using (var cmd = CreateCommand(sql, parameters))
{
var reader = cmd.ExecuteReader();
if (reader == null)
{
return list;
}
var schemaTable = reader.GetSchemaTable();
while (reader.Read())
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
var item = converter.GetObject<T>(schemaTable, values, mappings);
list.Add(item);
}
}
return list; }
public virtual bool Exists(string sql, params object[] parameters)
{
return SqlQuery<object>(sql, parameters).Any();
}
protected virtual IDbCommand CreateCommand(string commandText = null, params object[] parameters)
{
var command = this.Connection.CreateCommand();
if (this.Transaction != null)
{
command.Transaction = this.Transaction;
}
if (!string.IsNullOrEmpty(commandText))
{
command.CommandText = commandText;
}
if (parameters != null && parameters.Any())
{
foreach (var parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
return command;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (this.Connection != null)
{
this.Connection.Dispose();
}
this.Connection = null;
this.Transaction = null;
}
}
3.2。 然后,您需要基於命令實現更新 。 這是我的(簡化):
public class UpdateHelper
{
private readonly ITransactionContext transactionContext;
public UpdateHelper(ITransactionContext transactionContext)
{
this.transactionContext = transactionContext;
}
public UpdateResponse Update(UpdateRequest request)
{
this.transactionContext.BeginTransaction(IsolationLevel.RepeatableRead);
var response = new UpdateResponse();
foreach (var command in request.Commands)
{
try
{
response = command.PerformAction(transactionContext);
if (response.Status != UpdateStatus.Success)
{
this.transactionContext.RollbackTransaction();
return response;
}
}
catch (Exception ex)
{
this.transactionContext.RollbackTransaction();
return HandleException(command, ex);
}
}
this.transactionContext.CommitTransaction();
return response;
}
private UpdateResponse HandleException(Command command, Exception exception)
{
Logger.Log(exception);
return new UpdateResponse { Status = UpdateStatus.Error, Message = exception.Message, LastCommand = command };
}
}
如您所見,這需要一個將執行操作的Command(即Command模式)。 基本命令實現:
public class Command
{
private readonly UpdateCommandType type;
private readonly object data;
private readonly IDbMapping mapping;
public Command(UpdateCommandType type, object data, IDbMapping mapping)
{
this.type = type;
this.data = data;
this.mapping = mapping;
}
public UpdateResponse PerformAction(ITransactionContext context)
{
var commandBuilder = new CommandBuilder(mapping);
var result = 0;
switch (type)
{
case UpdateCommandType.Insert:
result = context.ExecuteSqlCommand(commandBuilder.InsertSql, commandBuilder.InsertParameters(data));
break;
case UpdateCommandType.Update:
result = context.ExecuteSqlCommand(commandBuilder.UpdateSql, commandBuilder.UpdateParameters(data));
break;
case UpdateCommandType.Delete:
result = context.ExecuteSqlCommand(commandBuilder.DeleteSql, commandBuilder.DeleteParameters(data));
break;
}
return result == 0 ? new UpdateResponse { Status = UpdateStatus.Success } : new UpdateResponse { Status = UpdateStatus.Fail };
}
}
3.3您需要對象進行數據庫映射 。 這是由更新方法使用的。 在此示例中,如果省略映射,則假定EntityType的屬性對應於數據庫列。 您想要每個表的映射。
public interface IDbMapping
{
string TableName { get; }
IEnumerable<string> Keys { get; }
Dictionary<string, string> Mappings { get; }
Type EntityType { get; }
bool AutoGenerateIds { get; }
}
public class EmployeeMapping : IDbMapping
{
public string TableName { get { return "Employee"; } }
public IEnumerable<string> Keys { get { return new []{"EmployeeID"};} }
public Dictionary<string, string> Mappings { get { return null; } } // indicates default mapping based on entity type } }
public Type EntityType { get { return typeof (Employee); } }
public bool AutoGenerateIds { get { return true; } }
}
3.4。 您需要一個查詢構建器對象。 這將根據sql中的用戶輸入構建您的查詢。 例如,您可能希望按姓氏,名字,部門和加入日期搜索員工。 您可以實現這樣的查詢界面:
public interface IEmployeeQuery {
IEmployeeQuery ByLastName(string lastName);
IEmployeeQuery ByFirstName(string firstName);
IEmployeeQuery ByDepartment(string department);
IEmployeeQuery ByJoinDate(Datetime joinDate);
}
這可以通過構建sql查詢或linq查詢的類具體實現。 如果你要使用sql,請實現string Statement
和object[] Parameters
。 然后您的邏輯層可以編寫如下代碼:
public IEnumerable<Employee> QueryEmployees(EmployeeCriteria criteria) {
var query = new EmployeeQuery();
query.ByLastName(criteria.LastName);
query.ByFirstName(criteria.FirstName);
//etc.
using(var dbContext = new TransactionContext()){
return dbContext.SqlQuery<Employee>(query.Statement, query.Parameters);
}
}
3.5。 您需要一個用於對象的命令構建器 。 我建議你使用一個常見的命令構建器。 您可以使用SqlCommandBuilder類,也可以編寫自己的SQL生成器。 我不建議你為每個表和每次更新編寫sql。 這是非常難以維護的部分。 (根據經驗說。我們有一個,我們無法維護它,最終我寫了一個SQL生成器。)
注意:如果您沒有很多更新(即您的應用程序主要是面向顯示的),您可以省略它,只需在需要時手動編寫更新。
這是一個通用構建器(此代碼未經過測試,您需要在需要時使用它):
public interface ICommandBuilder
{
string InsertSql { get; }
string UpdateSql { get; }
string DeleteSql { get; }
Dictionary<string, object> InsertParameters(object data);
Dictionary<string, object> UpdateParameters(object data);
Dictionary<string, object> DeleteParameters(object data);
}
public class CommandBuilder: ICommandBuilder
{
private readonly IDbMapping mapping;
private readonly Dictionary<string, object> fieldParameters;
private readonly Dictionary<string, object> keyParameters;
public CommandBuilder(IDbMapping mapping)
{
this.mapping = mapping;
fieldParameters = new Dictionary<string, object>();
keyParameters = new Dictionary<string, object>();
GenerateBaseSqlAndParams();
}
private void GenerateBaseSqlAndParams()
{
var updateSb = new StringBuilder();
var insertSb = new StringBuilder();
var whereClause = new StringBuilder(" WHERE ");
updateSb.Append("Update " + mapping.TableName + " SET ");
insertSb.Append("Insert Into " + mapping.TableName + " VALUES (");
var properties = mapping.EntityType.GetProperties(); // if you have mappings, work that in
foreach (var propertyInfo in properties)
{
var paramName = propertyInfo.Name;
if (mapping.Keys.Contains(propertyInfo.Name, StringComparer.OrdinalIgnoreCase))
{
keyParameters.Add(paramName, null);
if (!mapping.AutoGenerateIds)
{
insertSb.Append(paramName + ", ");
}
whereClause.Append(paramName + " = @" + paramName);
}
updateSb.Append(propertyInfo.Name + " = @" + paramName + ", ");
fieldParameters.Add(paramName, null);
}
updateSb.Remove(updateSb.Length - 2, 2); // remove the last ","
insertSb.Remove(insertSb.Length - 2, 2);
insertSb.Append(" )");
this.InsertSql = insertSb.ToString();
this.UpdateSql = updateSb.ToString() + whereClause;
this.DeleteSql = "DELETE FROM " + mapping.TableName + whereClause;
}
public string InsertSql { get; private set; }
public string UpdateSql { get; private set; }
public string DeleteSql { get; private set; }
public Dictionary<string, object> InsertParameters(object data)
{
PopulateParamValues(data);
return mapping.AutoGenerateIds ? fieldParameters : keyParameters.Union(fieldParameters).ToDictionary(pair => pair.Key, pair => pair.Value);
}
public Dictionary<string, object> UpdateParameters(object data)
{
PopulateParamValues(data);
return fieldParameters.Union(keyParameters).ToDictionary(pair => pair.Key, pair => pair.Value);
}
public Dictionary<string, object> DeleteParameters(object data)
{
PopulateParamValues(data);
return keyParameters;
}
public void PopulateParamValues(object data)
{
var properties = mapping.EntityType.GetProperties(); // if you have mappings, work that in
foreach (var propertyInfo in properties)
{
var paramName = propertyInfo.Name;
if (keyParameters.ContainsKey(paramName))
{
keyParameters[paramName] = propertyInfo.GetValue(data);
}
if (fieldParameters.ContainsKey(paramName))
{
fieldParameters[paramName] = propertyInfo.GetValue(data);
}
}
}
}
使用更新幫助程序和邏輯層中的命令構建器進行更新的更新示例用法:
public class Logic
{
private readonly Func<ITransactionContext> createContext;
private readonly Func<ITransactionContext, UpdateHelper> createHelper;
public Logic(Func<ITransactionContext> createContext,
Func<ITransactionContext, UpdateHelper> createHelper)
{
this.createContext = createContext;
this.createHelper = createHelper;
}
public int UpdateEmployee(Employee employeeData)
{
using (var context = createContext())
{
var request = new UpdateRequest();
request.Commands.Add(new Command(UpdateCommandType.Update, employeeData, new EmployeeMapping()));
var helper = createHelper(context);
var response = helper.Update(request);
return response.TransactionId ?? 0;
}
}
}
ORM真的會幫助你:
總的來說,這種方法使用Repository模式中的Unit of Work,但是它使用UpdateHelper類代替存儲庫對象及其Add,Update和Delete方法,根據命令模式進行更新。 這允許直接編寫SQL,而無需ORM映射器。
嗯,這很長,但顯然沒有所有的細節我的答案被認為是不值得的。 我希望這有幫助。
是的,您可以根據通用的常量存儲庫接口輕松編寫優雅的DAL層。
不過,它很可能會有一個非常糟糕的表現。
在一個完美的世界中,可以毫無成本地從數據庫中檢索任何信息,一個簡單而通用的存儲庫就足夠了。 不幸的是,事實並非如此 - 對於我們知道數據庫可以處理的每個查詢操作,最好是擁有特定的查詢方法,而不是擁有通用的查詢方法,允許來自業務層的各種瘋狂查詢。 。
編輯
我相信你似乎對一個特定的觀點是錯誤的:避免使用通用的ORM Mapping庫意味着你沒有做ORM。 這不一定是真的。
除非你是暴露通用陣列狀物體的UI(這樣也會使有關存儲庫模式完全無用的討論),您要變換關系數據轉化成域對象。 這正是ORM的意思:你沒有使用NHibernate,EF和LINQ to SQL只是意味着你將有更多的工作。 :-)
所以,不,使用存儲庫模式仍然有意義,無論是否使用自動ORM工具。
當然,還有其他選項,如Active Record 。 這是一個更簡單的模式,它將域對象與數據訪問邏輯混合在一起(這里使用ORM工具也是可選的)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.