C# 具有数据访问层的通用存储库
我正在使用业务对象(员工、产品)创建一个新项目。由于限制,我没有使用LINQ到SQL或任何ORM映射器 我必须手工编写数据访问层的代码。我对使用“存储库模式”感兴趣 据我所知,我必须创建一个通用存储库C# 具有数据访问层的通用存储库,c#,oop,design-patterns,C#,Oop,Design Patterns,我正在使用业务对象(员工、产品)创建一个新项目。由于限制,我没有使用LINQ到SQL或任何ORM映射器 我必须手工编写数据访问层的代码。我对使用“存储库模式”感兴趣 据我所知,我必须创建一个通用存储库IRepository,它由所有存储库ProductRepository、EmployeeRepository实现 让我困惑的是,不同的业务对象有不同的需求。例如: interface IRepository { .... } ProductRepository : IRepository
IRepository
,它由所有存储库ProductRepository、EmployeeRepository
实现
让我困惑的是,不同的业务对象有不同的需求。例如:
interface IRepository
{
....
}
ProductRepository : IRepository
{
....
}
EmployeeRepository : IRepository
{
....
}
ProductRepository
GetAllProducts ();
GetProductById (int id);
GetProductByMaxPrice (double price);
GetProductByNamePrice (string name, double Price);
Get... (...);
GetEmployeeByAge ();
GetEmployeeByJob (string description);
GetEmployeeBySalary (double salary);
Get... (...); //and so on
雇员安置所
GetAllProducts ();
GetProductById (int id);
GetProductByMaxPrice (double price);
GetProductByNamePrice (string name, double Price);
Get... (...);
GetEmployeeByAge ();
GetEmployeeByJob (string description);
GetEmployeeBySalary (double salary);
Get... (...); //and so on
如何创建满足不同对象不同数据访问要求的通用存储库
我已经阅读了很多关于存储库模式的理论,但如果能提供一个工作示例,我将不胜感激
此外,如果我可以使用通用存储库创建所有存储库,那么使用factory模式也会变得很容易。例如:
interface IRepository
{
....
}
ProductRepository : IRepository
{
....
}
EmployeeRepository : IRepository
{
....
}
然后,我们可以有效地使用工厂模式,如下所示:
IRepository repository;
repository = new ProductRepository ();
repository.Call_Product_Methods ();
repository = new EmployeeRepository ();
repository.Call_Employee_Methods ();
是的,您可以基于一个通用的、恒定的存储库接口轻松地编写一个优雅的DAL层 不过,它很可能会有一个糟糕得可笑的表现 在一个完美的世界中,任何信息都可以从数据库中检索而不需要任何成本,一个简单的通用存储库就足够了。不幸的是,事实并非如此——对于我们知道数据库可以处理的每个查询操作,最好有特定的查询方法,而不是有一个通用存储库,它使用通用查询方法,允许来自业务层的各种疯狂查询 编辑 我相信您在一个特定点上似乎是错误的:避免使用通用的ORM映射库意味着您没有使用ORM。这不一定是真的 除非您向UI公开通用的类似数组的对象(这也会使关于存储库模式的讨论完全无用),否则您就是在将关系数据转换为域对象。这正是ORM的意义所在:您没有使用NHibernate、EF或LINQ to SQL,这意味着您将有更多的工作要做。:-) 因此,不管是否使用自动ORM工具,使用仍然是有意义的
当然,还有其他的选择,比如。这是一个更简单的模式,它将域对象与数据访问逻辑混合在一起(这里使用ORM工具也是可选的)。一般来说,在我看来,通用存储库“基础”接口并不能真正解决那么多问题。有人提到,理论上,它可以提供一个get属性,该属性接受一个整数并返回一条记录。是的,这是好的和方便的-并且取决于您的用例,甚至可能是可取的 我个人划定的界限是
插入
,更新
,和删除
方法。除了最简单的情况外,我们应该确定我们在做什么。是的,创建新的供应商
可能仅仅意味着调用插入操作。但大多数非琐碎的情况下,你会做其他的事情
因此,在设计存储库时,我认为最好确定您将要执行的操作,并将方法准确命名为:
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
我们现在正在定义我们对数据所做的事情。通用的Insert、Update和Delete方法不会推断我们的存储库的使用情况,并且可能会导致开发人员误用,因为他们不知道当我们实际去做某件事情时,还需要其他辅助的事情
那么,基础存储库的一个好例子是什么呢?那么,实现缓存的存储库呢?基本存储库可以有某种缓存,我们的派生存储库可以根据需要使用该缓存返回过时数据
当我们需要回答将要返回的内容时,即使是this[int]
default属性也有复杂的问题。如果它是一个有很多引用的大对象,我们将返回整个对象及其所有部分,还是返回一个非常简单的POCO,需要进一步查询来填补空白。通用的这个[int]
不能回答这个问题,但是:
GetBareBonesClient(int id);
GetClientAndProductDetail(int id);
GetClientAndContracts(int id);
在我看来,它们的定义相当明确。在intellisense的今天,针对您的存储库进行编码的开发人员将知道他/她需要调用什么才能得到他们想要的。您如何确定这些方法中有多少种?好吧,你看看你正在开发的产品。您有哪些案例可以获取数据。。。谁在获取数据,他们为什么要获取数据?大多数时候,这些问题都很容易回答
然而,一个常见的问题是,我们希望允许用户以表格形式“浏览”数据。“给我‘x’个记录,按‘x’字段排序,以分页方式……哦,我可能会也可能不会在某个列上包含某种搜索。”。这种代码是您确实不希望为每个存储库实现的。因此,在支持SPAGINATION的假设的i库中,可能存在一些锅炉板查询构造的情况。我相信你能想出一个更好的名字
显然,很可能会有更多的案例。但是我决不会把默认的CRUD
操作扔到基本存储库接口/类中,因为除了不重要的、琐碎的情况外,它没有任何意义。存储库模式是一个很好的使用模式,但是如果没有正确地完成,而不是让您的生活更轻松,那将是一个巨大的痛苦
因此,实现这一点的最佳方法(因为您不想使用EF或其他ORM)是创建一个通用接口,然后创建一个基本抽象实现。这样,您不需要对每个存储库进行编码,只需
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);
}
} ...
IEnumerable<Employee> GetEmployee(int age)
{
return rep.GetAll<Employee>(e=> e.Age == age);
}
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();
}
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;
}
}
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 };
}
}
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 };
}
}
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; } }
}
public interface IEmployeeQuery {
IEmployeeQuery ByLastName(string lastName);
IEmployeeQuery ByFirstName(string firstName);
IEmployeeQuery ByDepartment(string department);
IEmployeeQuery ByJoinDate(Datetime joinDate);
}
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);
}
}
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;
}
}
}