Dapper Nedir? Dapper Temelleri ve Kullanımı
Dapper, 2011 yılında Stackoverflow ekibindeki kişiler tarafınca yazılmış açık kaynak kodlu ADO.NET tabanlı bir mikro ORM aracıdır. Microsoft 2016 yılında Dapper’ ı satın alarak kendi bünyesine dahil etmiştir.
Entity Framework gibi uzun bağlantı konfigurasyonları, tablo ve context sınıfları, tablo ilişkileri gibi detaylı işlemler yerine veri tabanına en hızlı şekilde bağlanıp sorgu yazmanıza olanak sağlamaktadır. Tabii haliyle Entity Framework’ ün sağladığı cache, change tracking, lazy loading, migration gibi özelliklerden arındırılmış haldedir.
Dapper nerelerde kullanılır?
Hızlı aksiyon alınması gereken herhangi bir yerde kullanılabilir. Örneğin veri tabanınızdaki datalardan bir rapor çıkarmanız veya toplu olarak ekleme, güncelleme yapmanız gerekiyor. Bunun için bir konsol projesi açıp Dapper ile bir kaç satır kod yazarak istediğinizi kısa sürede performanslı bir şekilde elde edebilirsiniz.
Dapper nasıl kullanılır?
Dapper’ ın nasıl kullanılacağını bir konsol projesi üzerinden anlatacağım. Veri tabanında ise Employees adında tek bir tablo bulunmaktadır.
Dapper’ ı kullanabilmek için iki tane paket indirmeniz gerekmektedir:
1- Dapper (Sam Saffron, Marc Gravell, Nick Craver)
2- System.Data.SqlClient (Microsoft)
Bu paketleri indirdikten sonra veri tabanı bağlantısını kurabilirsiniz. Ben örneklerimi transaction içerisinde gerçekleştireceğim.
using (IDbConnection conn = new SqlConnection("Data Source=.;Initial Catalog=TestDB;Integrated Security=True;Connect Timeout=30;"))
{
...
}
Dapper, her sorgu çağırıldığında bağlantıyı sizin yerinize otomatik olarak açar ve sorgudan cevap döndüğünde kapatır. Eğer manuel olarak conn.Open() metodu ile bağlantınızı açtıysanız, manuel olarak conn.Close() ile kapatmanız gerekmektedir.
Eğer transaction yapısıyla kullanıp, bağlantınızı manuel olarak açarsanız transaction sonunda conn nesnesi dispose edileceği için bağlantı da otomatik olarak kapatılacaktır. Bağlantı durumlarını test etmek isterseniz yazının sonunda vermiş olduğum github reposundaki “master-connection” branch’ ini inceleyebilirsiniz. Transaction yapısında bağlantı durumu örneği:
using (var _conn = new SqlConnection("Data Source=.;Initial Catalog=TestDB;Integrated Security=True;Connect Timeout=30;"))
{
Console.WriteLine(_conn.State.ToString());
var result = await _conn.QueryAsync<Employee>("SELECT * FROM Employees");
Console.WriteLine(_conn.State.ToString());
}
Manuel olarak bağlantı açılmadığı için sorgudan önce ve sonra bağlantı durumu “Closed” görünüyor. Fakat Dapper sorguyu çalıştırırken arka tarafta kendi bağlantısını açıp-kapattı.
Parametre olarak string tipinde SQL sorgusu alır ve IEnumerable tipinde sonuç döner. Yani SELECT sorgularında sıkça kullanır. Örnek:
var result = await conn.QueryAsync<Employee>("SELECT * FROM Employees");
ExecuteAsync(…)
Parametre olarak string tipinde SQL sorgusu alır ve integer tipinde (etkilenen kayıt sayısı) sonuç döner. Yani INSERT/UPDATE/DELETE sorgularında sıkça kullanır. Örnek:
Insert
var result = await conn.ExecuteAsync("INSERT INTO Employees Values(1001,'Enes','Çakır',23,'eckr@mail.com','Turkey','34000','Red','01/08/2021','10000')");
Update
var result = await conn.ExecuteAsync("UPDATE Employees SET favorite_color = 'Yellow' WHERE employee_id = '1001'");
Delete
var result = await conn.ExecuteAsync("DELETE FROM Employees WHERE age < 19");
Görüldüğü üzere raw SQL sorgularıyla DML işlemlerini de gerçekleştirebiliyoruz.
İlişkisel veri sorgusu kullanımı (MultipleMapping)
İlişkisel verileri sorgulamak istediğimizde multi-mapping desteği de sağlamaktadır. QueryAsync(…) metodu içerisine bir Func delegesi parametresi aracılığıyla maplemenin nasıl yapılacağını belirttiğimizde sonuç olarak ilişkisel verileri tek model üzerinden elde etmemizi sağlar.
Örnek olarak bire-bir ilişkili verileri INNER JOIN ile sorgulamayı göstereceğim fakat öncesinde akışı daha iyi anlayabilmeniz için tablo modellerini göstermek istiyorum:
public class Employee
{
public int employee_id { get; set; }
public string? first_name { get; set; }
public string? last_name { get; set; }
public int age { get; set; }
public string? email { get; set; }
public string? postal_code { get; set; }
public string? favorite_color { get; set; }
public string? hire_date { get; set; }
public double salary { get; set; }
public Company? Company { get; set; }
}
public class Company
{
public int company_id { get; set; }
public string? company_name { get; set; }
public string? founding_date { get; set; }
//public string employee_id { get; set; }
}
Company tablosunda employee_id adında JOIN yaparken kullanabileceğimiz bir kolon bulunmaktadır. Fakat bu alanı, veri tekrarı olmaması için modele eklemedim.
Sorgu için QueryAsync(…) metodunun overloadlarından birisini kullanacağız. Kullanımı aşağıdaki gibidir:
var sql = @"SELECT e.employee_id, e.first_name, e.last_name, e.email, c.company_id, c.company_name, c.founding_date
FROM Employees e
INNER JOIN Companies c on e.employee_id = c.employee_id ";
var result = await conn.QueryAsync<Employee, Company, Employee>(sql, (employee, company) =>
{
employee.Company = company;
return employee;
}, splitOn: "company_id");
Tip güvenli çalışırken tipler: <Tablo1, Tablo2, ReturnModel> sırasıyla verilmektedir. Son model, dönecek sonucun tipini belirlemektedir. SQL sorgusu verildikten sonra ise, bir Func delegesi ile SELECT ile çağırılan alanların maplemesi gerçekleştirilir.
Burada dikkat edilmesi gereken kısım ise splitOn parametresidir. Bu parametre gelen kolonların hangi kolondan itibaren farklı modellere ayrışacağını belirtilen bir bayrak kolondur. Örnekte görüldüğü üzere company_id’ den itibaren gelen kolonlar Company modeline maplenmiştir. Daha sonra bu modeli de Employee altındaki Company property’ sine setleyerek return edildi. Sonuç:
Tabii bunu kullanmak yerine, JOIN sorgunuzdan gelecek kolonlarla birebir eşleşen bir model oluşturup QueryAsync<T>(string query) metoduna dönüş tipi olarak onu verebilirsiniz.
Stored Procedure (SP) kullanımı
Çoğu projede, sık kullanılan sorgular performanslı sonuçlar elde etmek için stored procedure olarak saklanmaktadır. Dapper, SP oluşturmaya ve kullanmaya olanak sağlar.
Örneğin iki parametreli bir SELECT sorgusu içeren SP oluşturmak istendiğinde:
var spResult = await conn.QueryAsync(@"CREATE PROCEDURE GetYoungEmployeesByFavoriteColor
(@age int, @favColor varchar(50))
AS
BEGIN
SELECT * FROM Employees WHERE age < @age AND favorite_color = @favColor
END");
Sorgusu kullanılabilir. Burada sonuç olarak boş bir liste dönecektir bu da işlemin başarılı olduğunu göstermektedir. (ExecuteAsync(…) metodu da kullanılabilir, etkilenen bir data olmadığı için cevap olarak -1 dönecektir)
Oluşturduğumuz bu SP’ yi kullanmak için ise aşağıdaki kod çalıştırılmalıdır:
var parameters = new DynamicParameters();
parameters.Add("age", 23);
parameters.Add("favColor", "blue");
var result = await conn.QueryAsync("GetYoungEmployeesByFavoriteColor", parameters, null, null, CommandType.StoredProcedure);
Öncelikle parametreli SP’ ye göndereceğimiz parametrelerin oluşturulması gerekmektedir. ilk olarak DynamicParameters sınıfından nesne oluşturmaktayız. Parametrelerin isimlerini ve değerlerini verdikten sonra QueryAsync(…) metodu aracılığıyla SP çağırılır.
Parametreleri QueryAsync(…)’ e object tipinde de gönderebiliriz, aynı SP için örneğini aşağıda bulabilirsiniz:
var result = await conn.QueryAsync("GetYoungEmployeesByFavoriteColor", new { age = 25, favColor = "red" }, null, null, CommandType.StoredProcedure);
Bunun yanı sıra bir INSERT sorgusuna sahip SP’ yi çalıştırmak için ise ExecuteAsync(…) kullanılmaktadır. Kullanımı tıpkı SELECT SP’ de kullandığımız QueryAsync(…) gibidir. Önce SP’ yi oluşturalım:
var spResult = await conn.QueryAsync(@"CREATE PROCEDURE InsertEmployee
(@id int,@name varchar,@lastname varchar, @country varchar, @age int, @favColor varchar)
AS
BEGIN
INSERT INTO Employees(employee_id, first_name, last_name, country, age, favorite_color)
VALUES(@id,@name,@lastname,@country, @age, @favColor);
END");
Daha sonra bu SP’ yi çağıralım:
var parameters = new DynamicParameters();
parameters.Add("id", 3279);
parameters.Add("name", "Ahmet");
parameters.Add("lastname", "Sevgi");
parameters.Add("country", "Turkey");
parameters.Add("age", 26);
parameters.Add("favColor", "black");
var result = await conn.ExecuteAsync("InsertEmployee", parameters, null, null, CommandType.StoredProcedure);
SP çalıştırılması sonucunda tabloya bir adet kayıt ekleneceği için sonuç olarak bize 1 dönecektir.
Vakit ayırıp okuduğunuz için teşekkür ederim, umarım sizler içi faydalı olmuştur. Benim hazırladığım, aynı zamanda burada anlatılan örnekleri de içeren kodların olduğu bir GitHub reposu bulunmakta, test etmek isteyen kişiler aşağıdaki linkten repoya ulaşabilir.
GitHub: https://github.com/kuzudoli/Dapper-Basics-Simple-Example