简体   繁体   English

在ASP.NET Core中使用存储库模式中的接口和抽象类

[英]Using interface and abstract class in repository pattern in ASP.NET Core

We are currently using dapper ORM to access data by calling store procedures. 我们目前正在使用dapper ORM通过调用存储过程来访问数据。 The current code is having a class BusinessFunctions which inherits another class DataFunctions which are having helper methods to execute the stored procedures. 当前代码有一个类BusinessFunctions,它继承了另一个类DataFunctions,它们有辅助方法来执行存储过程。

I am not happy with this code. 我对这段代码不满意。 It's just too rigid and not future proof. 它过于僵化而不是未来的证据。 And above all it's not coded to an interface rather coded to an implementation. 最重要的是,它没有编码到接口而是编码为实现。 I propose an interface IRepository with an abstract class Repository which implements all helper generic methods. 我提出了一个带有抽象类Repository的接口IRepository,它实现了所有helper泛型方法。 Then I create BusinessRepository that implements the abstract Repository class and call the generic helpers method. 然后我创建实现抽象Repository类的BusinessRepository并调用泛型helpers方法。 Again, my colleague is telling me to remove the IRepository interface and just use the Repository abstract class. 同样,我的同事告诉我删除IRepository接口并只使用Repository抽象类。

public class BusinessFunctions : DataFunctions
{
    public BusinessFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser) : base(conMgr, logWriter, appUser)
    {

    }

    public async Task<Business> FindAsync(int businessId)
    {
        throw new NotImplementedException();
    }

    public async Task<Business> FindAsync(string businessGuid)
    {
        var lst = await StoredProcQueryAsync<Business>("spBusinessGetSetupGUID", new { BusinessGuid = businessGuid });

        if (lst.Count() == 0)
            throw new NotFoundInDatabaseException("Business", businessGuid);
        else
            return lst.Single();
    }

    public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
    {
        var b = await FindAsync(businessGuid);
        if (b.HostedPaymentEnabled)
            return true;
        else
            return false;
    }
}



 public class DataFunctions : IDisposable
{
    private ConnectionManager _conMgr;
    private LogWriter _logWriter;
    private AppUser _appUser;

    public ConnectionManager ConnMgr
    {
        get { return _conMgr; }
    }

    protected LogWriter Logger
    {
        get { return _logWriter; }
    }

    protected AppUser User
    {
        get { return _appUser; }
    }

    public DataFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser)
    {
        _conMgr = conMgr;
        _logWriter = logWriter;
        _appUser = appUser;
    }

    public void Dispose()
    {

    }

    public async Task StoredProcExecuteNonQueryAsync(string storedProc,
        List<StoredProcParameter> storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite
        )
    {
        using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
        {
            await conn.OpenAsync();
            await StoredProcExecuteNonQueryAsync(conn,
                storedProc, 
                storedProcParameters: storedProcParameters, 
                commandTimeout: commandTimeout,
                accessType: accessType);
        }
    }

    public async Task StoredProcExecuteNonQueryAsync(SqlConnection conn, 
        string storedProc,
        List<StoredProcParameter> storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite,
        SqlTransaction trans = null
        )
    {
        using (SqlCommand cmd = new SqlCommand(storedProc, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandTimeout = (int)commandTimeout;
            if (trans != null) cmd.Transaction = trans;
            if (storedProcParameters != null)
            {
                foreach(var p in storedProcParameters)
                {
                    cmd.Parameters.Add(p.ToSqlParameter());
                }
            }
            await cmd.ExecuteNonQueryAsync();
        }
    }

    public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(string storedProc,
        object storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite)
    {
        using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
        {
            conn.Open();
            return await StoredProcQueryAsync<T>(conn,
                storedProc,
                storedProcParameters,
                commandTimeout);
        }
    }

    public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(SqlConnection conn,
        string storedProc,
        object storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default)
    {
        return await conn.QueryAsync<T>(storedProc,
                commandType: CommandType.StoredProcedure,
                commandTimeout: (int)commandTimeout,
                param: storedProcParameters);


    }
}

I think the reason you're unhappy with the code is that it seems to be intermingling service functionality into the repository layer. 我认为您对代码不满意的原因是它似乎将服务功能混合到存储库层。 The repository layer should simply call the stored procedure. 存储库层应该只调用存储过程。

public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
{
    var b = await FindAsync(businessGuid);
    if (b.HostedPaymentEnabled)
        return true;
    else
        return false;
}

This for example is a good candidate to be in the service layer. 例如,这是服务层中的一个很好的候选者。

Your repo layer should really just have your ConnectionManager or a Connection factory injected via IoC. 您的repo层应该只通过IoC注入ConnectionManager或Connection工厂。

The trick we use is to put an attribute on data model fields that we know are going to be stored procedure parameters (usually most or all). 我们使用的技巧是在数据模型字段上放置一个属性,我们知道它们将是存储过程参数(通常是大多数或全部)。 Then we have an extension method that reflects over the attributes and pulls the fields, values, and types creating a dapper DynamicParameters object. 然后我们有一个扩展方法,它反映了属性并提取了创建一个精巧的DynamicParameters对象的字段,值和类型。 Most of our repository calls look like this: 我们的大多数存储库调用如下所示:

 public async Task<User> AddUserAsync(UserAdd user) 
 {
    using (var connection = _connectionFactory.Create() 
      {
         var result = connection.ExecuteAsync("dbo.AddUser", user.GetParameters(), commandType: CommandType.StoredProcedure";
         return result;
      }
  }

It's relatively quick and easy to use. 它相对快速且易于使用。 Gets are very easy to test. 获取非常容易测试。 Inserts/Deletes/Updates not so much. 插入/删除/更新不是那么多。 You get into needing to mock the SqlConnection which can be problematic. 您需要模拟SqlConnection,这可能会有问题。

In addition, if you get into more complex areas that are subject to change, you can use the strategy pattern to move methods into their own classes. 此外,如果您进入可能发生变化的更复杂区域,您可以使用策略模式将方法移动到自己的类中。 Below would be an example of splitting your add method into its own class: 下面是将add方法拆分为自己的类的示例:

 public class MyRepository
 {
    private readonly IAddMethod<UserAdd> _addMethod;
    private readonly IConnectionFactory _connectionFactory;

    public MyRepository(IAddMethod<UserAdd> userAddMethod, 
      IConnectionFactory connectionFactory) 
    {
       //..guard clauses, assignments, etc.
    }

    public async Task<int> AddAsync(UserAdd user)
    {
        return _addMethod.AddAsync(user);
    }
 }

You can even decorate these strategy methods in IoC to hide/augment them as needed. 您甚至可以在IoC中修饰这些策略方法,以根据需要隐藏/扩充它们。 (in structuremap it's .DecorateAllWith() (在结构图中,它是.DecorateAllWith()

In short, move any logic to the service layer, consider a generic extension method for creating your DynamicParameters list, and inject the connection factory via IoC. 简而言之,将任何逻辑移动到服务层,考虑用于创建DynamicParameters列表的通用扩展方法,并通过IoC注入连接工厂。 I think you'll find the separation of concerns will simplify things significantly. 我想你会发现关注点的分离会大大简化事情。

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

相关问题 如何在 ASP.NET 核心中使用存储库模式进行排序? - How to sort using repository pattern in ASP.NET core? 为什么 ASP.NET Core 的 Startup 类不是接口或抽象类? - Why is ASP.NET Core's Startup class not an interface or abstract class? 在ASP.NET MVC存储库模式中,每个类都需要具有一个接口吗? - In ASP.NET MVC repository pattern, does every class needs to have an interface? 在ASP.NET MVC 5中使用具有通用存储库模式UnitOfWork的继承接口的方法 - Using Method of Inherited Interface with Generic Repository Pattern, UnitOfWork in ASP.NET MVC 5 Asp.Net Core 通用存储库模式软删除 - Asp.Net Core Generic Repository Pattern Soft Delete ASP.NET CORE 3.1 中通过存储库模式的多对多关系 - Many to Many relationship in ASP.NET CORE 3.1 by repository pattern 用于发布实体的 Asp.Net 核心存储库和工作单元模式 - Asp.Net core Repository and Unit Of Work Pattern for Posting an entity 实体框架和 ASP.NET Core UOW 存储库模式 - Entity Framework and ASP.NET Core UOW Repository pattern ASP.Net核心MVC存储库模式意外处置 - ASP.Net Core MVC Repository Pattern Unexpectedly disposing Asp.net核心通用存储库模式,继承不起作用 - Asp.net core generic repository pattern with inheritance not working
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM