简体   繁体   English

使用 Dapper 的存储库设计模式

[英]Repository Design Pattern with Dapper

This is maybe more a question for code review rather than stack overflow.这可能更像是代码审查而不是堆栈溢出的问题。

I am using Dapper for a MicroORM to retrieve and Save Data to SQL Server 2014. I have got DTO classes in a DTO Proj that represent the Data retrieved from the DB or saved to the DB.我在 MicroORM 中使用 Dapper 来检索数据并将其保存到 SQL Server 2014。我在 DTO Proj 中有 DTO 类,这些类表示从数据库中检索到的数据或保存到数据库中的数据。

I am using the Repository Pattern so at my Service layer if a repository is required I am using constructor DI to inject that dependency and then call the method on the Repository to do the work.我正在使用存储库模式,因此在我的服务层,如果需要存储库,我将使用构造函数 DI 注入该依赖项,然后调用存储库上的方法来完成工作。

so let say I have 2 services called CustomerService and CarService.假设我有 2 项服务,称为 CustomerService 和 CarService。

I then have 2 Repositories a CustomerRepository and a CarRepository.然后我有 2 个存储库,一个 CustomerRepository 和一个 CarRepository。

I have an interface which defines all the methods in each Repository and then the concrete implementations.我有一个接口,它定义了每个 Repository 中的所有方法,然后是具体的实现。

An example method is shown below (calling a Stored Proc to do the DB INSERT (note the actual string variable for the stored proc is defined as a private string at the top of the class):下面显示了一个示例方法(调用存储过程来执行 DB INSERT(注意存储过程的实际字符串变量被定义为类顶部的私有字符串):

    public void SaveCustomer(CustomerDTO custDTO)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            db.Execute(saveCustSp, custDTO, commandType: CommandType.StoredProcedure);
        }
    }

This all works fine but I am finding myself repeating the using block in every method in every repository.这一切都很好,但我发现自己在每个存储库的每个方法中重复使用块。 I have two real questions outlined below.我有两个真实的问题概述如下。

Is there a better approach which I could be using perhaps somehow using a BaseRepository class which every other Repository inherits from and the Base would implement the instantiation of the DB connection?有没有更好的方法可以使用,也许以某种方式使用 BaseRepository 类,每个其他 Repository 都继承自该类,而 Base 将实现数据库连接的实例化?

Would that still work ok for multiple concurrent Users on the system?对于系统上的多个并发用户,这仍然可以正常工作吗?

****UPDATE**** ****更新****

Based on Silas answer I have created the following根据 Silas 的回答,我创建了以下内容

public interface IBaseRepository
{
    void Execute(Action<IDbConnection> query);
}

public class BaseRepository: IBaseRepository
{
        public void Execute(Action<IDbConnection> query)
        {
            using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
            {
                query.Invoke(db);
            }
        }
}

However, in my repositories, I have other methods such as the below:但是,在我的存储库中,我还有其他方法,例如:

    public bool IsOnlyCarInStock(int carId, int year)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            var car = db.ExecuteScalar<int>(anotherStoredSp, new { CarID = carId, Year = year },
                                commandType: CommandType.StoredProcedure);

            return car > 0 ? true : false;
        }
    }

and

    public IEnumerable<EmployeeDTO> GetEmployeeDetails(int employeeId)
    {
        using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
        {
            return db.Query<EmployeeDTO>(anotherSp, new { EmployeeID = employeeId },
                                commandType: CommandType.StoredProcedure);
        }
    }

What is the correct way to add these to my Base repository using Generic Type T so I could return any type of DTO or any C# Native type使用通用类型 T 将这些添加到我的基本存储库的正确方法是什么,以便我可以返回任何类型的 DTO 或任何 C# 本机类型

Sure, a function to create and dispose your Connection will work great.当然,创建和处理 Connection 的函数会很好用。

protected void Execute(Action<IDbConnection> query)
{
    using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
    {
        query.Invoke(db);
    }
}

And your simplified call site:以及您的简化呼叫站点:

public void SaveCustomer(CustomerDTO custDTO)
{
    Execute(db => db.Execute(saveCustSp, custDTO, CommandType.StoredProcedure));
}

With Return Values:带返回值:

public T Get<T>(Func<IDbConnection, T> query)
{
    using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["myDB"].ConnectionString))
    {
        return query.Invoke(db); 
    }
}

In your call site, just write the logic you wish to use.在您的呼叫站点中,只需编写您希望使用的逻辑即可。

public IEnumerable<EmployeeDTO> GetEmployeeDetails(int employeeId)
{
    return Get<IEnumerable<EmployeeDTO>(db => 
        db.Query<EmployeeDTO>(anotherSp, new { EmployeeID = employeeId }, CommandType.StoredProcedure));
}

This is not directly relevant to your question.这与您的问题没有直接关系。 But I suggest you consider using DapperExtensions.但我建议您考虑使用 DapperExtensions。

Initially, I did implemented Repository pattern using Dapper.最初,我确实使用 Dapper 实现了 Repository 模式。 The drawback was that, I have to write queries all over;缺点是,我必须一遍遍地编写查询; it was very stringy.它非常粘稠。 Due to hard coded queries it was near to impossible to write generic repository.由于硬编码查询,几乎不可能编写通用存储库。

Recently, I upgraded my code to use DapperExtensions.最近,我升级了我的代码以使用 DapperExtensions。 This fixes lot many issues.这解决了很多问题。

Following is the generic repository:以下是通用存储库:

public abstract class BaseRepository<T> where T : BasePoco
{
    internal BaseRepository(IUnitOfWork unitOfWork)
    {
        dapperExtensionsProxy = new DapperExtensionsProxy(unitOfWork);
    }

    DapperExtensionsProxy dapperExtensionsProxy = null;

    protected bool Exists()
    {
        return (GetCount() == 0) ? false : true;
    }

    protected int GetCount()
    {
        var result = dapperExtensionsProxy.Count<T>(null);
        return result;
    }

    protected T GetById(Guid id)
    {
        var result = dapperExtensionsProxy.Get<T>(id);
        return result;
    }
    protected T GetById(string id)
    {
        var result = dapperExtensionsProxy.Get<T>(id);
        return result;
    }

    protected List<T> GetList()
    {
        var result = dapperExtensionsProxy.GetList<T>(null);
        return result.ToList();
    }

    protected void Insert(T poco)
    {
        var result = dapperExtensionsProxy.Insert(poco);
    }

    protected void Update(T poco)
    {
        var result = dapperExtensionsProxy.Update(poco);
    }

    protected void Delete(T poco)
    {
        var result = dapperExtensionsProxy.Delete(poco);
    }

    protected void DeleteById(Guid id)
    {
        T poco = (T)Activator.CreateInstance(typeof(T));
        poco.SetDbId(id);
        var result = dapperExtensionsProxy.Delete(poco);
    }
    protected void DeleteById(string id)
    {
        T poco = (T)Activator.CreateInstance(typeof(T));
        poco.SetDbId(id);
        var result = dapperExtensionsProxy.Delete(poco);
    }

    protected void DeleteAll()
    {
        var predicateGroup = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
        var result = dapperExtensionsProxy.Delete<T>(predicateGroup);//Send empty predicateGroup to delete all records.
    }

As you can see in above code, most of the methods are just wrapper over underlying DapperExtensionsProxy class.正如您在上面的代码中看到的,大多数方法只是对底层DapperExtensionsProxy类的包装。 DapperExtensionsProxy internally also manages UnitOfWork which you can see below. DapperExtensionsProxy内部还管理 UnitOfWork,您可以在下面看到。 These two classes can be combined without any issue.这两个类可以毫无问题地组合在一起。 I personally prefer to keep them separate.我个人更喜欢将它们分开。

You can also notice that additional methods Exists , DeleteById , and DeleteAll are implemented those are not part of DapperExtensionsProxy .您还可以注意到实现了其他方法ExistsDeleteByIdDeleteAll ,这些方法不是DapperExtensionsProxy一部分。

Method poco.SetDbId is defined in each POCO class to set its Identifier property.方法poco.SetDbId在每个 POCO 类中定义以设置其 Identifier 属性。 In my case, identifiers of POCOs may have different datatypes and names.就我而言,POCO 的标识符可能具有不同的数据类型和名称。

Following is DapperExtensionsProxy :以下是DapperExtensionsProxy

internal sealed class DapperExtensionsProxy
{
    internal DapperExtensionsProxy(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
    }

    IUnitOfWork unitOfWork = null;

    internal int Count<T>(object predicate) where T : BasePoco
    {
        var result = unitOfWork.Connection.Count<T>(predicate, unitOfWork.Transaction);
        return result;
    }

    internal T Get<T>(object id) where T : BasePoco
    {
        var result = unitOfWork.Connection.Get<T>(id, unitOfWork.Transaction);
        return result;
    }

    internal IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort = null, bool buffered = false) where T : BasePoco
    {
        var result = unitOfWork.Connection.GetList<T>(predicate, sort, unitOfWork.Transaction, null, buffered);
        return result;
    }

    internal IEnumerable<T> GetPage<T>(object predicate, int page, int resultsPerPage, IList<ISort> sort = null, bool buffered = false) where T : BasePoco
    {
        var result = unitOfWork.Connection.GetPage<T>(predicate, sort, page, resultsPerPage, unitOfWork.Transaction, null, buffered);
        return result;
    }

    internal dynamic Insert<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Insert<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal void Insert<T>(IEnumerable<T> listPoco) where T : BasePoco
    {
        unitOfWork.Connection.Insert<T>(listPoco, unitOfWork.Transaction);
    }

    internal bool Update<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Update<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal bool Delete<T>(T poco) where T : BasePoco
    {
        var result = unitOfWork.Connection.Delete<T>(poco, unitOfWork.Transaction);
        return result;
    }

    internal bool Delete<T>(object predicate) where T : BasePoco
    {
        var result = unitOfWork.Connection.Delete<T>(predicate, unitOfWork.Transaction);
        return result;
    }
}

Following is the BasePoco used above:以下是BasePoco使用的BasePoco

public abstract class BasePoco
{
    Guid pocoId = Guid.NewGuid();

    public Guid PocoId { get { return pocoId; } }

    public virtual void SetDbId(object id)
    {//Each POCO should override this method for specific implementation.
        throw new NotImplementedException("This method is not implemented by Poco.");
    }

    public override string ToString()
    {
        return PocoId + Environment.NewLine + base.ToString();
    }
}

This also uses UnitOfWork which is explained here .这也使用了此处解释的 UnitOfWork。

I know this is a very old question, but I still wanted to make a suggestion.我知道这是一个非常古老的问题,但我仍然想提出一个建议。

Dapper.SimpleRepository is a NuGet package that has already done all of the work for you of creating a Repository built on top of Dapper. Dapper.SimpleRepository是一个 NuGet 包,它已经为您完成了创建基于 Dapper 构建的存储库的所有工作。 It gives you basic CRUD methods as well as the ability to use filters, full queries, stored procs, etc. It supports both Async and Non-Async.它为您提供基本的 CRUD 方法以及使用过滤器、完整查询、存储过程等的能力。它支持异步和非异步。 And it will work with Framework, Standard, and Core.它将与框架、标准和核心一起使用。

It gives you two options.它为您提供了两种选择。 Assuming Foo is a C# class that mirrors a database table...假设Foo是一个 C# 类,它反映了一个数据库表......

Option 1: Create your repository by injecting the connection string and defining the type.选项 1:通过注入连接字符串并定义类型来创建您的存储库。

Dapper.SimpleRepository.Repository<Foo> fooRepo = new Dapper.SimpleRepository.Repository<Foo>("your connection string");

Then, basic CRUD is as simple as:那么,基本的 CRUD 就这么简单:

fooRepo.Insert(foo);    // Add a record to the database
fooRepo.Get(55);             // Get a sinlge item from the database by Id
fooRepo.Update(foo);    // Update a record in the database
fooRepo.Delete(55);          // Delete a single object from the database by Id

Option 2: Create your repository by injecting the connection string but DO NOT define the type.选项 2:通过注入连接字符串来创建您的存储库,但不要定义类型。

Dapper.SimpleRepository.Repository repo = new Dapper.SimpleRepository.Repository("your connection string");

Then your CRUD methods look like this:然后您的 CRUD 方法如下所示:

repo.Insert<Foo>(foo);    // Add a record to the database
repo.Get<Foo>(55);        // Get a sinlge item from the database by Id
repo.Update<Foo>(foo);    // Update a record in the database
repo.Delete<Foo>(55);     // Delete a single object from the database by Id

For all of the methods that go beyond basic crud (and there are many), see the GitHub page.对于超出基本 crud 的所有方法(并且有很多),请参阅GitHub页面。

(Full disclosure... I created the NuGet package.) (完全披露......我创建了 NuGet 包。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM