Generic Repository ve Unit of Work Kullanarak Temel Bir Infrastructure Tasarlamak

Merhaba arkadaşlar, bu makalemde sizlerle Generic Repository ve Unit of Work kurumsal tasarım kalıplarını uygulayarak temel bir alt yapı (infrastructure) tasarlayacağız. Tasarlama sırasında Entity Framework’den yararlanarak code first yaklaşımı ile geliştireceğiz. Tabi ki alt yapımız ORM bağımsız (independent) olacak.

Alt yapımızı geliştirirken makul bir seviyede sıkı sıkıya bağlı (tight coupled) olmadan  gevşek bağlanmış (loosely coupled) bir şekilde geliştirmeye çalışacağız ki, gerek unit test‘ler olsun gerekse de ileride geliştireceğimiz bir modül için kırılma noktasına gelmeden, esnek bir şekilde entegre edebilmek için dependency inversion (bağımlılığın ters çevirilmesi) prensibinden faydalanarak alt yapımızı esnek ve tekrar kullanılabilir bir şekilde tasarlayacağız.

Alt yapımızı tasarlamaya başlamadan önce bu iki kurumsal tasarım kalıbını bir tanımlayalım.

Repository Pattern Nedir?

Bu desenin temelinde yatan asıl amaç;

Veritabanına veri ekleme, güncelleme ve okuma gibi CRUD (Create Read Update Delete) işlemlerimiz için oluşturmuş olduğumuz kodların tekrar kullanılabilirliğini sağlamaktır.

Yazılım geliştirmede, belkide en önemli prensiplerden birisi olan DRY (Don’t repeat yourself) yani kendini tekrarlama prensibine göre kod tekrarlarından sakınmak gerekmektedir. Repository Patterni ise bize bunu sağlamaktadır. Oluşturduğumuz bir sınıf ile tüm tablolar için CRUD işlemlerimizi yapabilmemizi sağlamaktadır ve kod tekrarını önlemektedir. Aksi durumda bir düşünsenize, 30 tablolu bir yapımız mevcut ve her biri için CRUD işlemleri yapmamız gerektiğini? Vay anam vay…

Unit of Work Pattern Nedir?

Unit of Work ise bize;

Veritabanı ile yapılacak olan tüm işlemleri, tek bir kanal aracılığı ile gerçekleştirme ve hafızada tutma işlemlerini sunmaktadır. Bu sayede işlemlerin toplu halde gerçekleştirilmesi ve hata durumunda geri alınabilmesi sağlamaktadır.

Unit of Work deseni tek başına kullanılabileceği gibi, Repository kurumsal tasarım deseni ile (ki makalemiz konusudur aşağıda beraber kullanımını göreceğiz) veya Identity Map kurumsal tasarım deseni ile de kullanılabilir.

Identity Map: ORM geliştirirken güncelleme (update) işlemleri sırasında veritabanından çekilmiş olan nesnenin, sadece değişime uğrayan alanlarını güncelleyebilmemizi sağlayan bir desendir.

Unit of Work deseninde ise, işlemleri hafızada tutma özelliğinden bahsetmiştik. Identity Map deseni işte bu noktada implemente edilebilir.

Yukarıdaki şemayı da incelediğimizde göreceğimiz üzere ilk başta veritabanına direkt erişimi görüyoruz klasik programlamada. İkinci kısımda ise bir tarafta Entity Framework için hazırlanmış olan Unit of Work deseni ile ilgili Repository’ler aracılığı ile veritabanına erişim var. Bu sayede Unit Test’ler için olan Mocklanmış sınıflar aracılığı ile test bir veritabanına veya in-memory hazırlanmış veri alanına erişimini görüyoruz.

Mock Object: Unit testler yaparken uygulamamızın tümünü ayakta tutmak/tutabilmek zor olacaktır bir hayli. Bunun yerine uygulamamızın test ortamında çalışabileceği kadar fonksiyonları içeren sınıflar hazırlanır. Bu tür sınıflara Mock Object denir.

Şimdi Repository ve Unit of Work kurumsal kalıplarının ne olduğunu anladığımıza göre, örneğimize geçelim ve beraber kullanımlarını detaylıca incelemeye başlayalım.

Örneğimiz gereği basitleştirilmiş temel bir alt yapı tasarlayacağız. Bu alt yapımız Entity Framework‘den yararlanarak code first yaklaşımı ile geliştireceğiz ve Repository ve Unit of Work desenlerini ise maksimum seviyede izole ederek, gerek Entity Framework ve MSSQL ile gereksede Mongo DB ilede çalışabilir bir şekilde modüler geliştirmeye çalışacağız.

Örneğimiz basit bir Blog sistemi olacak ve User, Article ve Category sınıflarından oluşacak.

İşe RepveUOW isminde boş bir solution oluşturarak içerisine ise RepveUOW.Data.Model isminde bir sınıf kütüphanesi (class library) ekleyerek başlıyorum.

Burada isminden de anlaşılabileceği üzere Poco sınıflarımız yani modellerimiz yer alacak. Bu sayede uygulamamız içi bu entity nesnelerimiz orm bağımsız bir şekilde gelişiyor olacak.

Model katmanı içerisine ilk başta tüm entity nesnelerimiz için ortak olan CreatedDate özelliğini kalıtım yolu ile aktarabilmek için ModelBase isminde bir soyut sınıf oluşturuyoruz.

using System;

namespace RepveUOW.Data.Model
{
    public abstract class ModelBase
    {
        public DateTime CreatedDate { get; set; }
    }
}

Soyut sınıfımız hazır olduğuna göre şimdi entitylerimizi oluşturmaya başlayabiliriz.

User sınıfı ile başlıyoruz.

using System;
using System.Collections.Generic;

namespace RepveUOW.Data.Model
{
    public class User : ModelBase
    {
        public User()
        {
            this.Articles = new List<Article>();
        }

        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }

        // Fluently bir şekilde ilişkileri kullanabilmemiz için tanımlıyoruz.
        public virtual ICollection<Article> Articles { get; set; }
    }
}

Category sınıfı ekliyoruz.

using System.Collections.Generic;

namespace RepveUOW.Data.Model
{
    public class Category : ModelBase
    {
        public Category()
        {
            this.Articles = new List<Article>();
        }

        public int Id { get; set; }
        public string Name { get; set; }

        // Relations
        public virtual ICollection<Article> Articles { get; set; }
    }
}

Şimdide Article sınıfını ekliyoruz ve Model katmanımızı tamamlamış oluyoruz.

using System;

namespace RepveUOW.Data.Model
{
    public class Article : ModelBase
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public int UserId { get; set; }

        public string Title { get; set; }
        public string Content { get; set; }

        // Relations
        public virtual Category Category { get; set; }
        public virtual User User { get; set; }
    }
}

Modellerimizi oluştururken gördüğünüz gibi ilişkileri kullanabilmek için altta // Relations skobunda tanımladık. Bu ilişkileri daha sonra ilgili Context sınıfı içerisinde context ayağa kalkarken tanımlayacağız.

Model katmanını tasarladığımıza göre şimdi Data katmanını oluşturmaya başlayabiliriz.RepveUOW.Data isminde bir sınıf kütüphanesi daha oluşturuyoruz ve References kısmında Manage Nuget Packages sekmesine girip Entity Framework‘ü install ettikten sonra tekrar References kısmından Add Reference seçeneğini seçerek Projects tabından RepveUOW.Data.Model katmanını seçerek referans olarak ekliyoruz.

Referans işlemlerini tamamladığımıza göre RepveUOW.Data katmanı içerisinde Context isminde bir klasör tanımlayıp EFBlogContext sınıfını oluşturuyoruz.

EFBlogContext sınıfı bizim entity framework için kullanacak olduğumuz context’imiz olacak.

using System.Data.Entity;
using RepveUOW.Data.Model;

namespace RepveUOW.Data
{
    public class EFBlogContext : DbContext
    {
        public EFBlogContext()
            : base("BlogContext")
        {
        }

        public DbSet<User> Users { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<Article> Articles { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // İlişkileri kuruyoruz one-to-many olarak.
            modelBuilder.Entity<Article>()
                .HasRequired<Category>(x => x.Category)
                .WithMany(x => x.Articles)
                .HasForeignKey(x => x.CategoryId);

            modelBuilder.Entity<Article>()
                .HasRequired<User>(x => x.User)
                .WithMany(x => x.Articles)
                .HasForeignKey(x => x.UserId);

            base.OnModelCreating(modelBuilder);
        }
    }
}

Code first yaklaşımı ile geliştirmek istediğimizden EFBlogContext sınıfımızı System.Data.Entity namespace’inde bulunan DbContext sınıfından türetiyoruz. EFBlogContext’in constructor’ında DbContext sınıfının constructur’ına connection string’in ismini gönderiyoruz. Daha sonra bu connection string ismini Data katmanımızı kullanacak olan sunum katmanının ilgili App.config veya Web.config içerisinde tanımlıyor olacağız.

DbSet’ler aracılığı ile ilgili poco sınıflarımızı tanımladık ve modelimiz oluşurken ilişkileri tanımlayabilmemiz için OnModelCreating metotunu ezerek ilgili ilişkileri tanımladık. Fluent bir arayüze sahip olduğu için DbModelBuilder sınıfı gayet kolay bir şekilde ilişkilerimizi tanımlayabildik.

Code first olarak devam ettiğimiz için Sql Server üzerinde henüz hiç bir veritabanı bulunmamaktadır. EFBlogContext hazır olduğuna göre şimdi poco sınıflarımızdan yola çıkarak kolay bir şekilde veritabanı nasıl oluşturulur ona bir bakalım.

Bu işleme başlamadan önce henüz bir sunum katmanı bulunmadığı için App.config configuration tag’inin içerisinde ve configSections tag’inin altında bir conenction string tanımlayalım.

  <connectionStrings>
    <add
      name="BlogContext"
      connectionString="Data Source=localhost;Initial Catalog=BlogContext;Integrated Security=True;MultipleActiveResultSets=True;"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Şimdi sırasıyla View->Other Windows-> Package Manager Console açıyoruz ve üzerindeki Default project kısmında RepveUOW.Data seçili olduğundan emin oluyoruz.

Açılan konsola önce: Enable-Migrations –EnableAutomaticMigrations yazıyoruz ve connection string’de herhangi bir hata yapmadı isek Migrations isminde bir klasör oluşturup içerisinde Configuration sınıfını oluşturduğunu göreceğiz.

Şimdi tekrar Package Manager Console üzerinde Update-Database yazarak veritabanını oluşturmasını sağlıyoruz.

Veritabanımız ve context’imiz oluştuğuna göre şimdi generic repository sınıfını tasarlamaya başlayabiliriz. Hemen data katmanımıza Repositories isminde bir klasör ekleyerek içerisinde IRepository arayüzünü tasarlamaya başlıyoruz.

IRepository

using System;
using System.Linq;
using System.Linq.Expressions;

namespace RepveUOW.Data.Repositories
{
    /// <summary>
    /// Model katmanımızda bulunan her T tipi için aşağıda tanımladığımız fonksiyonları gerçekleştirebilecek generic bir repository tanımlıyoruz.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IRepository<T> where T : class
    {
        IQueryable<T> GetAll();
        IQueryable<T> GetAll(Expression<Func<T, bool>> predicate); // LINQ desteği sunabilmek içinde expression'ları kullanıyoruz.
        T GetById(int id);
        T Get(Expression<Func<T, bool>> predicate);

        void Add(T entity);
        void Update(T entity);
        void Delete(T entity);
        void Delete(int id);
    }
}

Şimdi entity framework için kullanıyor olacağımız EFRepository sınıfını ekleyerek kodlarını aşağıdaki gibi yazıyoruz.,

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;

namespace RepveUOW.Data.Repositories
{
    /// <summary>
    /// EntityFramework için hazırlıyor olduğumuz bu repositoriyi daha önceden tasarladığımız generic repositorimiz olan IRepository arayüzünü implemente ederek tasarladık.
    /// Bu şekilde tasarlamamızın ana sebebi ise veritabanına independent(bağımsız) bir durumda kalabilmek. Örneğin MongoDB için ise ilgili provider'ı aracılığı ile MongoDBRepository tasarlayabiliriz.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class EFRepository<T> : IRepository<T> where T : class
    {
        private readonly DbContext _dbContext;
        private readonly DbSet<T> _dbSet;

        public EFRepository(EFBlogContext dbContext)
        {
            if (dbContext == null)
                throw new ArgumentNullException("dbContext can not be null.");

            _dbContext = dbContext;
            _dbSet = dbContext.Set<T>();
        }

        #region IRepository Members
        public IQueryable<T> GetAll()
        {
            return _dbSet;
        }

        public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
        {
            return _dbSet.Where(predicate);
        }

        public T GetById(int id)
        {
            return _dbSet.Find(id);
        }

        public T Get(Expression<Func<T, bool>> predicate)
        {
            return _dbSet.Where(predicate).SingleOrDefault();
        }

        public void Add(T entity)
        {
            _dbSet.Add(entity);
        }

        public void Update(T entity)
        {
            _dbSet.Attach(entity);
            _dbContext.Entry(entity).State = EntityState.Modified;
        }

        public void Delete(T entity)
        {
            // Eğer sizlerde genelde bir kayıtı silmek yerine IsDelete şeklinde bool bir flag alanı tutuyorsanız,
            // Küçük bir refleciton kodu yardımı ile bunuda otomatikleştirebiliriz.
            if (entity.GetType().GetProperty("IsDelete") != null)
            {
                T _entity = entity;

                _entity.GetType().GetProperty("IsDelete").SetValue(_entity, true);

                this.Update(_entity);
            }
            else
            {
                // Önce entity'nin state'ini kontrol etmeliyiz.
                DbEntityEntry dbEntityEntry = _dbContext.Entry(entity);

                if (dbEntityEntry.State != EntityState.Deleted)
                {
                    dbEntityEntry.State = EntityState.Deleted;
                }
                else
                {
                    _dbSet.Attach(entity);
                    _dbSet.Remove(entity);
                }
            }
        }

        public void Delete(int id)
        {
            var entity = GetById(id);
            if (entity == null) return;
            else
            {
                if (entity.GetType().GetProperty("IsDelete") != null)
                {
                    T _entity = entity;
                    _entity.GetType().GetProperty("IsDelete").SetValue(_entity, true);

                    this.Update(_entity);
                }
                else
                {
                    Delete(entity);
                }
            }
        }
        #endregion
    }
}

Repositorimiz hazır olduğuna göre artık Unit of Work desenini tasarlamaya başlayabiliriz. UnitOfWork isminde bir klasör daha oluşturarak içerisinde IUnitOfWork arayüzünü tanımlıyoruz.

using System;
using RepveUOW.Data.Repositories;

namespace RepveUOW.Data.UnitOfWork
{
    public interface IUnitOfWork : IDisposable
    {
        IRepository<T> GetRepository<T>() where T : class;
        int SaveChanges();
    }
}

İçerisinde istediğimiz tipteki repository’i bize getirecek olan GetRepository generic metotunu ve kaydetme işlemlerini toplu yapabilmemizi sağlayacak olan SaveChanges metotunun imzalarını tanımlıyoruz.

Şimdi yine entity framework için kullanacağımız EFUnitOfWork sınıfımızı UnitOfWork klasörü içerisinde tanımlıyoruz.

using System;
using RepveUOW.Data.Repositories;
using System.Data.Entity;

namespace RepveUOW.Data.UnitOfWork
{
    /// <summary>
    /// EntityFramework için oluşturmuş olduğumuz UnitOfWork.
    /// EFRepository'de olduğu gibi bu şekilde tasarlamamızın ana sebebi ise veritabanına independent(bağımsız) bir durumda kalabilmek. Örneğin MongoDB için ise ilgili provider'ı aracılığı ile MongoDBOfWork tasarlayabiliriz.
    /// </summary>
    public class EFUnitOfWork : IUnitOfWork
    {
        private readonly EFBlogContext _dbContext;

        public EFUnitOfWork(EFBlogContext dbContext)
        {
            Database.SetInitializer<EFBlogContext>(null);

            if (dbContext == null)
                throw new ArgumentNullException("dbContext can not be null.");

            _dbContext = dbContext;

            // Buradan istediğiniz gibi EntityFramework'ü konfigure edebilirsiniz.
            //_dbContext.Configuration.LazyLoadingEnabled = false;
            //_dbContext.Configuration.ValidateOnSaveEnabled = false;
            //_dbContext.Configuration.ProxyCreationEnabled = false;
        }

        #region IUnitOfWork Members
        public IRepository<T> GetRepository<T>() where T : class
        {
            return new EFRepository<T>(_dbContext);
        }

        public int SaveChanges()
        {
            try
            {
                // Transaction işlemleri burada ele alınabilir veya Identity Map kurumsal tasarım kalıbı kullanılarak
                // sadece değişen alanları güncellemeyide sağlayabiliriz.
                return _dbContext.SaveChanges();
            }
            catch
            {
                // Burada DbEntityValidationException hatalarını handle edebiliriz.
                throw;
            }
        }
        #endregion

        #region IDisposable Members
        // Burada IUnitOfWork arayüzüne implemente ettiğimiz IDisposable arayüzünün Dispose Patternini implemente ediyoruz.
        private bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _dbContext.Dispose();
                }
            }

            this.disposed = true;
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

Unit of Work desenide hazır olduğuna göre katmanların son görünümüne bir bakalım:

 

Temel bir alt yapı tasarımımızı tamamladığımıza göre kullanımlarına bakabilmek amaçlı RepveUOW.Presentation.UnitTest isminde bir Unit Test projesi oluşturalım.

RepveUOW.Presentation.UnitTest katmanını oluşturduktan sonra References kısmından Add Reference seçeneğini seçerek, Data katmanında yaptığımız gibi Projects sekmesinden Data ve Model katmanımızı referans olarak ekliyoruz.

EntityTest isminde bir UnitTest sınıfı oluşturarak içerisinde aşağıda açıklamaları ile de görebildiğiniz gibi ilgili test metotlarımızı tanımlıyoruz.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using RepveUOW.Data;
using RepveUOW.Data.UnitOfWork;
using RepveUOW.Data.Model;
using RepveUOW.Data.Repositories;

namespace RepveUOW.Presentation.UnitTest
{
    /// <summary>
    /// Repository ve UOW kullanarak ilgili test metotlarımızı yazıyoruz.
    /// </summary>
    [TestClass]
    public class EntityTest
    {
        // Entity framework için geliştirmiş olduğumuz context. Farklı ORM veya Veritabanı içinde bu context'i değiştirebiliriz.
        private EFBlogContext _dbContext;

        private IUnitOfWork _uow;
        private IRepository<User> _userRepository;
        private IRepository<Category> _categoryRepository;
        private IRepository<Article> _articleRepository;

        [TestInitialize]
        public void TestInitialize()
        {
            _dbContext = new EFBlogContext();

            // EFBlogContext'i kullanıyor olduğumuz için EFUnitOfWork'den türeterek constructor'ına
            // ilgili context'i constructor injection yöntemi ile inject ediyoruz.
            _uow = new EFUnitOfWork(_dbContext);
            _userRepository = new EFRepository<User>(_dbContext);
            _categoryRepository = new EFRepository<Category>(_dbContext);
            _articleRepository = new EFRepository<Article>(_dbContext);
        }

        [TestCleanup]
        public void TestCleanup()
        {
            _dbContext = null;
            _uow.Dispose();
        }


        [TestMethod]
        public void AddUser()
        {
            User user = new User
            {
                FirstName = "Gökhan",
                LastName = "Gökalp",
                CreatedDate = DateTime.Now,
                Email = "gok.gokalp@yahoo.com",
                Password = "123456"
            };

            _userRepository.Add(user);
            int process = _uow.SaveChanges();

            Assert.AreNotEqual(-1, process);
        }

        [TestMethod]
        public void GetUser()
        {
            User user = _userRepository.GetById(1);

            Assert.IsNotNull(user);
        }

        [TestMethod]
        public void UpdateUser()
        {
            User user = _userRepository.GetById(1);

            user.FirstName = "Mehmet";

            _userRepository.Update(user);
            int process = _uow.SaveChanges();

            Assert.AreNotEqual(-1, process);
        }

        [TestMethod]
        public void DeleteUser()
        {
            User user = _userRepository.GetById(1);

            _userRepository.Delete(user);
            int process = _uow.SaveChanges();

            Assert.AreNotEqual(-1, process);
        }
    }
}

Not: Test metotlarını çalıştırmadan önce App.config içerisinde configSections tag’inin altına tekrardan ilgili connection string’i eklemeyi unutmuyoruz.

Bu makalemizde temel bir şekilde kurumsal düzeyde alt yapı tasarlarken Generic Repository ve Unit of Work desenlerinin uygulanışını görmüş olduk.

İlgili proje örneğini aşağıdan indirebilirsiniz.

RepveUOW

 

Gökhan Gökalp

View Comments

  • Selam, makale çok açıklayıcı olmuş teşekkürler.
    Unit test haricinde web tarafında unit of work'ü kullanmak için yine her controller'ın constructor'ında mı unitofwork'e dbcontext'i tanımlayacağız?

    • Merhaba, base bir controller oluşturabilirsiniz. Base controller içerisinde bir IOC tool'u ile istediğiniz concrete UnitOfWork type'ını ve Context'i inject edebilirsiniz.

      • Önerebileceğiniz bir örnek var mı acaba?
        Bu bahsettiğiniz sanırım Unity ile depencity injection yapmakla aynı şey değil mi?

        • Evet Unity ile yapabilirsiniz injection işlemini. İnternet üzerinde unity örneklerini bulabilirsiniz.

  • Merhabalar,

    Açıklayıcı bir makale. Elinize sağlık. Anlayamadığım kısım IUnitWork içinde tanımlanan GetRepository() generic metodunun ne için tanımlandığı ? Kullanım olarak bir satır ibaresi görünmüyor.

    • Merhaba, tanımlanan method aslında EFUnitOfWork sınıfını initialize ettikten sonra repository'leri kolaylıkla oluşturabilmek içindi. Örneğin:

      _userRepository = _uow.GetRepository< User >();

      gibi. Açıkcası bu örnek UI kısmına bir MVC projesi hazırlıyordum, fakat vaktim yetmediği için test case'lerinde bu şekilde kalmış. :)

      Teşekkürler dikkatiniz için.

  • Merhaba,

    Yazılarınızı yeni takip etmeye başladım. Bu faydalı konular hakkında bizleri bilgilendirdiğiniz için öncelikle çok teşekkür ediyorum.

    Şöyle bir sorum olacaktı:

    Repository içerisindeki Delete metodunda neden EntityState.Detached kullanmadığınızı öğrenebilir miyim? Sadece bilgi almak için soruyorum. EntityState.Deleted kodunu özellikle mi kullandınız?

    Bir de UnitOfWork içerisindeki Database.SetInitializer(null); kodunun neden kullanıldığını anlayamadım.

    Yanlış anlamayın lütfen sadece bilgi almak ve öğrenmek için soruyorum.

    Yeni yazılarınızı bekliyorum. ;)

    İyi çalışmalar.

    • Merhaba, öncelikle ben teşekkür ederim.
      İlk soruna gelecek olursak eğer, EntityState'in değişmesinin sebebi; Entityframework kendi içerisinde bir "change tracking" mekanizmasına sahiptir. Kısaca bahsetmem gerekirse eğer, daha önceden çekmiş olduğun entity'lerin state'lerini tutmaktadır üzerinde. Örneğin sadece bir proprety güncelledi isen, bunun update sorgusunda sadece güncellediğin property'leri göndermesi gibidir. Silinme aşamasında ise önce silinip silinmediğini kontrol edip sonrasında context'e attach'leyip siliyoruz. Bu konuyu biraz detaylı google'layabilirsin "change tracking". Bir diğer sorun ise, SetInitializer kısmında herhangi bir Database initializer kullanmamadır null bırakmam. Orada istersen IDatabaseInitializer interface'inden implemente ettiğin bir sınıf ile initialize edebilirsin veya inbuilt olarak içerisinde bulunan, DropCreateDatabaseAlways veya DropCreateDatabaseIfModelChanges gibi sınıfları kullanabilirsin.

      İyi günler dilerim.

      • Vakit ayırıp bilgilendirdiğiniz için çok teşekkür ederim. "change tracking" konusunu araştıracağım.

  • Merhaba, öncelikle makale için teşekkürler. Veri tabanını package manager console'dan Update-Database diyerek oluştururken A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified) şeklinde bir hata alıyorum sebebi ne olabilir ?

    • Merhaba, Migration sırasında bu işlemi yapmadan önce, Package Manager'ın Default project kısmında RepveUOW.Data seçili olduğundan emin olun ve, içerisindeki app.config'de bir connection string'in tanımlı olduğundan emin olun.

      • Default olarak RepveUOW.Data seçili ve app.config'de connectionString'i tanımladım. Bildiğiniz başka bir sebebi olabilir mi ?

        • Sağ kısımda solutiondaki RepveUOW.Data class library sağ tıklayıp set as startup project seç sonra buildleyip tekrar dene olur.

  • Merhaba Gökhan,

    Güzel makale. Yalnız, IUnitOfWork'de fonksiyon ismi olarak SaveChanges kullanmak, interface'i EntityFramework için yazılmış gibi gösteriyor. Commit daha uygun bir isim olabilirdi. Aynı şekilde interface'de Rollback eksik. Onun haricinde güzel makale, eline sağlık.

  • Merhaba,

    Neden repository içerisinde iki tane Delete metodu kullandınız acaba? Sonuçta her ikisi de aynı amacı gerçekleştirmiyor mu? Çünkü Delete(int id) de Delete(T entity) metodunu çağırıyor.

    GetById metodunu test metodu içerisinden çağırsam ve entity'i elde ettikten sonra sadece Delete(T entity) metodunu kullanarak silme işlemi yapsam olmaz mı?

    Ben sadece Delete(T entity) metodunu kullanmak istiyorum ama yanlış yapmak istemiyorum. Bu yüzden müsait bir vaktinizde sizden bilgi almak istiyorum.

    Şimdiden çok teşekkürler.

    • Merhaba,
      İstediğiniz method imzasını kullanabilirsiniz Delete işlemi için, bu sadece kullanım kolaylığı için yazılmış bir method imzası. Yeri gelir elinizde sadece entity'nin Id'si olur, yeri gelir sadece entity olur gibi. Kullanımı size kalmış. Bir nevi delete işlemini ilgili developer arkadaşa, tek satırda yapabilme yeteneğini sağlamak diyebiliriz.

  • Tekrar Merhaba,

    Bu konu başlığı altındaki üçüncü sorum olacak, umarım çok rahatsız etmiyorumdur. :)

    EFRepository içerisinde id'ye göre exists kontrolü yapmak istiyorum. IEntity adında bir interface ürettim ve içerisinde de "Guid id { get; set; }" tanımladım. Ardından bunu EFRepository'si içerisinde şu şekilde tanımladım:

    "public class EFRepository : IRepository where T : class, IEntity"

    Sonra "public bool Exists(Guid id)" metodunu oluşturdum ve sadece aşağıdaki kodu return ettim:

    "return dbSet.Any(e => e.Id == id);"

    Projeyi build ettiğimde EFUnitOfWork sınıfımda "public IRepository GetRepository() where T : class" metodunda hata aldım. Hata da şu şekilde:

    "The type 'T' cannot be used as type parameter 'T' in the generic type or method 'EFRepository'. There is no implicit reference conversion from 'T' to 'SolutionName.Data.Repository.IEntity'."

    Gün boyunca bu hatayı çözmeye çalıştım. Aslında tek yapmak istediğim id'ye göre exists kontrolü yapmak. Bunu dbSet.Find(id) ile de yapabilirim ama Any ile sadece var/yok bilgisini almak istiyorum. Find bana komple kaydı döndürecek. Bana performans açısından daha etkili bir şey gerekiyor. Bunun da Any olacağını düşündüm.

    Bunu en etkin şekilde nasıl yapabilirim acaba? Tavsiyeleriniz benim için çok önemli.

    Teşekkürler.

    • Where T konusunda hata alan arkadaş T nerede sınıfa geçiyor, yani T yi sınıfla özdeştirmemişsin. Class ın veya class ın interfacelerinden birinin template ine T yi geçirmen gerek. Yani class efrepo gibi

    • Üzerinden uzun zaman geçmiş ve elbette çözümü bulunmuştur ama yine de bu soruyu görenler açısından, bu cevabı da eklemek gerek bence.

      Eğer soru içerisinde yazılan kod birebir bu şekilde ise, muhtemel hata şu satırda:

      public class EFRepository : IRepository where T : class, IEntity

      Bu satırın düzeltilip şöyle yazılması gerekir:

      public class EFRepository : IRepository where T : class, IEntity

  • Merhaba hocam,
    Benim iki farklı sorum olacak.

    1- Migrations kısmını enable etmek şart mıdır? Örneğin biz entityframework ile sunucuda çalışan bir veritabanı ile çalışıyorsak nasıl bir yol izlemeliyiz?

    2- UnitOfWork bize ne gibi yarar sağladı bu projede net bir şekilde göremedim. Olmasaydı ne olurdu mesela?

    • Merhabalar,

      1) Öncelikle ef ile sunucuda çalışan bir veritabanı ile çalışıyorsak kısmını tam anlayamadım. Zaten olması gereken değil mi? Buradaki migrations sorunuz sizin yaklaşımınız ile ilgili açıkcası. Code-first, model-first, db-first vb. Code-first gidiyorsanız ve entity modelinizin db deki tablonuzu tamamen prezente etmesini istiyorsanız (herhangi bir değişim karşısında) migration'ı enable etmelisiniz.
      2) UnitOfWork genelde transaction'ları yönetmekte kullanabilirsiniz. Tabi transactional bir işlem gerçekleştiriyorsanız business case'lreinizde. Bunlar dışında zaten EF kendi içerisinde bir uow barındırıyor. Faydasını bu case'de görememenizin sebebi ise belkide test case'lerini biraz daha açmalıydım. Örneğin add user örneğinde sadece user oluşturup en son _uow.SaveChanges(); diyerek ekletmek yerine, bir kaç farklı işlem daha gerçekleştirip tek bir pipe üzerinden _uow.SaveChanges(); dersek daha anlamlı olabilirdi...

      Saygılar.

  • öncelikle yazınız için çok teşekkür ederim. işimi fazlası ile gördü. fakat karşılaştığım bir durum var. bir projede Repository Class'ı abstract olarak tanımlı. ( public abstract class Repository : Irepository where T : class )
    ben unit'Test de
    private IUnitOfWork _uow
    private Irepository _tabloAdiRepository; olarak tanımlayıp

    TestInitialize methodunda ise
    _tabloAdiRepository= new Repository(_dbContext);

    yazdığımda hata alıyorum. abstract class'lar için kullanımı hakkında bilgi verirseniz çok iyi olur. iyi günler.

    • Merhaba, ilgili repository class'ınızın bir concrete repository'si olmalıdır. Zaten _tabloAdiRepository = new Repository diyerek hangi reposiyory olduğunu dbContext üzerinden resolve edemezsiniz. Abstraction üzerinden gitmelisiniz. Saygılar.

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

1 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

5 ay ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

1 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

1 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

1 yıl ago

.NET 7 ile Gelen Bazı Harika Yenilikler

{:tr}Bildiğimiz gibi .NET Conf 2022, 8-10 kasım arasında gerçekleşti. Konferans sırasında ise .NET 7 ve…

1 yıl ago