Repository Pattern, veri erişim katmanını soyutlayan ve iş mantığını veri erişiminden ayıran önemli bir tasarım desenidir. GenericRepository yaklaşımı ile ortak CRUD operasyonlarını tekrar kullanılabilir hale getirebilir, kod tekrarını önleyebilir ve daha temiz bir mimari oluşturabilirsiniz.

Repository Pattern Nedir?

Veri Erişim Soyutlaması

Repository Pattern, veri erişim mantığını kapsülleyerek, iş mantığını veri tabanı detaylarından ayıran bir tasarım desenidir.

Neden Repository Pattern?

Katman Ayrımı

İş mantığı ile veri erişimi arasında net bir ayrım sağlar

Test Edilebilirlik

Mock nesnelerle kolay unit test yazılabilir

Sürdürülebilirlik

Veri erişim değişiklikleri merkezi olarak yapılır

Esneklik

Farklı veri kaynaklarına geçiş kolaylaşır

Klasik Repository vs GenericRepository

Klasik Repository

Geleneksel

Her entity için ayrı repository sınıfı oluşturulur

IProductRepository
ICategoryRepository
ICustomerRepository
// Her entity için ayrı...
Avantajlar
  • Entity'ye özel metodlar
  • Tip güvenliği
  • Açık interface
Dezavantajlar
  • Kod tekrarı
  • Çok fazla sınıf
  • Bakım zorluğu

GenericRepository

Modern

Tek generic sınıf ile tüm entity'ler için ortak operasyonlar

IGenericRepository<T>
// Tüm entity'ler için tek interface
GenericRepository<Product>
GenericRepository<Category>
Avantajlar
  • Kod tekrarı yok
  • Hızlı geliştirme
  • Tek merkezi kod
Dezavantajlar
  • Entity özel metodlar zor
  • Over-abstraction riski
  • Interface pollution

GenericRepository Implementasyonu

1

Interface Tanımı

Önce generic repository interface'imizi tanımlayalım

IGenericRepository.cs C#
public interface IGenericRepository<T> where T : class
{
    // Okuma operasyonları
    Task<T?> GetByIdAsync(int id);
    Task<T?> GetByIdAsync(Guid id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
    Task<T?> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate);
    
    // Sayım operasyonları
    Task<int> CountAsync();
    Task<int> CountAsync(Expression<Func<T, bool>> predicate);
    Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate);
    
    // Yazma operasyonları
    Task<T> AddAsync(T entity);
    Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities);
    void Update(T entity);
    void UpdateRange(IEnumerable<T> entities);
    void Delete(T entity);
    void Delete(int id);
    void Delete(Guid id);
    void DeleteRange(IEnumerable<T> entities);
    
    // Sayfalama
    Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize);
    Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize, 
        Expression<Func<T, bool>> predicate);
}
2

Entity Framework Implementation

Entity Framework kullanarak generic repository'yi implement edelim

GenericRepository.cs C#
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;

    public GenericRepository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public virtual async Task<T?> GetByIdAsync(int id)
    {
        return await _dbSet.FindAsync(id);
    }

    public virtual async Task<T?> GetByIdAsync(Guid id)
    {
        return await _dbSet.FindAsync(id);
    }

    public virtual async Task<IEnumerable<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }

    public virtual async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
    {
        return await _dbSet.Where(predicate).ToListAsync();
    }

    public virtual async Task<T?> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
    {
        return await _dbSet.FirstOrDefaultAsync(predicate);
    }

    public virtual async Task<int> CountAsync()
    {
        return await _dbSet.CountAsync();
    }

    public virtual async Task<int> CountAsync(Expression<Func<T, bool>> predicate)
    {
        return await _dbSet.CountAsync(predicate);
    }

    public virtual async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate)
    {
        return await _dbSet.AnyAsync(predicate);
    }

    public virtual async Task<T> AddAsync(T entity)
    {
        var result = await _dbSet.AddAsync(entity);
        return result.Entity;
    }

    public virtual async Task<IEnumerable<T>> AddRangeAsync(IEnumerable<T> entities)
    {
        await _dbSet.AddRangeAsync(entities);
        return entities;
    }

    public virtual void Update(T entity)
    {
        _dbSet.Update(entity);
    }

    public virtual void UpdateRange(IEnumerable<T> entities)
    {
        _dbSet.UpdateRange(entities);
    }

    public virtual void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }

    public virtual void Delete(int id)
    {
        var entity = _dbSet.Find(id);
        if (entity != null)
            _dbSet.Remove(entity);
    }

    public virtual void Delete(Guid id)
    {
        var entity = _dbSet.Find(id);
        if (entity != null)
            _dbSet.Remove(entity);
    }

    public virtual void DeleteRange(IEnumerable<T> entities)
    {
        _dbSet.RemoveRange(entities);
    }

    public virtual async Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize)
    {
        return await _dbSet
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
    }

    public virtual async Task<IEnumerable<T>> GetPagedAsync(int page, int pageSize, 
        Expression<Func<T, bool>> predicate)
    {
        return await _dbSet
            .Where(predicate)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
    }
}
3

Unit of Work Pattern

Repository'leri koordine etmek için Unit of Work pattern'ı ekleyelim

IUnitOfWork.cs C#
public interface IUnitOfWork : IDisposable
{
    IGenericRepository<T> Repository<T>() where T : class;
    Task<int> SaveChangesAsync();
    int SaveChanges();
    Task BeginTransactionAsync();
    Task CommitTransactionAsync();
    Task RollbackTransactionAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private readonly Dictionary<Type, object> _repositories;
    private IDbContextTransaction? _transaction;

    public UnitOfWork(DbContext context)
    {
        _context = context;
        _repositories = new Dictionary<Type, object>();
    }

    public IGenericRepository<T> Repository<T>() where T : class
    {
        var type = typeof(T);
        
        if (!_repositories.ContainsKey(type))
        {
            var repositoryType = typeof(GenericRepository<>).MakeGenericType(type);
            var repositoryInstance = Activator.CreateInstance(repositoryType, _context);
            _repositories[type] = repositoryInstance!;
        }
        
        return (IGenericRepository<T>)_repositories[type];
    }

    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public int SaveChanges()
    {
        return _context.SaveChanges();
    }

    public async Task BeginTransactionAsync()
    {
        _transaction = await _context.Database.BeginTransactionAsync();
    }

    public async Task CommitTransactionAsync()
    {
        if (_transaction != null)
        {
            await _transaction.CommitAsync();
            await _transaction.DisposeAsync();
            _transaction = null;
        }
    }

    public async Task RollbackTransactionAsync()
    {
        if (_transaction != null)
        {
            await _transaction.RollbackAsync();
            await _transaction.DisposeAsync();
            _transaction = null;
        }
    }

    public void Dispose()
    {
        _transaction?.Dispose();
        _context?.Dispose();
    }
}

Dependency Injection Konfigürasyonu

Program.cs C#
var builder = WebApplication.CreateBuilder(args);

// DbContext registration
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Generic Repository registration
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

// Unit of Work registration
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();

// Specific repositories (if needed)
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();

var app = builder.Build();

Kullanım Örnekleri

Controller'da Kullanım

ProductController.cs C#
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public ProductController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        var products = await _unitOfWork.Repository<Product>().GetAllAsync();
        return Ok(products);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        
        if (product == null)
            return NotFound();
            
        return Ok(product);
    }

    [HttpGet("search")]
    public async Task<ActionResult<IEnumerable<Product>>> SearchProducts(string name)
    {
        var products = await _unitOfWork.Repository<Product>()
            .FindAsync(p => p.Name.Contains(name));
            
        return Ok(products);
    }

    [HttpPost]
    public async Task<ActionResult<Product>> CreateProduct(Product product)
    {
        await _unitOfWork.Repository<Product>().AddAsync(product);
        await _unitOfWork.SaveChangesAsync();
        
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateProduct(int id, Product product)
    {
        if (id != product.Id)
            return BadRequest();

        var existingProduct = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        if (existingProduct == null)
            return NotFound();

        _unitOfWork.Repository<Product>().Update(product);
        await _unitOfWork.SaveChangesAsync();
        
        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        if (product == null)
            return NotFound();

        _unitOfWork.Repository<Product>().Delete(product);
        await _unitOfWork.SaveChangesAsync();
        
        return NoContent();
    }
}

Service Layer'da Kullanım

ProductService.cs C#
public interface IProductService
{
    Task<ProductDto> CreateProductAsync(CreateProductRequest request);
    Task<ProductDto?> GetProductByIdAsync(int id);
    Task<IEnumerable<ProductDto>> GetActiveProductsAsync();
    Task<bool> UpdateProductAsync(int id, UpdateProductRequest request);
    Task<bool> DeleteProductAsync(int id);
}

public class ProductService : IProductService
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    private readonly ILogger<ProductService> _logger;

    public ProductService(
        IUnitOfWork unitOfWork, 
        IMapper mapper, 
        ILogger<ProductService> logger)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _logger = logger;
    }

    public async Task<ProductDto> CreateProductAsync(CreateProductRequest request)
    {
        _logger.LogInformation("Creating product: {Name}", request.Name);

        // Business logic validation
        var existingProduct = await _unitOfWork.Repository<Product>()
            .FirstOrDefaultAsync(p => p.Name == request.Name);
            
        if (existingProduct != null)
            throw new BusinessException("Product with this name already exists");

        // Create entity
        var product = _mapper.Map<Product>(request);
        product.CreatedAt = DateTime.UtcNow;
        product.IsActive = true;

        // Save to database
        await _unitOfWork.Repository<Product>().AddAsync(product);
        await _unitOfWork.SaveChangesAsync();

        _logger.LogInformation("Product created successfully: {Id}", product.Id);

        return _mapper.Map<ProductDto>(product);
    }

    public async Task<ProductDto?> GetProductByIdAsync(int id)
    {
        var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        return product != null ? _mapper.Map<ProductDto>(product) : null;
    }

    public async Task<IEnumerable<ProductDto>> GetActiveProductsAsync()
    {
        var products = await _unitOfWork.Repository<Product>()
            .FindAsync(p => p.IsActive);
            
        return _mapper.Map<IEnumerable<ProductDto>>(products);
    }

    public async Task<bool> UpdateProductAsync(int id, UpdateProductRequest request)
    {
        var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        if (product == null)
            return false;

        _mapper.Map(request, product);
        product.UpdatedAt = DateTime.UtcNow;

        _unitOfWork.Repository<Product>().Update(product);
        await _unitOfWork.SaveChangesAsync();

        return true;
    }

    public async Task<bool> DeleteProductAsync(int id)
    {
        var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
        if (product == null)
            return false;

        _unitOfWork.Repository<Product>().Delete(product);
        await _unitOfWork.SaveChangesAsync();

        return true;
    }
}

Gelişmiş Özellikler

Include Support

Related entity'leri yüklemek için Include desteği ekleyelim

Enhanced IGenericRepository C#
public interface IGenericRepository<T> where T : class
{
    // Existing methods...
    
    Task<T?> GetByIdAsync(int id, params Expression<Func<T, object>>[] includes);
    Task<IEnumerable<T>> GetAllAsync(params Expression<Func<T, object>>[] includes);
    Task<IEnumerable<T>> FindAsync(
        Expression<Func<T, bool>> predicate, 
        params Expression<Func<T, object>>[] includes);
}

// Implementation
public virtual async Task<T?> GetByIdAsync(int id, params Expression<Func<T, object>>[] includes)
{
    IQueryable<T> query = _dbSet;
    
    foreach (var include in includes)
    {
        query = query.Include(include);
    }
    
    return await query.FirstOrDefaultAsync(GetIdPredicate(id));
}

// Usage
var product = await _unitOfWork.Repository<Product>()
    .GetByIdAsync(1, p => p.Category, p => p.Reviews);

Ordering Support

Sıralama desteği ekleyerek daha esnek sorgular yapabiliriz

Ordering Extension C#
public interface IGenericRepository<T> where T : class
{
    Task<IEnumerable<T>> GetAllOrderedAsync<TKey>(
        Expression<Func<T, TKey>> orderBy, 
        bool ascending = true);
        
    Task<IEnumerable<T>> FindOrderedAsync<TKey>(
        Expression<Func<T, bool>> predicate,
        Expression<Func<T, TKey>> orderBy,
        bool ascending = true);
}

// Implementation
public virtual async Task<IEnumerable<T>> GetAllOrderedAsync<TKey>(
    Expression<Func<T, TKey>> orderBy, 
    bool ascending = true)
{
    var query = ascending 
        ? _dbSet.OrderBy(orderBy) 
        : _dbSet.OrderByDescending(orderBy);
        
    return await query.ToListAsync();
}

// Usage
var products = await _unitOfWork.Repository<Product>()
    .GetAllOrderedAsync(p => p.Name, ascending: true);

Specification Pattern

Karmaşık sorgu mantığını kapsüllemek için Specification pattern'ı entegre edelim

Specification Pattern C#
public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    Expression<Func<T, object>>? OrderBy { get; }
    Expression<Func<T, object>>? OrderByDescending { get; }
    int Take { get; }
    int Skip { get; }
    bool IsPagingEnabled { get; }
}

public class ProductSpecification : BaseSpecification<Product>
{
    public ProductSpecification(string name, bool? isActive = null)
    {
        if (!string.IsNullOrEmpty(name))
            Criteria = p => p.Name.Contains(name);
            
        if (isActive.HasValue)
            Criteria = p => p.IsActive == isActive.Value;
            
        AddInclude(p => p.Category);
        AddOrderBy(p => p.Name);
    }
}

// Repository'de kullanım
public interface IGenericRepository<T> where T : class
{
    Task<IEnumerable<T>> FindWithSpecAsync(ISpecification<T> spec);
    Task<T?> FirstOrDefaultWithSpecAsync(ISpecification<T> spec);
    Task<int> CountWithSpecAsync(ISpecification<T> spec);
}

// Usage
var spec = new ProductSpecification("laptop", isActive: true);
var products = await _unitOfWork.Repository<Product>()
    .FindWithSpecAsync(spec);

Unit Testing

Repository Test Örneği

GenericRepositoryTests.cs C#
public class GenericRepositoryTests : IDisposable
{
    private readonly DbContextOptions<TestDbContext> _options;
    private readonly TestDbContext _context;
    private readonly GenericRepository<Product> _repository;

    public GenericRepositoryTests()
    {
        _options = new DbContextOptionsBuilder<TestDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
            
        _context = new TestDbContext(_options);
        _repository = new GenericRepository<Product>(_context);
    }

    [Fact]
    public async Task AddAsync_ShouldAddEntity_WhenEntityIsValid()
    {
        // Arrange
        var product = new Product 
        { 
            Name = "Test Product", 
            Price = 100, 
            IsActive = true 
        };

        // Act
        var result = await _repository.AddAsync(product);
        await _context.SaveChangesAsync();

        // Assert
        result.Should().NotBeNull();
        result.Id.Should().BeGreaterThan(0);
        
        var savedProduct = await _repository.GetByIdAsync(result.Id);
        savedProduct.Should().NotBeNull();
        savedProduct!.Name.Should().Be("Test Product");
    }

    [Fact]
    public async Task GetByIdAsync_ShouldReturnEntity_WhenEntityExists()
    {
        // Arrange
        var product = new Product 
        { 
            Name = "Test Product", 
            Price = 100, 
            IsActive = true 
        };
        await _repository.AddAsync(product);
        await _context.SaveChangesAsync();

        // Act
        var result = await _repository.GetByIdAsync(product.Id);

        // Assert
        result.Should().NotBeNull();
        result!.Name.Should().Be("Test Product");
    }

    [Fact]
    public async Task FindAsync_ShouldReturnMatchingEntities_WhenPredicateMatches()
    {
        // Arrange
        var products = new[]
        {
            new Product { Name = "Active Product", IsActive = true },
            new Product { Name = "Inactive Product", IsActive = false },
            new Product { Name = "Another Active", IsActive = true }
        };
        
        await _repository.AddRangeAsync(products);
        await _context.SaveChangesAsync();

        // Act
        var result = await _repository.FindAsync(p => p.IsActive);

        // Assert
        result.Should().HaveCount(2);
        result.All(p => p.IsActive).Should().BeTrue();
    }

    [Fact]
    public async Task CountAsync_ShouldReturnCorrectCount_WhenEntitiesExist()
    {
        // Arrange
        var products = new[]
        {
            new Product { Name = "Product 1", IsActive = true },
            new Product { Name = "Product 2", IsActive = false },
            new Product { Name = "Product 3", IsActive = true }
        };
        
        await _repository.AddRangeAsync(products);
        await _context.SaveChangesAsync();

        // Act
        var totalCount = await _repository.CountAsync();
        var activeCount = await _repository.CountAsync(p => p.IsActive);

        // Assert
        totalCount.Should().Be(3);
        activeCount.Should().Be(2);
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Service Test ile Mock Kullanımı

ProductServiceTests.cs C#
public class ProductServiceTests
{
    private readonly Mock<IUnitOfWork> _mockUnitOfWork;
    private readonly Mock<IGenericRepository<Product>> _mockProductRepository;
    private readonly Mock<IMapper> _mockMapper;
    private readonly Mock<ILogger<ProductService>> _mockLogger;
    private readonly ProductService _productService;

    public ProductServiceTests()
    {
        _mockUnitOfWork = new Mock<IUnitOfWork>();
        _mockProductRepository = new Mock<IGenericRepository<Product>>();
        _mockMapper = new Mock<IMapper>();
        _mockLogger = new Mock<ILogger<ProductService>>();

        _mockUnitOfWork.Setup(u => u.Repository<Product>())
            .Returns(_mockProductRepository.Object);

        _productService = new ProductService(
            _mockUnitOfWork.Object,
            _mockMapper.Object,
            _mockLogger.Object);
    }

    [Fact]
    public async Task CreateProductAsync_ShouldCreateProduct_WhenRequestIsValid()
    {
        // Arrange
        var request = new CreateProductRequest { Name = "Test Product", Price = 100 };
        var product = new Product { Id = 1, Name = "Test Product", Price = 100 };
        var productDto = new ProductDto { Id = 1, Name = "Test Product", Price = 100 };

        _mockProductRepository.Setup(r => r.FirstOrDefaultAsync(It.IsAny<Expression<Func<Product, bool>>>()))
            .ReturnsAsync((Product?)null);

        _mockMapper.Setup(m => m.Map<Product>(request))
            .Returns(product);

        _mockProductRepository.Setup(r => r.AddAsync(It.IsAny<Product>()))
            .ReturnsAsync(product);

        _mockMapper.Setup(m => m.Map<ProductDto>(product))
            .Returns(productDto);

        // Act
        var result = await _productService.CreateProductAsync(request);

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("Test Product");
        
        _mockProductRepository.Verify(r => r.AddAsync(It.IsAny<Product>()), Times.Once);
        _mockUnitOfWork.Verify(u => u.SaveChangesAsync(), Times.Once);
    }

    [Fact]
    public async Task CreateProductAsync_ShouldThrowException_WhenProductNameExists()
    {
        // Arrange
        var request = new CreateProductRequest { Name = "Existing Product", Price = 100 };
        var existingProduct = new Product { Id = 1, Name = "Existing Product" };

        _mockProductRepository.Setup(r => r.FirstOrDefaultAsync(It.IsAny<Expression<Func<Product, bool>>>()))
            .ReturnsAsync(existingProduct);

        // Act & Assert
        var action = async () => await _productService.CreateProductAsync(request);
        await action.Should().ThrowAsync<BusinessException>()
            .WithMessage("Product with this name already exists");

        _mockProductRepository.Verify(r => r.AddAsync(It.IsAny<Product>()), Times.Never);
        _mockUnitOfWork.Verify(u => u.SaveChangesAsync(), Times.Never);
    }
}

Avantajlar vs Dezavantajlar

Avantajlar

Kod Tekrarını Önler

CRUD operasyonları tek yerde tanımlanır

Hızlı Geliştirme

Yeni entity'ler için hemen kullanılabilir

Tutarlı API

Tüm repository'ler aynı interface'i kullanır

Test Edilebilirlik

Mock nesneler kolayca oluşturulabilir

Merkezi Bakım

Değişiklikler tek yerden yapılır

Dezavantajlar

Over-Abstraction

Gereksiz soyutlama katmanı oluşturabilir

Entity Özel Metodlar

Özel business logic implementasyonu zor

Interface Pollution

Kullanılmayan metodlar interface'i kirletir

Performance Sorunları

Generic yapı bazen optimize olmayabilir

EF Core Abstraction

EF Core zaten repository pattern implement eder

En İyi Pratikler

Yapılması Gerekenler

Unit of Work pattern ile birlikte kullanın
Entity'ye özel metodlar için ayrı repository oluşturun
Async metodları kullanın
Include operasyonları için ayrı metodlar tanımlayın
Specification pattern ile karmaşık sorguları yönetin
Repository'leri interface ile soyutlayın

Yapılmaması Gerekenler

Repository'de business logic yazmayın
DbContext'i controller'da direkt kullanmayın
Generic repository'yi her durumda zorlamayın
Kullanılmayan metodları interface'e eklemeyin
Transaction yönetimini repository'de yapmayın
Complex join'leri generic repository ile zorlamayın

Alternatif Yaklaşımlar

MediatR + CQRS

Modern

Repository pattern yerine MediatR ile Command/Query ayrımı

public class GetProductQuery : IRequest<ProductDto>
public class CreateProductCommand : IRequest<int>
// Handler'lar direkt DbContext kullanır
Avantajlar: Single Responsibility, Clear Intent, Easy Testing

Direct EF Core

Basit

Repository olmadan doğrudan EF Core kullanımı

// Service'te direkt DbContext
var products = await _context.Products
    .Where(p => p.IsActive)
    .ToListAsync();
Avantajlar: Less Abstraction, EF Core Features, Performance

Specification Only

Odaklanmış

Sadece Specification pattern ile query abstraction

var spec = new ActiveProductsSpec();
var products = await _context.Products
    .Where(spec.Criteria)
    .ToListAsync();
Avantajlar: Query Reusability, Business Rules, Testability

Sonuç

GenericRepository Pattern, orta ölçekli projeler için kod tekrarını önleyen ve geliştirme hızını artıran faydalı bir yaklaşımdır. Ancak her projeye uygun olmayabilir. Proje gereksinimlerinizi, ekip deneyimini ve karmaşıklık seviyesini değerlendirerek karar vermeniz önemlidir.

Karar Verme Rehberi

GenericRepository Kullanın

  • Orta ölçekli projelerde
  • Hızlı prototype geliştirmede
  • Çok entity'li CRUD uygulamalarda
  • Team standardizasyonu gerektiğinde

GenericRepository Kullanmayın

  • Karmaşık business logic'li projelerde
  • Performance kritik uygulamalarda
  • Çok özel sorgu gereksinimlerinde
  • Microservice mimarilerinde

Sonraki Adımlar

  • Kendi projenizde GenericRepository implement edin
  • Unit of Work pattern'ı entegre edin
  • Specification pattern ile genişletin
  • MediatR alternatifini değerlendirin
  • Performance testleri yapın