RepositoryBase - lobodava/artisan-orm GitHub Wiki

RepositoryBase is the base class for custom repositories. It uses one SqlConnection and attaches its current SqlTransaction to commands created by inherited methods. In the source being prepared for 4.1.0, connection ownership depends on the constructor: a connection-string repository owns its new connection; a repository constructed with an external transaction borrows that transaction's existing connection.

public class UserRepository : RepositoryBase
{
    public UserRepository(string connectionString) : base(connectionString) { }
}

What it gives you

  • Connection management: an owned SqlConnection starts closed. Individual read/execute operations open it when necessary and close it if they opened it. A borrowed transaction uses its already-open connection. Repository disposal disposes owned resources and only detaches borrowed resources.

  • Three SqlCommand initialization entry points — GetByCommand, RunCommand, ExecuteCommand. See SqlCommand initialization for the differences.

  • Read shortcuts — ReadTo<T>, ReadToList<T>, ReadAs<T>, ReadToArray<T>, ReadToDictionary<TKey,TValue>, ReadToTree<T>, ReadToObjectRows<T>, and the multi-result-set ReadToLists<T1, T2[, T3[, T4]]>. All of them have async siblings and most accept either params SqlParameter[] or Action<SqlCommand> for parameter setup. See Read methods.

  • Write shortcuts — Execute(...) / ExecuteAsync(...) for stored procedures or text batches with optional return value, plus BulkCopy<T> / BulkCopyAs<T> for high-throughput inserts.

  • Transactions — both manual-commit (BeginTransaction) and auto-commit (RunInTransaction), each with isolation-level overloads and async variants. See Transactions.

Lifetime

A repository instance uses one connection and mutable transaction state, so it is not safe to share across concurrent operations. Treat one repository as one unit of work:

  • ASP.NET Core / DI containers — register as Scoped and inject. The container disposes the repository at the end of the HTTP request automatically. See Using Artisan.Orm in ASP.NET Core.

  • Console apps and scripts — using var repo = new UserRepository(connStr);.

  • Background workers without a DI scope — using var scope = _scopeFactory.CreateScope(); var repo = scope.ServiceProvider.GetRequiredService<MyRepo>();.

Constructors

public RepositoryBase(string connectionString);
public RepositoryBase(SqlTransaction? transaction, string connectionString);

With a non-null transaction, the second overload uses transaction.Connection, even if the supplied connection string describes another server or database. The string remains available as ConnectionString; it does not select a new connection in this case. With a null transaction, this overload creates an owned connection just like the first constructor.

The external transaction and connection remain caller-owned. Disposing or asynchronously disposing a borrowing repository does not commit, roll back, close, or dispose either resource. The constructor rejects a completed or disposed transaction whose connection is no longer available. BeginTransaction* and RunInTransaction* reject an already attached transaction; they are not savepoint helpers. The ownership choice is made by the constructor, so assigning the public Transaction property on an independently created repository is not a substitute for this constructor.

For sharing one active transaction between repositories, see the external-transaction example. These fixes describe the upcoming 4.1.0 source contract, not a guarantee about older packages.

Public surface

public class RepositoryBase : IDisposable, IAsyncDisposable
{
    public SqlConnection?   Connection      { get; }   // nullable, disposed-aware
    public string           ConnectionString { get; }
    public SqlTransaction?  Transaction     { get; set; }

    // SqlCommand initialization
    public T          GetByCommand<T>(Func<SqlCommand, T> func);
    public Task<T>    GetByCommandAsync<T>(Func<SqlCommand, Task<T>> funcAsync);
    public void       RunCommand(Action<SqlCommand> action);
    public int        ExecuteCommand(Action<SqlCommand> action);
    // ... + Async / CancellationToken variants

    // Read families
    public T?            ReadTo<T>      (string sql, params SqlParameter[] sqlParameters);
    public IList<T>      ReadToList<T>  (string sql, params SqlParameter[] sqlParameters);
    public T[]           ReadToArray<T> (string sql, params SqlParameter[] sqlParameters);
    public ObjectRows    ReadToObjectRows<T>(...);
    public IDictionary<TKey, TValue> ReadToDictionary<TKey, TValue>(...);
    public T?            ReadToTree<T>(...) where T : class, INode<T>;
    public (IList<T1>, IList<T2>) ReadToLists<T1, T2>(...);
    // ... + ReadAs* (auto-mapping) siblings, all with Async variants

    // Write
    public int           Execute(string sql, params SqlParameter[] sqlParameters);
    public int           BulkCopy<T>     (IEnumerable<T> rows, string destinationTable, ...);
    public int           BulkCopyAs<T>   (IEnumerable<T> rows, string destinationTable, ...);

    // Transactions
    public void          BeginTransaction(Action<SqlTransaction> action);
    public void          RunInTransaction(Action<SqlTransaction> action);
    // ... + IsolationLevel and Async overloads
}

See also:

⚠️ **GitHub.com Fallback** ⚠️