Neden Dapper ve Connection Factory Tasarımı?
Modern yazılım geliştirme süreçlerinde performans ve verimlilik ön planda olduğundan, veri erişim katmanında güçlü ve hafif çözümler geliştirmek oldukça önemlidir. .NET ORM ekosisteminde en popüler araçlardan biri olan Dapper, minimal bir ORM (Object-Relational Mapper) olarak hız, esneklik ve kolay kullanım sunar.
Ancak, Dapper ile çalışan projelerde bağlantı yönetimini pratik ve sürdürülebilir bir hale getirmek, aynı zamanda kod tekrarını azaltmak için kapsamlı ve dinamik bir yapı gereklidir. Bu bağlamda, Dapper ile birlikte kurgulanan Connection Factory tasarımı, yeniden kullanılabilir ve temiz bir bağlantı yönetimi sağlar.
Bu yazımda, .NET Standard tabanlı Dapper Connection Factory tasarımını adım adım ele alarak, projelerinizde veri tabanı bağlantı yönetimini nasıl kolaylaştırabileceğinizi anlatacağım.
Standard bir senaryoda Dapper’ın kullanımı
Çoklu veritabanı bağlantıları ile çalışan projelerde, aynı kod içerisinde farklı veritabanlarıyla işlem yapmak gerekebilir. Aşağıdaki örnek kodda, farklı veritabanlarına bağlantı sağlamak için her bağlantı türü için ayrı bir connection objesi oluşturulmuş. Ancak bu yaklaşım, kodun tekrarını artırdığı gibi, bakım ve genişletilebilirlik açısından da zorluklar çıkarabilir.
using Dapper;
using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
using MySql.Data.MySqlClient;
using Npgsql;
using Oracle.ManagedDataAccess.Client;
using (MySqlConnection mysqlConn = new MySqlConnection("MysqlConnectionString"))
{
var list = mysqlConn.Query<string>("SELECT ADI FROM MUSTERI");
}
using (OracleConnection oracleConn = new OracleConnection("OracleConnectionString"))
{
var list = oracleConn.Query<string>("SELECT ADI FROM MUSTERI");
}
using (NpgsqlConnection postgreSQLConn = new NpgsqlConnection("PostgreSQLConnectionString"))
{
var list = postgreSQLConn.Query<string>("SELECT ADI FROM MUSTERI");
}
using (SqliteConnection SQLiteConn = new SqliteConnection("SQLiteConnectionString"))
{
var list = SQLiteConn.Query<string>("SELECT ADI FROM MUSTERI");
}
using (SqlConnection SqlConn = new SqlConnection("SqlServerConnectionString"))
{
var list = SqlConn.Query<string>("SELECT ADI FROM MUSTERI");
}
Bu noktada Connection Factory deseni ile bağlantı yönetimini tek bir yerden tanımlayarak daha temiz ve sürdürülebilir bir yapı oluşturabiliriz.
Connection Factory Tasarımı
Aynı projeyi farklı .net platformlarında (.NET Framework, .NET Core ve Xamarin gibi) kullanarak tekrar kod yazmaya gerek duymadan, taşınabilir ve daha uyumlu bir yapı elde edebilmek için .NET Standard’ı tercih ettim.
Kullandığım sürüm: .NET Standard 2.0
DBType Enum’u
Veritabanı türlerini Enum tipinde tanımlıyoruz.
namespace DapperFactory.Domain.Entities.Enums
{
public enum DBType
{
Oracle,
SQLServer,
MySQL,
PostgreSQL,
SQLite
}
}
Dapper’ın genel metodları
Dapper’ın genel metodlarını tanımlıyoruz senkron ve asenkron şeklinde.
using Dapper;
using DapperFactory.Application.Interfaces;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace DapperFactory.Domain.Data
{
public class DapperMethods : IDapperMethods
{
private IDbConnection _dbConnection;
public void SetConnection(IDbConnection dbConnection)
{
_dbConnection = dbConnection;
}
#region Sync
public int Execute(string sql)
{
return _dbConnection.Execute(sql);
}
public IEnumerable<T> Query<T>(string sql)
{
return _dbConnection.Query<T>(sql);
}
#endregion
#region Async
public async Task<int> ExecuteAsync(string sql)
{
return await _dbConnection.ExecuteAsync(sql);
}
public async Task<IEnumerable<T>> QueryAsync<T>(string sql)
{
return await _dbConnection.QueryAsync<T>(sql);
}
#endregion
}
}
Connection Factory metodları
Connection’ları dinamik seçtirmek için factory metodlarını tanımlıyoruz.
using DapperFactory.Application.Interfaces;
using DapperFactory.Domain.Entities.Enums;
using MySql.Data.MySqlClient;
using System.Data;
namespace DapperFactory.Domain.Factory
{
public class MySQLFactory : IDapperFactory
{
public readonly string _connectionString;
public DBType _dbType { get; }
public string _connName { get; }
public MySQLFactory(string connectionString, DBType dbType, string connName)
{
_connectionString = connectionString;
_dbType = dbType;
_connName = connName;
}
public IDbConnection GetConnection()
{
return new MySqlConnection(_connectionString);
}
}
public class SQLServerFactory : IDapperFactory
{
public readonly string _connectionString;
public DBType _dbType { get; }
public string _connName { get; }
public SQLServerFactory(string connectionString, DBType dbType, string connName)
{
_connectionString = connectionString;
_dbType = dbType;
_connName = connName;
}
public IDbConnection GetConnection()
{
return new SqlConnection(_connectionString);
}
}
}
Connection Factory Selector Metodu
Seçilen enum ve connection name ile istenilen veritabanına tek metod üzerinden erişme.
using DapperFactory.Application.Interfaces;
using DapperFactory.Domain.Entities.Enums;
using DapperFactory.Domain.Factory;
using System;
namespace DapperFactory.Infrastructure.FactorySelector
{
public static class DbFactorySelector
{
public static IDapperFactory GetConnection(DBType dbType, string connectionString, string connectionName)
{
switch (dbType)
{
case DBType.Oracle:
return new OracleFactory(connectionString, dbType, connectionName);
case DBType.SQLServer:
return new SQLServerFactory(connectionString, dbType, connectionName);
case DBType.MySQL:
return new MySQLFactory(connectionString, dbType, connectionName);
case DBType.PostgreSQL:
return new PostgreSQLFactory(connectionString, dbType, connectionName);
case DBType.SQLite:
return new SQLiteFactory(connectionString, dbType, connectionName);
default:
throw new ArgumentException("Unsupported db type");
}
}
}
}
Interface’ler
Factory Selector Interface’i
using DapperFactory.Domain.Entities.Enums;
using System.Data;
namespace DapperFactory.Application.Interfaces
{
public interface IDapperFactory
{
DBType _dbType { get; }
IDbConnection GetConnection();
string _connName { get; }
}
}
Dapper Metodları Interface’i
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace DapperFactory.Application.Interfaces
{
public interface IDapperMethods
{
void SetConnection(IDbConnection dbConnection);
#region Sync
int Execute(string sql);
IEnumerable<T> Query<T>(string sql, object param = null);
#endregion
#region Async
Task<int> ExecuteAsync(string sql);
Task<IEnumerable<T>> QueryAsync<T>(string sql);
#endregion
}
}
Kullanım Yöntemi
Kullanmak istediğiniz projeye referans olarak ekleyip kullanabilirsiniz.
Depency Injection ile kullanım;
Entry point’de (genelde program.cs olur) Depency injection tanımlanır.
Enum seçimi, connection string ve connection name çoklanarak istenildiği kadar veritabanı tanımı yapılabilir.
Örneğin: Oracle ana makine, Mysql genel makine ve Mysql ikinci makine vb.
Bunlarla birlikte Dapper’ın genel metodlarını içeren class depency edilir.
var mySqlFactory = DbFactorySelector.GetConnection(DBType.MySQL, "ConnectionString", "AnaMakine");
builder.Services.AddSingleton<IDapperFactory>(mySqlFactory);
var postgreSQLFactory = DbFactorySelector.GetConnection(DBType.PostgreSQL, "ConnectionString", "AnaMakine");
builder.Services.AddSingleton<IDapperFactory>(postgreSQLFactory);
builder.Services.AddScoped<IDapperMethods, DapperMethods>();
Metod içinde, örneğin api controller’ı içindeki action’da;
Entry point’de aynı interface’e birden fazla depency tanımı yapıp, istediğimiz class’ın default constructor’unda IEnumerable tipinde liste halinde alabiliyoruz.
Zaten depency yapmadan önce veritabanı objelerini enum ve name bazında ayrıştırdığımız için burda da kolay bir linq sorgusu ile yakalayabiliriz.
Aşağıdaki örnekde mysql ve postgresql global değişkenlerini tanımlayıp, default constructor içinde depency ayrı ayrı depency ediliyor.
Dapper metodları için IDapperMethods interface’i bir kez depency edilir.
SetConnection metodu içinde ilgili factory’nin GetConnection metodu çağırılarak connection ataması yapılır ve istenilen Dapper metodu kullanılır.
Not: Aynı request’de birden fazla veritabanı sorgulanacaksa dapper metodu kullanılmadan önce ilgili veritabanına bağlı connection set edilir.
Tıpkı aşağıdaki örnekte olduğu gibi, önce mysql set edildi, sorgu yapıldı ve sonrasında postgresql set edilip sorgu yapıldı. Bununla birlikte karışmaması için kullanılan her db tanımı özelinde ayrı Dapper interface’ide çağırılabilir, tamamen tercihe bağlı.
using DapperFactory.Application.Interfaces;
using DapperFactory.Domain.Entities.Enums;
using Microsoft.AspNetCore.Mvc;
namespace Test.WebApi.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IDapperFactory _mysqlFactory;
private readonly IDapperFactory _postgreSQLFactory;
private readonly IDapperMethods _dapperMethods;
public HomeController(IEnumerable<IDapperFactory> dbFactory, IDapperMethods dapperMethods, ILogger<HomeController> logger)
{
_logger = logger;
_mysqlFactory = dbFactory.First(x => x._dbType == DBType.MySQL && x._connName == "AnaMakine");
_postgreSQLFactory = dbFactory.First(x => x._dbType == DBType.PostgreSQL && x._connName == "AnaMakine");
_dapperMethods = dapperMethods;
}
public IActionResult Index()
{
_dapperMethods.SetConnection(_mysqlFactory.GetConnection());
var mysqlResult = _dapperMethods.Query<string>("SELECT TCKN FROM MUSTERILER");
_dapperMethods.SetConnection(_postgreSQLFactory.GetConnection());
var postgreSqlResult = _dapperMethods.Query<string>("SELECT TCKN FROM MUSTERILER");
return View();
}
}
}
Depency Injection olmadan kullanım;
Interface’den değilde ana class’dan çağırarak kullanılabilir.
using DapperFactory.Domain.Data;
using DapperFactory.Domain.Entities.Enums;
using DapperFactory.Infrastructure.FactorySelector;
var mysqlFactory = DbFactorySelector.GetConnection(DBType.MySQL, "ConnectionString", "AnaMakine");
DapperMethods dapperMethods = new DapperMethods();
dapperMethods.SetConnection(mysqlFactory.GetConnection());
var mysqlList = dapperMethods.Query<string>("SELECT TCKN FROM MUSTERILER");
var sqlServerFactory = DbFactorySelector.GetConnection(DBType.SQLServer, "ConnectionString", "AnaMakine");
dapperMethods.SetConnection(sqlServerFactory.GetConnection());
var sqlServerList = dapperMethods.Query<string>("SELECT TCKN FROM MUSTERILER");
Özet
Dapper ile etkili bir veri tabanı bağlantı yönetimi sağlamak için geliştirdiğim Connection Factory tasarımını sizlere aktardım. Dapper’ın sağladığı performans avantajlarını koruyarak bağlantı yönetiminde nasıl daha temiz, yeniden kullanılabilir ve sürdürülebilir bir yapı oluşturulabileceğini adım adım açıklamaya çalıştım. Bu çözümün, veri erişim katmanında kod tekrarını azaltırken daha düzenli ve verimli bir yapı sunacağına inanıyorum.
Yazıda yapıyı açıklarken bazı metodları eklemedim, projenin asıl haline github hesabımdan ulaşabilirsiniz.
Github repository linki: DapperConnectionFactory
Okuduğunuz için teşekkürler, İyi kodlamalar 😉