【问题标题】:Generic Repository with Data Access Layer具有数据访问层的通用存储库
【发布时间】:2013-05-10 11:29:57
【问题描述】:

我正在使用业务对象(员工、产品)创建一个新项目。由于限制,我没有使用 LINQ to SQL 或任何 ORM 映射器。

我必须手动编码数据访问层。我对使用“存储库模式”感兴趣。

据我了解,我必须创建一个通用存储库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

如何创建满足不同对象的不同数据访问要求的通用存储库

我已经阅读了很多关于存储库模式的理论,但我非常感谢一个工作示例。

此外,如果我可以使用通用存储库创建所有存储库,那么使用工厂模式也变得容易。例如:

interface IRepository
{
    ....
}

ProductRepository : IRepository
{
    ....
}

EmployeeRepository : IRepository
{
    ....
}

那么我们可以有效地使用工厂模式:

IRepository repository;
repository = new ProductRepository ();
repository.Call_Product_Methods ();

repository = new EmployeeRepository ();
repository.Call_Employee_Methods ();

【问题讨论】:

  • 通用存储库接口通常只有标准——插入、按 ID 获取、获取全部、删除、更新。不然怎么可能通用呢?
  • 它可能有 IQueryable Query { get; }
  • 如果你的方法不同,你就不能使用同一个IRepository。你想要的是一个 IEmployeeRepository ... 和代码。然后实现一个或多个具体的 EmployeeRepository。
  • Wiktor,我们如何使用 IQueryable Query { get; } 有效吗?
  • 谁施加了这些限制?坦率地说,禁止 ORM 是荒谬的。手工编码的数据访问是一场维护灾难。如果你想把它做好,即没有原始 SQL 的墙壁和长长的映射代码,你很快就会发现自己正在编写自己的 ORM!这将大大增加项目的交付周期。我觉得你真的应该试着在某个地方扭动一些手臂。

标签: c# oop design-patterns


【解决方案1】:

[根据 MikeSW 的意见编辑] 我的意见(在这里加入 Moo-Juice)是您需要选择最适合您的实现。存储库模式是一个很好的模式(Gabriel 的回答描述了一个很好的实现),但是如果以纯粹的形式实现它可能会做很多工作。 ORM 将许多繁重的工作自动化。

无论您选择哪种方法,您都需要以下组件:

  1. 您的业务接口 - 您的客户端程序员需要调用的方法,例如 GetAllEmployees(criteria)、UpdateEmployee(Employee employee) 等。如果您有客户端/服务器架构,这些将对应于服务调用与数据合同。

  2. 创建适当输出以满足您的合同的内部逻辑。这将是组成查询或执行多个数据库更新的层,例如 UpdateEmployee 可能必须验证员工是否存在,更新者是否有权更新,然后更新多个表,并将审计记录或记录插入审查队列.这将涉及查询和更新,并且是一个工作单元。

  3. 您的数据访问架构,由您的内部逻辑调用。这就是存储库模式的用武之地。无论您使用什么,这都需要以下内容:

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 Statementobject[] 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 映射器。

嗯,这很长,但显然没有所有细节,我的回答被认为是不值得的。我希望这会有所帮助。

【讨论】:

  • 如果您要否决我的答案,请解释一下。我已经编写了这两种类型的 DAL,并且我们确实有一个使用 ADO 的存储库实现。维护起来非常痛苦,我们将其删除并切换到使用数据集和表的通用 ADO 辅助类。如果他不想使用 ORM,你告诉我你的建议。到目前为止,我是唯一一个给他另一种选择的人。
  • 我没有投票给你,但这是我的看法,为什么你有点错误。存储库不与 ORM 耦合,它们是独立的并且具有不同的用途。如果有人不使用 ORM,并不意味着他必须编写数据集。微型 ORM 即数据映射器有很大帮助,根据应用程序的需要,手动编写 sql 可能是最佳解决方案。就我个人而言,我写 sql 比写 Linq2Sql 快得多。 ORM 实体毕竟是表,只是伪装成对象。
  • 感谢您的意见。我想我不清楚,我将对其进行更新 - 我想说的是 Moo-Juice 的意思,即不要围绕模式设计 DAL,而是围绕您的要求进行设计。如果您愿意,请选择对您有帮助的模式,但不要只使用会为您创造更多工作的模式。
  • 我添加了更多细节。 QueryEmployees 和 UpdateEmployees 函数将暴露给您的业务逻辑。请注意,大部分内容都没有经过测试,因为我是凭记忆编写的。
  • 我认为你的答案有很多差距,但不可能有所不同,因为没有某种映射(有一些 T4 替代方案),没有简单的方法将表达式映射到 SQL,但是+1 为您投入的所有时间尝试提出观点!干得好人
【解决方案2】:

是的,您可以基于通用的常量存储库接口轻松编写优雅的 DAL 层。

不过,它的性能很可能会非常糟糕。

在一个完美的世界中,任何信息都可以从数据库中检索而无需任何成本,一个简单而通用的存储库就足够了。不幸的是,事实并非如此——对于我们知道我们的数据库可以处理的每个查询操作,最好有一个特定的查询方法,而不是有一个通用的存储库,该通用的查询方法允许来自业务层的各种疯狂查询.

编辑

我相信您在一个特定点上似乎是错误的:避免使用通用 ORM 映射库意味着您没有使用 ORM。这不一定是真的。

除非您向 UI 公开类似数组的通用对象(这也将使有关存储库模式的讨论完全无用),否则您正在将关系数据转换为域对象。这正是 ORM 的意义所在:您没有使用 NHibernate、EF 或 LINQ to SQL,这意味着您将有更多的工作。 :-)

所以,不,不管是否使用自动 ORM 工具,使用 Repository Pattern 仍然有意义。

当然,还有其他选项,例如Active Record。这是一个更简单的模式,它将域对象与数据访问逻辑混合在一起(这里使用 ORM 工具也是可选的)。

【讨论】:

  • 我的目标不是创建一个完美的 DAL 存储库来处理所有类型的请求。我只是想看看在我必须编写方法来满足业务对象要求的情况下,是否可以有效地使用存储库模式。如果我错了,请纠正我,存储库模式更适合与 LINQ to SQL 或 ORM 映射器一起使用。就我而言,如果不使用存储库模式,您能否提出更好的方法?
【解决方案3】:

存储库模式是一个很好用的模式,但如果没有正确使用,不但不会让你的生活更轻松,反而会是一个巨大的痛苦!

因此,执行此操作的最佳方法(因为您不想使用 EF 或其他 ORM)是创建一个通用接口,然后创建一个基本抽象实现。这样你就不需要对每个存储库进行编码,你可以通过类型来实例化它们!

在此之后,如果您有任何特定于某些实体的特定方法,您都可以从存储库继承并覆盖或添加方法和属性作为 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()、.Delete() 方法不应向数据库发送任何内容,但它们应始终将更改发送到 IUnitOfWork(您可以在 DAL 类中实现),并且仅在您调用 . IUnitOfWork 的 Save() 方法,您会将内容保存到数据库中。

我已经使用 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);
}

或者你可以直接调用(不需要创建方法)

【讨论】:

  • 这是很好的洞察力。您提到“因此,执行此操作的最佳方法(因为您不想使用 EF 或其他 ORM)是创建一个通用接口,然后创建一个基本抽象实现。这样您就不需要对每个存储库进行编码,您可以只需按类型实例化它们!在此之后,如果您有任何特定于某些实体的特定方法,您都可以从存储库继承并根据需要覆盖或添加方法和属性”。如果问题不大,能不能举个小例子。非常感谢您的帮助。
  • 老实说,如果您真的愿意采用这种方法,请准备好学习一些新东西!因为对于不知道实体类型的手动实现(这意味着不使用任何映射器),您必须编写 T4 (.tt) 文件来为所有实体生成 CRUD 操作的存储过程,然后是您的通用类的实现将根据您定义的某些模式调用这些过程,例如:PR_ENTITYNAME_INSERT。但这不是一个很容易破解的解决方案。我以前做过这个,然后我遇到了 EF 并改变了我的生活!
  • 另一件事,如果您可以在 POCO 类中使用 Linq to SQL 的属性,那么编写基础存储库是一种更容易的方法。没有属性的问题是如何将实体映射到数据库。如果您不以任何方式映射,我将不得不编写大量代码将您的 Expression> 转换为SQL 语句。如果您可以使用属性(例如放置 [DatabaseTable] 和其他属性,那么您就成功了一半
【解决方案4】:

总的来说,在我看来,通用存储库“基础”接口并不能真正解决那么多问题。有人提到,理论上它可以提供一个获取整数并返回记录的属性。是的,这很好也很方便 - 根据您的用例,甚至可能是可取的。

我个人划清界限的是InsertUpdateDelete 方法。除了最简单的情况外,我们应该确定我们正在做什么。是的,创建一个新的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

我们现在正在用数据定义我们在做什么。通用的 Insert、Update 和 Delete 方法不会推断我们的存储库的使用情况,并且可能会导致开发人员误用,因为他们不了解在我们实际执行并某事时还需要发生哪些其他辅助事情。

那么什么是基础存储库的好例子?那么,实现缓存的存储库呢?基础存储库可以有某种缓存,如果需要,我们的派生存储库可以使用该缓存返回陈旧数据。

当我们需要回答 what 我们要返回时,即使是 this[int] 默认属性也有复杂的问题。如果它是一个有很多引用的大对象,我们是要返回整个事物及其所有部分,还是要返回一个非常简单的 POCO,需要进一步查询来填补空白。通用的this[int] 不能回答这个问题,但是:

GetBareBonesClient(int id);
GetClientAndProductDetail(int id);
GetClientAndContracts(int id);

在我看来定义得很好。在这些智能感知时代,针对您的存储库进行编码的开发人员将知道他/她需要调用什么来获取他们想要的内容。您如何确定这些方法中有多少种存在?好吧,你看看你实际开发的产品。您有哪些获取数据的案例……谁在获取数据,他们为什么要获取数据?大多数时候,这些问题都很容易回答。

然而,一个常见的问题是,当我们希望允许用户以表格形式“浏览”数据时。 “给我'x'条记录,按'x'字段排序,以分页方式......哦,我可能会或可能不会在某些列上包含某种搜索”。这种代码是您真正不想为每个存储库 实现的代码。因此,在假设的IRepositoryThatSupportsPagination 中可能存在一些样板查询构造的情况。我相信你能想到一个更好的名字。

显然,可能还有更多案例。但我永远不会将默认的CRUD 操作扔到基础存储库接口/类中,因为它意味着除了不重要、微不足道的情况之外没有任何意义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-22
    • 2021-01-21
    • 1970-01-01
    • 2012-12-15
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多