Dapper ORM Nedir

Object relational mappers (ORMs); ilişkisel veritabanları ile programlama dillerinin nesne modelleri arasındaki uyumsuzluğu ortadan kaldırmak, kod ile database arasındaki uyumu %100 sağlamak için yazılım dünyasında uzun süredir kullanılmaktadırlar. Daha önceki ORM yazımızda Fluent NHibernate Nedir ve Kullanımı konusuna değinmiştik. Bu yazımızda ise birçok kişi tarafından en hızlı ORM olarak kabul edilen Dapper'ı inceleyeceğiz.

Dapper, Stack Overflow ekibi tarafından open-source geliştirilen lightweight bir ORM'dir. Lightweight olması sebebiyle diğer ORM'lere kıyasla çok daha hızlıdır.

Dapper, performans ve kullanım kolaylığı sağlaması düşünülerek geliştirilmiş bir ORM dir. Transaction, stored procedure yada bulk insery kullanarak static ve dynamic object binding yapabilmemizi sağlar.

Dapper Kullanımı

Dapper kullanımı için örnek basit bir app. geliştirelim. İlk olarak Vs'da Dapper_Sample adında bir ConsoleApp oluşturalım. Sonrasında nuget üzerinden Dapper'ı projemize indirip kuralım.

 Install-Package Dapper -Version 

Kurulum işlemi tamamlandıktan sonra örneğimiz şu şekilde olsun; ContosoCorp adında bir db ve bu db de Customer adında bir tablomuz olsun ve dapper kullanarak repository class ile bu tablo üzerinde CRUD işlemleri yapalım.

Öncelikle ContosoCorp db si için table create script'ini aşağıdaki gibi yazıp execute edelim ve Customer tablomuz oluşturalım.

create table Customer
(
 Id uniqueidentifier,
 FirstName nvarchar(50) not null,
 LastName nvarchar(50) not null,
 Email nvarchar(50) not null
)

İlk olarak tablomuza karşılık gelen Customer entity'sini aşağıdaki gibi oluşturalım.

    public class Customer
    {
        public Guid Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }

Şimdi ise CRUD işlemleri için CustomerRepository adında bir class oluşturalım ve içerisini CRUD metotlarını tanımlayalım.

    public class CustomerRepository
    {
        protected SqlConnection GetOpenConnection()
        {
            var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ContosoCorpDbConn"].ConnectionString);
            connection.Open();
            return connection;
        }
    }

Config dosyamızda kullanacağımız db için gerekli olan conn. string'inin ContosoCorpDbConn adıyla yazılı olduğunu varsayalım ve GetOpenConnection() metodu ile repository metotlarında kullanacağımız SqlConnection yaratacağız.

Dapper'da bulunan Query() metodu veritabanından retrieve data işlemini yani veri almamızı sağlayan ve model mapping'i yaparak modellerimizi dolduran metoddur. Aşağıda yazacağımız All() metodu ile Customer tablosunda bulunan bütün kayıtları select edeceğiz. 

        public IEnumerable<Customer> All()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("Select * From Customer").ToList();
            }
        }

İkinci metot olarak Id parametresi alarak geriye tek bir Customer objesi return eden Get() metodunu yazalım.

        public Customer Get(Guid id)
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("Select * From Customer WHERE Id = @Id", new { id }).SingleOrDefault();
            }
        }

Dapper'da bulunan Execute() metodu ise db de insert, update veya delete işlemlerinde kullanılır. Aşağıda yazacağımız metot ile parametre olarak verilen customer nesnesini insert edeceğiz.

        public void Insert(Customer customer)
        {
            using (var conn = GetOpenConnection())
            {
                string sqlQuery = "INSERT Customer(FirstName,LastName,Email) Values(@FirstName,@LastName,@Email)";
                conn.Execute(sqlQuery, customer);
            }
        }

Yukarıda görüldüğü üzre ado.net'e aşinalığı olan kişiler aslında bir nevi ado.net yazdığımızı göreceklerdir çünkü dapper'ın temelinde AdoçNet vardır. Son metot olarak dapper ile yukarıda yazdığımız All() metodunu birde stored-procedure kullanarak yazalım. İlk olarak db üzerinden aşağıdaki gibi GetAllCustomersSP adında stored-procedure'ü tanımlayalım.

CREATE PROCEDURE GetAllCustomersSP
AS
BEGIN
    SELECT * FROM Customer
END

Sonrasında bu procedure'ü execute eden repository metodunu yazalım.

        public IEnumerable<Customer> AllUsingSp()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("GetAllCustomersSP", commandType: CommandType.StoredProcedure).ToList();
            }
        }

 Repository metotlarını tanımladık sırada bu metotları kullanmak var. Program.cs içerisinde bu metotları call ederek yazdığımız kodları test edelim.

        static void Main(string[] args)
        {
            var custRepo = new CustomerRepository();

            var customer1 = new Customer { Id = Guid.NewGuid(), FirstName = "Caner", LastName = "Tosuner", Email = "info@canertosuner.com" };
            var customer2 = new Customer { Id = Guid.NewGuid(), FirstName = "Berker", LastName = "Sönmez", Email = "info@berkersonmez.com" };

            custRepo.Insert(customer1);
            custRepo.Insert(customer2);

            //returns 2 records
            var allCustomersUsingQuery = custRepo.All();

            //returns 2 records
            var allCustomersUsingSp = custRepo.AllUsingSp();

            Console.ReadLine();
        }

Projemizi run ettiğimizde Customer tablosuna sırasıyla 2 kayıt atacaktır ve sonrasında All() ve AllUsingSp() metotlarını kullanarak tabloya select sorgusu yaptığımızda bize yukarıda kaydettiğimiz 2 kaydı return edecektir.

Dapper aynı zamanda transactional işlemleri de destekler. UnitOfWork pattern'i BeginTransaction() ve EndTransaction() metotlarını kullanarak uygulanabilir.

 

Not : Nuget de bulunan DapperExtensions kütüphanesi kullanarak gerekli mapping işlemlerini yaptıktan sonra repository metotları içerisinde sql komutları yerine aşağıdaki gibi de kullanabiliriz. Not: Bu mapping üzerinden table/column auto generate işlemi olmamakta sadece mapping işlemini sağlamakta.

Install-Package DapperExtensions 

    public class CustomerMapper : ClassMapper<Customer>
    {
        public CustomerMapper()
        {
            base.Table("Customer");
            Map(f => f.Id).Key(KeyType.Guid);
            Map(f => f.FirstName);
            Map(f => f.LastName);
            Map(f => f.Email);
        }
    }
        public IEnumerable<Customer> All()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.GetList<Customer>().ToList();
            }
        }

        public Customer Get(Guid id)
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Get<Customer>(id);
            }
        }

DapperExtensions kütüphanesi ile kolayca bir Generic Repository Pattern infrastructure'ı geliştirebilirsiniz. Yazının sadeliği açısından bu ben değinmedim ancak sonraki yazılarımızda bir IoC kütüphanesi kullanarak Dapper ile basitçe bir api projesi geliştireceğiz.

Dapper son derece geliştirmesi kolay ve kullanışlı bir ORM dir. NHibernate ve EntityFramework'de olduğu gibi model mapping'inizden table/column generate etmez ancak sql sorgularını oldukça hızlı bir şekilde execute eder ve result'larını POCO class'larınıza map'ler. Bu özelliğiyle developer'lar arasında oldukça popülerdir.

Nancy Nedir (NancyFx)

Nancy .Net ve Mono için HTTP protokolü üzerinde çalışan uygulamalar geliştirmemizi sağlayan bir lightweight framework dür. Ruby de kullanılan Sinatra framework'ün den esinlenerek geliştirilmiştir ve az kaynak tüketmesinden dolayı performansıyla ön plana çıkmıştır.

Nancy developer'ları MVC(Model-View-Controller) pattern'nini veya başka herhangi bir pattern kullanmaya zorlamadan basit bir şekilde geliştirme yapmamıza olanak sağlar. Sebebi ise yukarıda bahsettiğimiz gibi sadece HTTP isteklerine cevap veren küçük ve orta ölçekli bir uygulama görevi görüyor olması.

MVC pattern'nini implement etmeye zorlamıyor derken edemeyeceğimiz anlamına da gelmemekte. Tıpkı ASP.Net MVC yada WebApi projelerinde olduğun gibi solution'da View klasörü yaratarak projeniz için olan .cshtml'leri bu dosya altında oluşturabilir veya Model klasörü yaratarak projede kullandığınız request response yada viewModel sınıflarınızı bu klasör altına oluşturabilirsiniz. Özetle Nancy ASP.Net MVC ve Web Api'nin bir alternatifi diyebiliriz. 

En büyük özelliği ise IIS e bağımlı olmadan Windows'da çalışmakta kalmayıp OSX, Linux hatta Raspberry Pi üzerinde bile çalışabilmektedir. Raspberry Pi üzerinden ASP.Net MVC çalıştırmak nasıl olurdu acaba..

Nancy ile örnek bir api projesi yapalım. İlk olarak  vs. da Nancy_Sample adında bir console app. oluşturalım ve sonrasında aşağıdaki gibi nuget üzerinden ihtiyacımız olan dll leri kuralım.

 

Nancy kütüphanesini kullanabilmek için Nancy ve host edebilmemizi sağlayan Nancy.Hosting.Self ve cshtml view'lerini kullanabilmemizi sağlayan Nancy.Viewengines.Razor paketlerini projemize ekleyelim.

Sonrasında Program.cs içerisine aşağıdaki gibi nancy konfigurasyonlarımızı yapalım.

class Program
{
    private readonly NancyHost _nancy;

    public Program()
    {
        var uri = new Uri("http://localhost:7880");
        var hostConfigs = new HostConfiguration { UrlReservations = { CreateAutomatically = true } };
        _nancy = new NancyHost(uri, new DefaultNancyBootstrapper(), hostConfigs);
    }

    private void Start()
    {
        _nancy.Start();
        Console.WriteLine($"Started listenig address {"http://localhost:7880"}");
        Console.ReadKey();
        _nancy.Stop();
    }

    static void Main(string[] args)
    {
        var p = new Program();
        p.Start();
    }
}

Yukarıdaki kod bloğunda Nancy bizim için host edilen makinada http://localhost:7880 portunu reserv ederek dinlemeye başlayacaktır. Bu adrese gelen http isteklerini ilgili route'a yönlendirecektir.

Browser üzerinden bu adrese gittiğimizde aşağıdaki gibi bir ekran ile karşılaşırız.

404 Not Found sayfasını almamızın sebebi projemizde henüz endpoint'leri tanımlayacağımız NancyModule class'ından türeyen bir Module olmaması.

Hemen projemize SampleModule adında NancyModule class'ından inherit alan bir class oluşturalım ve içerisine httpGet isteği alan bir end-point tanımlayalım.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Get["/"] = parameters => "Que pasa primo !";
    }
}

Projeyi tekrar run edip browser'dan kontrol ettiğimizde aşağıdaki gibi Get metodunun return ettiği response'u göreceğiz.

Şimdi birde HttpPost örneği yapalım. Request olarak 2 sayı alan ve geriye bu 2 sayının toplamını return eden bir end-point yazalım. Request ve Response modellerimiz aşağıdaki gibi olacak şekilde oluşturalım.

public class SumRequestModel
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class SumResponseModel
{
    public int Result { get; set; }
}

SampleModule içerisine yazacağımız end-point ise aşağıdaki gibi gönderilen request parametrelerine göre geriye toplamlarını dönecektir.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Post["/sum"] = parameters =>
        {
            var request = this.Bind<SumRequestModel>();

            return new SumResponseModel { Result = request.X + request.Y };
        };
    }
}

Postman üzerinden aşağıdaki gibi bir httpPost request'inde bulunduğumuzda request olarak gönderilen parametrelere göre response da toplamlarını dönmektedir.

 

Yukarıda yaptığımız örnek ile Nancy kullanarak basit bir api nasıl geliştirebiliriz inceledik. Yazının başında da söylediğimiz üzre Nancy ile geriye View de yani html sayfaları da return edebiliriz.

Örnek olarak solution'da View adında bir klasör ve içerisine Home adında .cshml uzantılı bir htmlFile oluşturalım. İçerisine de aşağıdaki gibi body tagleri arasına basit bir form input'ları ekleyelim.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <form method="post" name="myForm">
        First name: <input type="text" name="fname"><br>
        Last name: <input type="text" name="lname"><br>
        <input type="button" value="Send">
    </form>
</body>
</html>

Oluşturduğumuz bu sayfaya solution'da sağ tıklayıp Properties'den Copy to Output Directory özelliğini Copy always olarak değiştirmemiz gerekmekte aksi taktirde proje run edildiğinde Home.cshtml dosyasına erişemiyor.

Yukarıdakileri yaptıktan sonra browser'dan http://localhost:7880/home adresine bir istek geldiğinde Home.cshtml sayfamıza yönlendirecek kodu yazalım.

public class SampleModule : NancyModule
{
    public SampleModule()
    {
        Get["/home"] = parameters =>
        {
            return View["View/Home.cshtml"];
        };
    }
}

Projeyi run edip browser'dan http://localhost:7880/home adresine gittiğimizde bizi Home.cshtml sayfasına yönlendirip ekrana formu yazdıracaktır.

 

Özetle; Nancy Microsoft tarafından ASP.Net'in core dll'i olan System.Web'e bağımlı olmadan özgürce geliştirdiği şahane bir framework dür. Genelde çok büyük ölçekli projelerde tercih edilmese de ihtiyaç olduğunda bizleri çok fazla iş yükünden kurtararak ve de bence en önemlisi IIS'e bağlı kalmadan belli bir göreve hizmet eden küçük ölçekli lightweight uygulamalar geliştirmemize olanak sağlar. Daha fazla bilgi için nancyfx.org sayfasına göz atabilirsiniz.

RabbitMQ MassTransit Kullanarak Producer Consumer Yapısı

Daha önceki RabbitMQ Nedir yazımızda genel hatlarıyla rabbitmq dan bahsedip windows makina üzerinde kurulumunu anlatıp kısaca; erişilebilirliği, güvenilirliği yüksek ve ölçeklenebilen loosly-coupled asenkron iletişim sağlayan uygulamalar geliştirmektir diye tanımlamıştık.

Bu yazımızda ise Masstransit kullanarak bir Producer Consumer projesi oluşturacağız ancak makinamızda rabbitmq kurulu çalışır vaziyette olduğundan emin olalım.

Enterprise Service Bus (ESB) Nedir ?

Enterprise service bus diğer ismiyle message broker; rabbitmq gibi messaging katmanlarının üzerinde bulunan ve distributed synchronous yada asynchronous communication sağlayabilen bir middleware' dir. Bize birbirinden tamamen farklı olabilen uygulamalar arasında message-based communication yapabilmemizi sağlayan bir transport gateway'i dir. Diğer bir deyişle; message'ları provider ve consumer arasında transport eden bir çeşit middleware tool'u dur. 

 

 

Şimdi ise masstransit kullanarak bir örnek üzerinden ilerleyelim. Örneğimiz şu şekilde olsun; günlük şirket bültenini tüm çalışanlara email olarak gönderen bir producer/consumer yapısı tasarlayalım.

VS da sırasıyla 3 proje oluşturacağız.

  1. RMQMessage(class lib.)
  2. RMQProducer(console app.)
  3. RMQConsumer(console app.)

1-) RMQMessage

İlk projemiz olan RMQMessage ile başlayalım. Vs da RMQMessage adından bir class-library oluşturalım. Masstransit'in message-base communication sağladığını söylemiştik ve hem producer hemde consumer tarafından kullanılacak bu message yada contract diyede adlandırdığımız model ve onun sub-model'lerini oluşturacağız. 

İlk olarak abstract olan BaseMessage.cs'i oluşturalım ve bu model içerisinde ortak olmasını istediğimiz property'ler yer alsın.

    public abstract class BaseMessage
    {
        public DateTime PublishedTime { get; set; }
        public DateTime ConsumedTime { get; set; }
        public Guid QueueId { get; set; }
    }

Sonrasında yukarıda bahsettiğimiz producer ve consumer'ın kullanacağı NewsletterMailMessage adında contract'ımızı aşağıdaki oluşturalım.

    public class NewsletterMailMessage: BaseMessage
    {
        public List<string> AddressList { get; set; }
        public NewsletterModel NewsLetter { get; set; }
    }
    public class NewsletterModel
    {
        public string MailSubject{ get; set; }
        public string HtmlContent { get; set; }
    }

Yukarıda modellerimizi oluşturduk ve RMQMessage projesindeki işimiz bitti. Şimdi sırada 2. projeyi oluşturmak var.

2-) RMQProducer

Bunun için aynı solution'da RMQProducer adında bir console app oluşturalım. Bu proje queue'ya ilgili message'ı publish etmeden sorumlu olacak. Projeyi oluşturduktan sonra nuget üzerinden package manager console kullanarak aşağıdaki masstransit.rabbitmq paketini yükleyelim.

Masstransit.RabbitMQ

 Install-Package MassTransit.RabbitMQ

Bu paket ile birlikte rabbitmq için kullanılan masstransit kütüphanesi onun dependency'leri projemize kurulmuş olacak.

Kurulum tamamlandıktan sonra RMQMessage projesini RMQProducer projesine reference olarak ekleyelim. Ekleme nedenimiz NewsletterMailMessage.cs modelini kullanabilmek.

Program.cs içerisine aşağıdaki gibi InitializeBus adında bir metot tanımlayalım ve bu metot IBusControl arayüzünü initialize etmekten sorumlu olsun.

    static IBusControl InitializeBus()
    {
        return Bus.Factory.CreateUsingRabbitMq(cfg =>
         {
             cfg.Host(new Uri("rabbitmq://localhost/"), h =>
             {
                 h.Username("guest");
                 h.Password("guest");
             });
         });
    }

local makinada kurulu olan rabbitmq url'i rabbitmq://localhost/ dir ve default username password ise guest olarak tanımlanmıştır.

RabbitMQ ya bağlantı kısmını halletik şimdi ise message objemizi initialize edelim.

    static NewsletterMailMessage InitializeMessage()
    {
        var message = new NewsletterMailMessage
        {
            AddressList = _customerService.GetAllMailAddresses(), //assume more than 2000
            NewsLetter = new NewsletterModel
            {
                MailSubject = "Daily Newsletter of Contoso Corp.",
                HtmlContent = "Lorem ipsum dolor sit amet, et nam mucius docendi hendrerit, an usu decore mandamus. Ei qui quod decore, cum nulla nostrud erroribus ut, est eu aperiri interesset. Legere mentitum per an. Hinc legimus nostrum cu vix."
            },
            PublishedTime = DateTime.Now,
            QueueId = Guid.NewGuid()
        };

        Console.WriteLine("Message published !\nSubject : " + message.NewsLetter.MailSubject + "\nPublished at : " + message.PublishedTime + "\nQueueId : " + message.QueueId);
        return message;
    }

Son adım olarak main fonksiyonu içerisinde bu iki metodu call ederek message'ı queue'ya push edip Producer projemizdeki işlemleri bitirelim.

   static void Main(string[] args)
   {
       var busControl = InitializeBus();
       busControl.Start();
       Console.WriteLine("Started publishing.");

       var message = InitializeMessage();

       busControl.Publish(message);
       Console.ReadLine();
   }

 

3-) RMQConsumer

Sırada son adım olan consumer projesini oluşturma var. Consumer Producer'ın gönderdiği message'ları dinleyerek ilgili queue için consume etmeden sorumlu. Projeyi oluşturduktan sonra nuget üzerinden package manager console kullanarak aşağıdaki masstransit.rabbitmq paketini yükleyelim.

Masstransit.RabbitMQ

 Install-Package MassTransit.RabbitMQ

Bu paket ile birlikte rabbitmq için kullanılan masstransit kütüphanesi onun dependency'leri projemize kurulmuş olacak.

Kurulum tamamlandıktan sonra RMQMessage projesini RMQConsumer projesine reference olarak ekleyelim. Ekleme nedenimiz NewsletterMailMessage.cs modelini kullanabilmek.

İlk olarak rabbitmq ile connection sağlama ve hangi queue kullanılacak gibi konfigurasyonları Pragram.cs içerisine tanımlayalım.

    static void Main(string[] args)
    {
        var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
        {
            var host = cfg.Host(new Uri("rabbitmq://localhost/"), h =>
            {
                h.Username("guest");
                h.Password("guest");
            });

            cfg.ReceiveEndpoint(host, "DailyNewsletterMail", e =>
            e.Consumer<NewsletterMailMessageConsumer>());
        });

        busControl.Start();
        Console.WriteLine("Started consuming.");
        Console.ReadLine();
    }

Yukarıdaki kod bloğu şunu söylüyor; eğer rabbitmq local makinamızda kurulu ise adresi rabbitmq://localhost/ şeklindedir ve default userName-password guest olarak gelmektedir. Queue ismi olarak DailyNewsletterMail verdik ve bu queue için consume edilecek olan message da NewsletterMailMessageConsumer şeklinde belirttik.

Şimdi ise NewsletterMailMessageConsumer.cs adında message objemizi consume edecek olan kısmı geliştirelim.

public class NewsletterMailMessageConsumer : IConsumer<NewsletterMailMessage>
{
    public Task Consume(ConsumeContext<NewsletterMailMessage> context)
    {
        var message = context.Message;
        message.ConsumedTime = DateTime.Now;

        Console.WriteLine("Message consumed !\nSubject : " + message.NewsLetter.MailSubject + "\nConsumed at: " + message.ConsumedTime + "\nQueueId : " + message.QueueId);

        //todo send mail impr.

        return context.CompleteTask;
    }
}

Yukarıdaki kod blou şunu anlatıyor; NewsletterMailMessageConsumer adında IConsumer interface'inin implement eden ve bu implementasyon sonucunda Consume metoduna sahip olan bir consumer'ı mız var. Bu consumer DailyNewsletterMail queue'suna gelen NewsletterMailMessage modelini consume eder.

Sırasıyla Consumer'ı ve Producer'ı run ettiğimizde ilk olarak rmq management-console da DailyNewsletterMail queue'su aşağıdaki gibi listelenecektir.

 Producer ve Consumer projelerinin ayrı ayrı console çıktıları ise aşağıdaki gibi olacaktır.

Aynı QueueId ye ait message'ımız Producer dan çıkıp consumer'a ulaştığı bilgisini yukarıdaki gibi görüyoruz.

Queue yapıları özellikle async mesajlaşma gerektiren yerlerde oldukça önemlidir ve hayat kurtarır. Bu yazıda service bus'lar dan Masstransit kullanarak basit bir consume/producer uygulaması geliştirdik ve daha sonraki yazılarımızda diğer queue yapıları ile ilgilide konuları ele almaya devam edeceğiz.