DapperUnitOfWorkLegacyDbf

2025-12-10 0 435

DapperUnitOfWorkLegacyDbf

This application demonstrates how to perform atomic create, update and delete operations on typical Customer and Customer Transaction tables in a one-to-many relationship, using the Dapper micro-ORM and FluentMap and a Repository\\Unit Of Work pattern. The database is comprised of DBF files and is accessed using the Visual FoxPro OleDb driver. There are no rules, triggers or similar implemented at database level.

By way of usage examples some integration tests using XUnit are provided in the \’Tests\’ project, and there is also a simple console application in the \’SimpleExample\’ project.

This is based on the approach detailed by Ian Rufus in this blog entry.

Important!
This application requires the Microsoft Visual FoxPro 9.0 OleDb Provider. This is a 32-bit only provider, there is no 64-bit version. As a result this application must be compiled for x86 only.

Dapper And FluentMap

Dapper

Dapper is a popular micro-ORM \\ object mapper that extends IDbConnection with convenience methods that return database results mapped to entity types.

Here\’s a DBF-format database table:

   Field   Field Name                  Type                                  Width
       1   CU_CODE                     Character                                10
       2   CU_NAME                     Character                                50
       3   CU_ADDR1                    Character                                50
       4   CU_ADDR2                    Character                                50
       5   CU_POSTCODE                 Character                                10
       6   CU_BALANCE                  Numeric                                  12

and here\’s the C# entity representing it.

public class Customer
{
    [Key]
    public string Code { get; set; } = string.Empty;

    public string Name { get; set; } = string.Empty;

    public string Address1 { get; set; } = string.Empty;

    public string? Address2 { get; set; } = string.Empty;

    public string? Postcode { get; set; }

    /// <summary>
    /// Gets or sets the customer balance. Not writable. It can only
    /// be updated by inserting, deleting or updating a
    /// transaction or transactions for this customer.
    /// </summary>
    [Write(false)]
    public float Balance { get; set; }

    public override string ToString()
    {
        return $\"{Code} {Name}\";
    }
}

Dapper then provides the ability to do things like:

    public Customer GetByCode(string code)
    {
        var cmd = @\"select cu_code, cu_name, cu_addr1, cu_addr2, cu_postcode, cu_balance \";
        cmd += \"from Customers where cu_code = ?\";
        return _connection.QueryFirstOrDefault<Customer>(cmd, param: new { c = code }, transaction);
    }

Note the way that query parameters are implemented – OleDB does not support named parameters, only positional parameters. So where multiple parameters are used the order is vital:

    public void Update(Customer customer)
    {
        var cmd = @\"update Customers set cu_name=?, cu_addr1=?, cu_addr2=?, cu_postcode=? where cu_code=?\";
        _connection.ExecuteScalar(cmd, param: new
        {
            n = customer.Name,
            add1 = customer.Address1,
            add2 = customer.Address2,
            pc = customer.Postcode,
            acc = customer.Code,
        },
        transaction);
    }

FluentMap

FluentMap is a Dapper extension allowing the mapping between C# entity properties and the associated database table fields to be explicitly declared.

public class CustomerEntityMap : EntityMap<Customer>
{
    public CustomerEntityMap()
    {
        Map(c => c.Code).ToColumn(\"cu_code\", caseSensitive: false);
        Map(c => c.Name).ToColumn(\"cu_name\", caseSensitive: false);
        Map(c => c.Address1).ToColumn(\"cu_addr1\", caseSensitive: false);
        Map(c => c.Address2).ToColumn(\"cu_addr2\", caseSensitive: false);
        Map(c => c.Postcode).ToColumn(\"cu_postcode\", caseSensitive: false);
        Map(c => c.Balance).ToColumn(\"cu_balance\", caseSensitive: false);
    }
}

The Repository And Unit Of Work Implementations

The Unit Of Work pattern allows database create, update and delete operations to be performed or rolled back as a single transaction, enabling database \’atomicity\’ where all updates occur, or none occur.

A repository pattern isolates database operations from the user interface and allows database operations to be performed by adding, updating or deleting items from a collection of objects.

Repositories

There are two repository classes in the application, CustomerRepositoty and CustomerTransactionRepository. Each is passed a parameter of type IDbConnection through the constructor. The database connection to use is then retrieved from that parameter:

private IDbConnection _connection { get => databaseTransaction.Connection!; }

Note the null-forgiving \’!\’ operator. This dependency injection of course makes the class database provider independent. The class then contains various methods for the database CRUD operations, such as the following method that will return a List of Customer objects:

 public List<Customer> GetAll()
 {
     var cmd = @\"select cu_code, cu_name, cu_addr1, cu_addr2, cu_postcode, cu_balance \";
     cmd += \"from Customers \";
     return _connection.Query<Customer>(cmd, transaction: transaction).ToList();
 }

Unit Of Work

In this application the unit of work is represented by the DapperUnitOfWork class. This is a class implementing IDisposable. It has instances of both types of repository. The constructor takes the connection string as a parameter and configures the FluentMap mapping if it has not been already. It then opens an OleDb connection and starts a new transaction.

public class DapperUnitOfWork : IDisposable
{
    private readonly IDbConnection databaseConnection;
    private IDbTransaction databaseTransaction;

    /// <summary>
    /// Initializes a new instance of the <see cref=\"DapperUnitOfWork\"/> class.
    /// Sets up the unit of work and configures the FluentMap mappings. 
    /// Opens the OleDb connection.
    /// </summary>
    /// <param name=\"connString\">The OleDb connection string.</param>
    public DapperUnitOfWork(string connString)
    {
        ConnectionString = connString;

        if (!FluentMapper.EntityMaps.Any(m => m.Key == typeof(Entities.Customer)))
        {
            FluentMapper.Initialize(config =>
            {
                config.AddMap(new CustomerEntityMap());
                config.AddMap(new CustomerTransactionEntityMap());
            });
        }

        databaseConnection = new OleDbConnection(ConnectionString);
        databaseConnection.Open();

        // Some default setup items for the connection.
        // \'Set null off\' - any inserts will insert the relevant empty value for the database field type instead of a null
        // where a value is not supplied.
        // \'set exclusive off\' - tables will be opened in shared mode.
        // \'set deleted on\' - unintuitively this means that table rows marked as deleted will be ignored in SELECTs.
        var cmd = $\"set null off{Environment.NewLine}set exclusive off{Environment.NewLine}set deleted on{Environment.NewLine}\";
        databaseConnection.Execute(cmd);

        databaseTransaction = databaseConnection.BeginTransaction();
    }

The repository properties on the class get the database transaction injected in their getter, the code below will either return an existing repository or create a new one as required:

 public CustomerRepository? CustomerRepository
 {
     get
     {
         return customerRepository ??= new CustomerRepository(dbTransaction);
     }
 }

The Commit() method on the class attempts to commit the current transaction. Any exception will cause a rollback, and the exception will be thrown up. There is also a Rollback() method that can be used to explicitly roll the transaction back. In all eventualities the current transaction will be disposed and a new one created, and the repository members reset.

 public void Commit()
 {
     try
     {
         databaseTransaction.Commit();
     }
     catch
     {
         databaseTransaction.Rollback();
         throw;
     }
     finally
     {
         databaseTransaction.Dispose();
         databaseTransaction = databaseConnection.BeginTransaction();
         ResetRepositories();
     }
 }

Because both the Customer and CustomerTransaction repository objects are using the same transaction, the commit or rollback are atomic and represent one unit of work.

Both Commit() and Rollback() methods will explicitly call the Dispose() methof the class. This method takes care of disposing the current transaction and connection, and resetting the repository members.

 public void Dispose()
 {
     dbTransaction?.Dispose();
     dbConnection?.Dispose();
     GC.SuppressFinalize(this);
 }

Important
Always disposing the transaction and connection when finished are extremely important in a file-based database such as DBF. Any file handles left open on the disk file can cause issues for other applications./

This is a simple example – the unit of work class here is kind of a \’god object\’ since it always has to contain an instance of each type of repository class. So it is a candidate for further abstraction.

下载源码

通过命令行克隆项目:

git clone https://github.com/AlanPBourke/DapperUnitOfWorkLegacyDbf.git

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 DapperUnitOfWorkLegacyDbf https://www.zuozi.net/33422.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务