使用Dapper的通用仓储库

11

我正在尝试使用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中这样调用我的Delete方法:

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"扩展方法。


当你遇到错误并询问有关该错误的问题时,您需要包含该错误。在运行时发生的错误称为“异常”(这是在 .net 中错误的表现方式)。包括“消息”,“类型”,“堆栈跟踪”,并递归地重复此过程,直到最后一个“内部异常”。使用问题上的编辑链接来包含该详细信息,请勿将其作为评论包含。请还阅读如何提出好问题 - Igor
@Igor:这不是运行时错误。请检查我的更新问题。 - Bryan
你计划使用的所有类型都有一个名为 Id 的公共属性,其类型为 int 吗? - Igor
是的,该属性存在。 - Bryan
@drizin发布了一个库,它真正有助于这种所需的方法。https://dev59.com/u1gR5IYBdhLWcg3wjNtN#65175483 - Slmmgdd
4个回答

7

定义一个接口,如下所示。

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中执行此查询,而不是在通用存储库中执行? - Bryan
@Bryan - 如果您对实体有特定的操作,而这些操作不是通用的,那么它们应该包含在派生存储库类型中,如 UserRepository。只有常见操作可以在 GenericRepository 中定义。 - Igor
谢谢。我的User类必须实现您提供的接口吗?User类是实体。 - Bryan
@Bryan - 正确(请参见添加的行)。您使用的类型必须符合GenericRepository上的约束。 - Igor

5

请不要这样做!您的通用存储库比带来更多困惑而非价值。它是脆弱的代码(_tableName使用字符串字面量,id参数存在无效转换错误),并引入了一个巨大的安全漏洞(通过_tableName进行SQL注入)。如果您选择了Dapper,则是因为您想控制自己的SQL,所以生成您发送到Dapper的SQL没有任何意义。


1
如果有帮助的话,我刚刚发布了一个库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();
}

完整的文档在这里

1
谢谢@drizin,你的库对我非常有帮助。 - Slmmgdd

1
你需要定义一个如下的接口。
public interface IIdentityEntity
{
  public int Id { get; set;}
}

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

第一行应更改为以下内容:

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

问题在于您只将TEntity描述为类,而类的描述中没有Id,因此您需要通知编译器泛型类型实现了包含Id字段的接口。

那么我的User类必须实现这个接口吗? - Bryan
所有想要使用通用删除的类必须实现该接口。 - Khatibzadeh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接