[根据 MikeSW 的意见编辑] 我的意见(在此处加入 Moo-Juice)是您需要选择最适合您的实现。存储库模式是一个很好的模式(Gabriel 的回答描述了一个很好的实现),但是如果以纯粹的形式实现它可能会做很多工作。ORM 自动化了许多繁重的工作。
无论您选择哪种方法,您都需要以下组件:
您的业务接口 - 您的客户端程序员需要调用的方法,例如 GetAllEmployees(criteria)、UpdateEmployee(Employee employee) 等。如果您有客户端/服务器架构,这些将对应于带有数据合同的服务调用。
您的内部逻辑创建适当的输出以满足您的合同。这将是组成查询或执行多个数据库更新的层,例如 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。请注意交互 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 };
}
}
如您所见,这需要一个将执行操作的命令(即命令模式)。基本命令实现:
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 将真正帮助您:
- 数据映射
- 命令构建(你不需要这样做)
- 查询构建 - 您可以使用内置的 Linq-to-Sql。
总体而言,此方法使用存储库模式中的工作单元,但它使用 UpdateHelper 类基于命令模式进行更新,而不是存储库对象及其添加、更新和删除方法。这允许直接编写 SQL,而无需 ORM 映射器。
好吧,这很长,但显然没有所有细节,我的回答被认为是不值得的。我希望这有帮助。