【问题标题】:Generic repository with Dapper使用 Dapper 的通用存储库
【发布时间】:2017-05-03 04:10:58
【问题描述】:

我正在尝试使用 Dapper 构建一个通用存储库。但是,我在实施 CRUD 操作时遇到了一些困难。

这是来自存储库的一些代码:

 public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
    internal IDbConnection Connection
    {
        get
        {
            return new SqlConnection(ConfigurationManager.ConnectionStrings["SoundyDB"].ConnectionString);
        }
    }

    public GenericRepository(string tableName)
    {
        _tableName = tableName;
    }

    public void Delete(TEntity entity)
    {
        using (IDbConnection cn = Connection)
        {

            cn.Open();
            cn.Execute("DELETE FROM " + _tableName + " WHERE Id=@ID", new { ID = entity.Id });
        }
    }
}

如您所见,我的删除方法将 TEntity 作为参数,它是类类型的参数。

我从我的 UserRepository 中调用我的删除方法,如下所示:

public class UserRepository : GenericRepository<User>, IUserRepository
{
    private readonly IConnectionFactory _connectionFactory;

    public UserRepository(IConnectionFactory connectionFactory) : base("User")
    {
        _connectionFactory = connectionFactory;
    }

    public async Task<User> Delete(User model)
    {
        var result = await Delete(model);
        return result;
    }
}

问题是我无法在通用存储库的删除操作中写入entity.Id。我得到一个错误。那么我怎样才能轻松地实现这样的 CRUD 操作呢?

这是错误信息:

TEntity 不包含“Id”的定义,并且找不到接受“TEntity”类型参数的扩展方法“Id”

【问题讨论】:

  • 当您遇到错误并询问有关该错误的问题时,您需要包含该错误。在这种情况下,运行时发生的错误称为Exception这是错误在 .net 中的表现方式)。包括MessageTypeStackTrace,并在InnerExceptions 中一直重复此操作。使用问题上的编辑链接包含该详细信息,不要将其作为评论包含在内。另请阅读How do I ask a Good Question
  • @Igor:这不是运行时错误。检查我更新的问题。
  • 你所使用的所有类型是否都有一个名为 int 的公共属性,名为 Id
  • 是的,该属性存在。
  • @drizin 发布了一个库,它真正有助于实现这种所需的方法。 stackoverflow.com/a/65175483/11406472

标签: c# sql asp.net-mvc dapper


【解决方案1】:

像这样定义一个接口。

public interface ITypeWithId {
    int Id {get;}
}

并确保您的 User 类型实现了该接口。

现在将它作为通用约束应用于您的类。

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, ITypeWithId

如果您有存储在存储库中但没有 Id 属性的类型,则将您的删除类型约束设置为特定于方法而不是类。这将允许您仍然使用相同的存储库类型,即使类型可能会键入字符串或复合(多)键等其他内容。

public void Delete<T>(T entity) where T : class, ITypeWithId
{
    using (IDbConnection cn = Connection)
    {

        cn.Open();
        cn.Execute("DELETE FROM " + _tableName + " WHERE Id=@ID", new { ID = entity.Id });
    }
}

【讨论】:

  • 谢谢你:)。因此,如果我想通过用户名获取用户,我应该在我的 UserRepository 而不是 Generic-repository 中执行此查询?
  • @Bryan - 如果您对实体有非通用的特定操作,则应将它们包含在派生存储库类型中,例如UserRepository。您的 GenericRepository 中只能定义常用操作。
  • 谢谢。我的用户类必须实现你提供的接口吗?用户类是实体
  • @Bryan - 正确(见添加的行)。您使用的类型必须符合 GenericRepository 的约束。
【解决方案2】:

请不要这样做!您的通用存储库增加了更多的混乱而不是价值。它是脆弱的代码(_tableName 的字符串文字,id 参数上的无效转换错误),并引入了一个巨大的安全漏洞(通过 _tableName 进行 sql 注入)。如果你选择了 Dapper,那是因为你想控制你的 sql,所以生成你发送给 Dapper 的 sql 是没有意义的。

【讨论】:

    【解决方案3】:

    你必须定义一个像下面这样的接口

    public interface IIdentityEntity
    {
      public int Id { get; set;}
    }
    

    所有想要使用该类的实体都必须实现 IIdentityEntity。

    第一行应该改成下面这样

    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class,IIdentityEntity
    

    问题是您仅将 TEntity 描述为类,而类在其描述中没有 Id,因此您必须通知编译器通用类型实现了一个接口,其中包含一个 Id 字段

    【讨论】:

    • 所以我的 User-class 必须实现这个接口?
    • 所有要使用泛型删除的类都必须实现该接口
    【解决方案4】:

    如果有帮助,我刚刚发布了一个库 Harbin.DataAccess,它使用“原始”DapperDapper.FastCRUDDapperQueryBuilder 实现了通用存储库(通用存储库模式)

    • 插入/更新/删除由 Dapper FastCRUD 自动生成(类应使用键/自动增量列的属性进行修饰)
    • 支持 FastCRUD 批量更新、批量删除和异步方法。
    • 可以使用自定义查询和自定义命令扩展存储库(允许/促进 CQRS 分离)
    • 可以手动定义查询(原始 sql)或使用 Dapper FastCRUD 语法
    • 可以使用 DapperQueryBuilder 构建动态查询(动态数量的条件)
    • 有只读连接包装器和只读存储库,因此很容易使用只读副本(或多个数据库)
    • 支持 ADO.NET 事务
    • 支持模拟查询和命令

    示例插入/更新/删除(通用存储库 - 这使用 Dapper FastCRUD):

    var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
    
    // Get a IReadWriteRepository<TEntity> which offers some helpers to Query and Write our table:
    var repo = conn.GetReadWriteRepository<ContactType>();
    
    var contactType = repo.QueryAll().First();
    
    // Updating a record
    contactType.ModifiedDate = DateTime.Now;
    repo.Update(contactType);
    
    // Adding a new record
    var newContactType = new ContactType() { Name = "NewType", ModifiedDate = DateTime.Now };
    repo.Insert(newContactType);
    // FastCRUD will automatically update the auto-generated columns back (identity or guid)
    
    // Deleting a record
    repo.Delete(newContactType);
    
    [Table("ContactType", Schema = "Person")]
    public class ContactType
    {
        [Key] // if column is part of primary key
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)] // if column is auto-increment
        public int ContactTypeId { get; set; }
    
        public DateTime ModifiedDate { get; set; }
    
        public string Name { get; set; }
    }
    

    动态查询示例

    var conn = new ReadDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
    
    
    // Get a IReadRepository<TEntity> which offers some helpers to Query our table:
    var repo = conn.GetReadRepository<Person>();
    
    // Custom Query (pure Dapper)
    var people = repo.Query("SELECT * FROM Person.Person WHERE PersonType = @personType ", new { personType = "EM" } );
    
    // DapperQueryBuilder allows to dynamically append conditions using string interpolation (but injection-safe)
    
    string type = "EM"; string search = "%Sales%";
    
    var dynamicQuery = repo.QueryBuilder(); // if not specified query is initialized with "SELECT * FROM tablename"
    dynamicQuery.Where($"PersonType = {type}");
    dynamicQuery.Where($"ModifiedDate >= {DateTime.Now.AddDays(-1)} ");
    dynamicQuery.Where($"Name LIKE {search}");
    
    // Result is SELECT * FROM [Person].[Person] WHERE PersonType = @p0 AND ModifiedDate >= @p1 AND Name LIKE @p2
    var people = dynamicQuery.Query();
    

    使用继承扩展存储库(添加自定义查询和命令)

    public class PersonRepository : ReadWriteDbRepository<Person>
    {
      public PersonRepository(IReadWriteDbConnection db) : base(db)
      {
      }
      public virtual IEnumerable<Person> QueryRecentEmployees()
      {
        return this.Query("SELECT TOP 10 * FROM [Person].[Person] WHERE [PersonType]='EM' ORDER BY [ModifiedDate] DESC");
      }
      public virtual void UpdateCustomers()
      {
        this.Execute("UPDATE [Person].[Person] SET [FirstName]='Rick' WHERE [PersonType]='EM' ");
      }
    }
    
    public void Sample()
    {
      // Registers that GetReadWriteRepository<Person>() should return a derived type PersonRepository
      ReadWriteDbConnection.RegisterRepositoryType<Person, PersonRepository>();
    
      var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));  
      
      // we know exactly what subtype to expect, so we can just cast.
      var repo = (PersonRepository) conn.GetReadWriteRepository<Person>();
      
      repo.UpdateCustomers();
      var recentEmployees = repo.QueryRecentEmployees();
    }
    

    完整文档here

    【讨论】:

    • 谢谢@drizin,你的图书馆对我很有帮助
    猜你喜欢
    • 2023-03-21
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多