You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Npgsql+PostgreSQL,求ADO.NET/MVC统一事务入口方案

Absolutely! You can absolutely build a unified repository to handle all your ADO.NET database operations (including transactions) with Npgsql, eliminating that repetitive boilerplate code you're dealing with. Let's walk through a practical implementation tailored to your PostgreSQL + MVC scenario.

统一仓储与事务处理实现方案

1. 创建通用仓储基类

First, we'll encapsulate all repeated connection, command, parameter, and transaction logic into a single repository class. This will act as your single entry point for all database interactions.

using Npgsql;
using System;
using System.Collections.Generic;
using System.Data;
using System.Configuration;

public class PostgreSqlRepository : IDisposable
{
    private readonly NpgsqlConnection _connection;
    private NpgsqlTransaction _transaction;

    public PostgreSqlRepository()
    {
        // Reuse your existing connection string logic
        string connectionString = ConfigurationManager.ConnectionStrings["Central"].ConnectionString;
        _connection = new NpgsqlConnection(connectionString);
    }

    // Safely open connection (avoid duplicate opens)
    public void OpenConnection()
    {
        if (_connection.State != ConnectionState.Open)
        {
            _connection.Open();
        }
    }

    // Safely close connection
    public void CloseConnection()
    {
        if (_connection.State != ConnectionState.Closed)
        {
            _connection.Close();
        }
    }

    // Start a transaction
    public void BeginTransaction()
    {
        OpenConnection();
        _transaction = _connection.BeginTransaction();
    }

    // Commit transaction and clean up
    public void CommitTransaction()
    {
        _transaction?.Commit();
        DisposeTransaction();
    }

    // Rollback transaction on failure
    public void RollbackTransaction()
    {
        _transaction?.Rollback();
        DisposeTransaction();
    }

    private void DisposeTransaction()
    {
        _transaction?.Dispose();
        _transaction = null;
    }

    // Generic query method: maps data reader results to your model type
    public List<T> ExecuteQuery<T>(string sql, NpgsqlParameter[] parameters, Func<IDataReader, T> mapper)
    {
        var results = new List<T>();
        
        OpenConnection();
        using (var cmd = new NpgsqlCommand(sql, _connection))
        {
            // Attach transaction if one is active
            if (_transaction != null)
            {
                cmd.Transaction = _transaction;
            }

            // Add parameters if provided
            if (parameters != null && parameters.Length > 0)
            {
                cmd.Parameters.AddRange(parameters);
            }

            using (var rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                {
                    results.Add(mapper(rdr));
                }
            }
        }

        return results;
    }

    // Generic non-query method (for Insert/Update/Delete)
    public int ExecuteNonQuery(string sql, NpgsqlParameter[] parameters)
    {
        OpenConnection();
        using (var cmd = new NpgsqlCommand(sql, _connection))
        {
            if (_transaction != null)
            {
                cmd.Transaction = _transaction;
            }

            if (parameters != null && parameters.Length > 0)
            {
                cmd.Parameters.AddRange(parameters);
            }

            return cmd.ExecuteNonQuery();
        }
    }

    // Auto-clean up resources
    public void Dispose()
    {
        // Rollback uncommitted transactions automatically
        RollbackTransaction();
        CloseConnection();
        _connection.Dispose();
    }
}

2. Simplify Controller Query Logic

Now you can refactor your existing controller code to use this repository, eliminating all repetitive boilerplate. Here's how your fault stats query would look:

public ActionResult FaultStats(string reginalManagers, DateTime dtFrom, DateTime dtTo)
{
    // Use 'using' to ensure proper resource cleanup
    using (var repo = new PostgreSqlRepository())
    {
        string sql = @"SELECT xc.m_error_group_name, xc.m_error_group_id, count(*), error_duration FROM (
            SELECT extract(year from m_date) m_year, v1.m_error_group_name, v1.m_error_group_id
            -- Note: I fixed a small syntax issue in your original SQL here
            FROM your_table_name t1
            JOIN zd.t_users g ON(g.user_id = t1.pv_person_resp_id) 
            WHERE g.user_name IN(@rgnal)
            AND t1.m_date BETWEEN @fr AND @to 
            AND t1.crew_present = FALSE 
            AND t1.m_grid_loss = FALSE
            LEFT OUTER JOIN ssw_mdt.t_master_pc_alarm_pattern t3 ON (t1.m_inv_error_details = t3.pc_group_pattern)
        ) xc GROUP BY 1, 2 ORDER BY error_duration DESC";

        var parameters = new NpgsqlParameter[]
        {
            new NpgsqlParameter("@rgnal", reginalManagers),
            new NpgsqlParameter("@fr", dtFrom),
            new NpgsqlParameter("@to", dtTo)
        };

        // Map reader results to a helper class (cleaner than direct ViewModel population)
        var faultItems = repo.ExecuteQuery<FaultStatItem>(sql, parameters, rdr => new FaultStatItem
        {
            ErrorGroupName = rdr["m_error_group_name"].ToString(),
            ErrorGroupId = (int)rdr["m_error_group_id"],
            Count = (int)rdr["count"],
            ErrorDuration = (TimeSpan)rdr["error_duration"]
        });

        // Populate your ViewModel
        var flt = new FaultStatViewModel();
        flt.m_error_group_name.AddRange(faultItems.Select(item => item.ErrorGroupName));
        // Add other ViewModel properties from faultItems as needed

        return View(flt);
    }
}

// Helper class to hold single fault stat record
public class FaultStatItem
{
    public string ErrorGroupName { get; set; }
    public int ErrorGroupId { get; set; }
    public int Count { get; set; }
    public TimeSpan ErrorDuration { get; set; }
}

3. Transaction Handling Example

For operations that need to run in a single transaction (e.g., insert + update), use the repository's transaction methods:

public ActionResult PerformTransactionalAction()
{
    using (var repo = new PostgreSqlRepository())
    {
        try
        {
            repo.BeginTransaction();

            // First operation: Insert
            string insertSql = "INSERT INTO audit_log (action, created_at) VALUES (@action, @now)";
            var insertParams = new NpgsqlParameter[]
            {
                new NpgsqlParameter("@action", "User created"),
                new NpgsqlParameter("@now", DateTime.UtcNow)
            };
            repo.ExecuteNonQuery(insertSql, insertParams);

            // Second operation: Update
            string updateSql = "UPDATE users SET last_login = @now WHERE id = @userId";
            var updateParams = new NpgsqlParameter[]
            {
                new NpgsqlParameter("@now", DateTime.UtcNow),
                new NpgsqlParameter("@userId", 123)
            };
            repo.ExecuteNonQuery(updateSql, updateParams);

            // Commit if all operations succeed
            repo.CommitTransaction();
            return RedirectToAction("Success");
        }
        catch (Exception ex)
        {
            // Rollback on any error
            repo.RollbackTransaction();
            // Log error here
            return RedirectToAction("Error");
        }
    }
}

Bonus Optimization Tips

  • Dependency Injection: Instead of instantiating the repository directly in controllers, inject it using a DI container (like Unity or Autofac) to follow MVC's dependency inversion principle and simplify testing.
  • Error Handling: Add centralized exception handling in the repository to convert Npgsql-specific exceptions into application-friendly exceptions.
  • Mapping: For complex ViewModels, consider using a library like AutoMapper to simplify DataReader-to-model mapping (optional, but reduces manual code).

内容的提问来源于stack exchange,提问作者papagallo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:59:58