在C#中使用泛型在ADO.NET中实现存储库模式和依赖注入






4.96/5 (14投票s)
在ADO.NET中使用泛型实现存储库模式
如今,我正在尝试学习面向对象范式中不同的设计模式,这些模式对于为不同的场景实现通用解决方案非常有用。几周前,为了找工作,我接到了一项任务,要做一个与数据库交互的Web应用程序,所以我把它作为一个挑战,并决定使用适用于该场景的设计模式来实现松耦合。
其中一个在我的作业中实现的是使用泛型的存储库模式,并使用依赖注入,通过构造函数注入存储库类的依赖项。
我创建了一个通用类,该类将由应用程序中不同表的其他类型继承。在这个类中,我使用了不同的框架特性,如反射和泛型。
我的通用类是一个abstract
类,因此需要被继承才能使用它。您将在后面看到我们将如何使用它。
这是Repository
类
public abstract class Repository<tentity> where TEntity : new()
{
DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
protected DbContext Context
{
get
{
return this._context;
}
}
protected IEnumerable<tentity> ToList(IDbCommand command)
{
using (var record = command.ExecuteReader())
{
List<tentity> items = new List<tentity>();
while (record.Read())
{
items.Add(Map<tentity>(record));
}
return items;
}
}
protected TEntity Map<tentity>(IDataRecord record)
{
var objT = Activator.CreateInstance<tentity>();
foreach (var property in typeof(TEntity).GetProperties())
{
if (record.HasColumn(property.Name) && !record.IsDBNull(record.GetOrdinal(property.Name)))
property.SetValue(objT, record[property.Name]);
}
return objT;
}
}
现在,我在数据库中有表User
,其模式是
CREATE TABLE [dbo].[tblUser] ( [UserID] INT IDENTITY (1, 1) NOT NULL, [FirstName] NVARCHAR (25) NULL, [LastName] NVARCHAR (25) NULL, [UserName] NVARCHAR (25) NULL, [Password] NVARCHAR (25) NULL, [IsActive] BIT NULL, [IsDeleted] BIT NULL, [CreatedBy] INT NULL, [CreatedAt] DATETIME NULL, [UpdatedBy] INT NULL, [UpdatedAt] DATETIME NULL, [Email] NVARCHAR (50) NULL, PRIMARY KEY CLUSTERED ([UserID] ASC) );
针对此表,我有一个Model
类用于从表映射到该类型,它看起来像
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public bool IsActive { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedAt { get; set; }
public int CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; }
public int UpdatedBy { get; set; }
public string Email { get; set; }
}
我们希望从User
表中获取数据,为此我们将为User
类型创建一个Repository
类,然后我们将编写实现以从数据库中的User
表中获取记录。所有需要获取数据、插入数据、更新数据或删除User
表中的数据的我们的方法都将驻留在UserRepository
类中。
这是UserRepository
类的实现
public class UserRepository : Repository
{
private DbContext _context;
public UserRepository(DbContext context)
: base(context)
{
_context = context;
}
public IList GetUsers()
{
using (var command = _context.CreateCommand())
{
command.CommandText = "exec [dbo].[uspGetUsers]";
return this.ToList(command).ToList();
}
}
public User CreateUser(User user)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspSignUp";
command.Parameters.Add(command.CreateParameter("@pFirstName", user.FirstName));
command.Parameters.Add(command.CreateParameter("@pLastName", user.LastName));
command.Parameters.Add(command.CreateParameter("@pUserName", user.UserName));
command.Parameters.Add(command.CreateParameter("@pPassword", user.Password));
command.Parameters.Add(command.CreateParameter("@pEmail", user.Email));
return this.ToList(command).FirstOrDefault();
}
}
public User LoginUser(string id, string password)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspSignIn";
command.Parameters.Add(command.CreateParameter("@pId", id));
command.Parameters.Add(command.CreateParameter("@pPassword", password));
return this.ToList(command).FirstOrDefault();
}
}
public User GetUserByUsernameOrEmail(string username, string email)
{
using (var command = _context.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "uspGetUserByUsernameOrEmail";
command.Parameters.Add(command.CreateParameter("@pUsername", username));
command.Parameters.Add(command.CreateParameter("@pEmail", email));
return this.ToList(command).FirstOrDefault();
}
}
}
我们现在已经完成了UserRepository
,我添加了方法并编写了一个存储过程来完成这项作业。现在,我将告诉您如何在服务层或业务规则中使用它来执行操作。
首先,创建一个名为IUserService
的接口
[ServiceContract]
public interface IUserService
{
[OperationContract]
IList GetUsers();
[OperationContract]
User RegisterUser(User user);
[OperationContract]
User Login(string id, string password);
[OperationContract]
bool UserNameExists(string username, string email);
}
这是我的用于User的WCF服务,它调用UserRepository
来执行操作
public class UserService : IUserService
{
private IConnectionFactory connectionFactory;
public IList<user> GetUsers()
{
connectionFactory = ConnectionHelper.GetConnection();
var context = new DbContext(connectionFactory);
var userRep = new UserRepository(context);
return userRep.GetUsers();
}
public User RegisterUser(User user)
{
connectionFactory = ConnectionHelper.GetConnection();
var context = new DbContext(connectionFactory);
var userRep = new UserRepository(context);
return userRep.CreateUser(user);
}
public User Login(string id, string password)
{
connectionFactory = ConnectionHelper.GetConnection();
var context = new DbContext(connectionFactory);
var userRep = new UserRepository(context);
return userRep.LoginUser(id, password);
}
public bool UserNameExists(string username, string email)
{
connectionFactory = ConnectionHelper.GetConnection();
var context = new DbContext(connectionFactory);
var userRep = new UserRepository(context);
var user = userRep.GetUserByUsernameOrEmail(username, email);
return !(user != null && user.UserID > 0);
}
}
您可以看到,在创建UserRepository
的实例时,我通过构造函数注入数据库上下文,然后根据需要从userRepository
中调用不同的方法。
这是DbContext的实现
public class DbContext { private readonly IDbConnection _connection; private readonly IConnectionFactory _connectionFactory; private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private readonly LinkedList<AdoNetUnitOfWork> _uows = new LinkedList<AdoNetUnitOfWork>(); public DbContext(IConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; _connection = _connectionFactory.Create(); } public IUnitOfWork CreateUnitOfWork() { var transaction = _connection.BeginTransaction(); var uow = new AdoNetUnitOfWork(transaction, RemoveTransaction, RemoveTransaction); _rwLock.EnterWriteLock(); _uows.AddLast(uow); _rwLock.ExitWriteLock(); return uow; } public IDbCommand CreateCommand() { var cmd = _connection.CreateCommand(); _rwLock.EnterReadLock(); if (_uows.Count > 0) cmd.Transaction = _uows.First.Value.Transaction; _rwLock.ExitReadLock(); return cmd; } private void RemoveTransaction(AdoNetUnitOfWork obj) { _rwLock.EnterWriteLock(); _uows.Remove(obj); _rwLock.ExitWriteLock(); } public void Dispose() { _connection.Dispose(); } }
ConnectionFactory的实现是
public class DbConnectionFactory : IConnectionFactory { private readonly DbProviderFactory _provider; private readonly string _connectionString; private readonly string _name; public DbConnectionFactory(string connectionName) { if (connectionName == null) throw new ArgumentNullException("connectionName"); var conStr = ConfigurationManager.ConnectionStrings[connectionName]; if (conStr == null) throw new ConfigurationErrorsException(string.Format("Failed to find connection string named '{0}' in app/web.config.", connectionName)); _name = conStr.ProviderName; _provider = DbProviderFactories.GetFactory(conStr.ProviderName); _connectionString = conStr.ConnectionString; } public IDbConnection Create() { var connection = _provider.CreateConnection(); if (connection == null) throw new ConfigurationErrorsException(string.Format("Failed to create a connection using the connection string named '{0}' in app/web.config.", _name)); connection.ConnectionString = _connectionString; connection.Open(); return connection; } }
UnitOfWork的实现
public class AdoNetUnitOfWork : IUnitOfWork { private IDbTransaction _transaction; private readonly Action<AdoNetUnitOfWork> _rolledBack; private readonly Action<AdoNetUnitOfWork> _committed; public AdoNetUnitOfWork(IDbTransaction transaction, Action<AdoNetUnitOfWork> rolledBack, Action<AdoNetUnitOfWork> committed) { Transaction = transaction; _transaction = transaction; _rolledBack = rolledBack; _committed = committed; } public IDbTransaction Transaction { get; private set; } public void Dispose() { if (_transaction == null) return; _transaction.Rollback(); _transaction.Dispose(); _rolledBack(this); _transaction = null; } public void SaveChanges() { if (_transaction == null) throw new InvalidOperationException("May not call save changes twice."); _transaction.Commit(); _committed(this); _transaction = null; } }
IConnectionFacotry和IUnitOfWork的接口看起来像
public interface IUnitOfWork { void Dispose(); void SaveChanges(); }
public interface IConnectionFactory { IDbConnection Create(); }
现在将来,当我在数据库中添加另一个表时,我将创建另一个Repository
类型并实现其数据访问逻辑,并以相同的方式调用它,因此应用依赖注入和存储库模式,我们在某种程度上遵循DRY原则,但我相信我们可以做得更好。