> ## Documentation Index
> Fetch the complete documentation index at: https://docs.diyanet.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# DbContext & Repository

> DiyanetCleanArchitectureDbContext, OnModelCreating, value object map'leme, EFRepository / CachedRepository, UnitOfWork

`DiyanetCleanArchitectureDbContext` data katmanının merkezidir. Hem klasik `DbContext`'tir hem de `IUnitOfWork` arayüzünü uygular — yani transaction sınırı ve domain event dispatch noktası buradadır.

```csharp theme={null}
public class DiyanetCleanArchitectureDbContext : DbContext, IUnitOfWork
{
    public const string DEFAULT_SCHEMA   = "public";
    public const string MESSAGING_SCHEMA = "messaging";
    // ...
}
```

## DbSet'ler

Tüm aggregate root'lar (ve bazı child entity'ler) `DbSet` olarak tanımlıdır:

```csharp theme={null}
#region DbSet — User & Role
public DbSet<User>           Users           { get; set; }
public DbSet<Role>           Roles           { get; set; }
public DbSet<Permission>     Permissions     { get; set; }
public DbSet<RolePermission> RolePermissions { get; set; }
public DbSet<UserRole>       UserRoles       { get; set; }
#endregion

public DbSet<Citizen>      Citizens      { get; set; }   // website kullanıcıları — Personel'den ayrı aggregate
public DbSet<Organization> Organizations { get; set; }
public DbSet<Branch>       Branches      { get; set; }
public DbSet<StaffMember>  StaffMembers  { get; set; }
public DbSet<City>         Cities        { get; set; }   // Enumeration
public DbSet<District>     Districts     { get; set; }

public DbSet<Faq>          Faqs          { get; set; }
public DbSet<Announcement> Announcements { get; set; }
public DbSet<Center>       Centers       { get; set; }
public DbSet<SiteSettings> SiteSettings  { get; set; }

public DbSet<BagisBasvuruPlan>    BagisBasvuruPlanlari    { get; set; }
public DbSet<BagisBasvuru>        BagisBasvurulari        { get; set; }
public DbSet<EtkinlikBasvuruPlan> EtkinlikBasvuruPlanlari { get; set; }
public DbSet<EtkinlikBasvuru>     EtkinlikBasvurulari     { get; set; }

public DbSet<LegalDocument>        LegalDocuments        { get; set; }
public DbSet<LegalDocumentVersion> LegalDocumentVersions { get; set; }
public DbSet<AdminNotification>     AdminNotifications     { get; set; }
public DbSet<AdminNotificationRead> AdminNotificationReads { get; set; }
public DbSet<SupportTicket>              SupportTickets             { get; set; }
public DbSet<SupportTicketStatusHistory> SupportTicketStatusHistory { get; set; }
public DbSet<SupportTicketComment>       SupportTicketComments      { get; set; }
public DbSet<SupportTicketAttachment>    SupportTicketAttachments   { get; set; }
```

<Note>
  `Citizen` (vatandaş portalı) ile `User` (personel/admin) **ayrı aggregate**'lerdir ve ayrı tablolara yazılır. Aynı kişi iki tarafta da bağımsız kayıt tutar.
</Note>

## OnModelCreating

Üç iş yapar: configuration'ları toplar, MassTransit messaging tablolarını ekler, global soft-delete filtresini kurar.

```csharp theme={null}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 1) Tüm IEntityTypeConfiguration<T>'lar assembly'den toplanır
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(DiyanetCleanArchitectureDbContext).Assembly);

    // 2) MassTransit EF Core Outbox + Inbox — `messaging` şemasında
    modelBuilder.AddInboxStateEntity(e   => e.ToTable("inbox_state",    MESSAGING_SCHEMA));
    modelBuilder.AddOutboxStateEntity(e  => e.ToTable("outbox_state",   MESSAGING_SCHEMA));
    modelBuilder.AddOutboxMessageEntity(e => e.ToTable("outbox_message", MESSAGING_SCHEMA));

    // 3) Global soft-delete query filter — ISoftDeletable uygulayan her tip için
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        if (!typeof(ISoftDeletable).IsAssignableFrom(entityType.ClrType))
            continue;

        var param  = Expression.Parameter(entityType.ClrType, "e");
        var prop   = Expression.Property(param, nameof(ISoftDeletable.DeletedAt));
        var body   = Expression.Equal(prop, Expression.Constant(null, typeof(DateTime?)));
        var lambda = Expression.Lambda(body, param);

        modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
    }

    base.OnModelCreating(modelBuilder);
}
```

Global filtre her `ISoftDeletable` için `DeletedAt == null` koşulu ekler — detaylar [Soft Delete](/data/soft-delete) sayfasında.

## UnitOfWork: SaveEntitiesAsync

`SaveEntitiesAsync` business akışının kullandığı kayıt metodudur. `SaveChanges` çağırır, ardından değişen aggregate'lerin domain event'lerini dispatch eder:

```csharp theme={null}
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
    // AuditInterceptor SavingChanges anında devreye girer — burada ekstra çağrı yok
    int result = await base.SaveChangesAsync(cancellationToken);

    if (result > 0)
        await _mediator.DispatchDomainEventsAsync(this);

    return true;
}
```

`DispatchDomainEventsAsync` (`SeedWork/MediatorExtension.cs`) change tracker'daki her `Entity`'nin event'lerini toplar, temizler ve tek tek yayar. `IntegrationEvent`'ler `IEventBus` (MassTransit Outbox), kalan domain event'ler MediatR ile in-memory işlenir:

```csharp theme={null}
foreach (var domainEvent in domainEvents)
{
    try
    {
        if (domainEvent is IBusEvent busEvent)
        {
            if (busEvent is IntegrationEvent integrationEvent)
                await ctx._eventBus.PublishAsync(integrationEvent);
        }
        else
        {
            await mediator.Publish(domainEvent);
        }
    }
    catch (Exception ex)
    {
        // Akış kesilmesin — kalan event'ler işlensin; ama silent failure olmasın
        ctx.GetService<ILoggerFactory>()?
           .CreateLogger(typeof(MediatorExtension))
           .LogError(ex, "[DomainEventDispatch] Event işlenemedi: {EventType}", domainEvent.GetType().Name);
    }
}
```

<Warning>
  Bir event handler'ı patlarsa akış kesilmez (`catch`/`continue`) ama hata DbContext logger'ına yazılır. Eski versiyonda `catch` sessizdi ve hatalar görünmüyordu.
</Warning>

`DbContext` ayrıca manuel transaction desteği sunar: `BeginTransactionAsync` (ReadCommitted), `CommitTransactionAsync`, `RollbackTransaction`. Çoğu komut tek `SaveEntitiesAsync` ile yetinir; çapraz-aggregate atomikliği gereken yerlerde açık transaction kullanılır.

## EntityTypeConfiguration örnekleri

### Value object'leri kolona çevirme — HasConversion

`User.Email`, `User.Phone`, `User.FullName` value object'tir; tek kolona serialize edilir:

```csharp theme={null}
builder.Property(u => u.Email)
    .HasConversion(e => e.Value, v => new Email(v))
    .HasColumnName("email")
    .HasMaxLength(255)
    .IsRequired(false);

builder.Property(u => u.Phone)
    .HasConversion(p => p.Value, v => new Phone(v))
    .HasColumnName("phone")
    .HasMaxLength(25)
    .IsRequired(false);
```

### Owned type — OwnsOne

TOTP konfigürasyonu nested owned type'tır (aynı `users` satırına kolon olarak yazılır):

```csharp theme={null}
builder.OwnsOne(u => u.Totp, totp =>
{
    totp.Property(t => t.IsEnabled).HasColumnName("totp_enabled");
    totp.Property(t => t.LastUsedTimeStep).HasColumnName("totp_last_used_time_step");

    totp.OwnsOne(t => t.Secret, secret =>
    {
        secret.Property(s => s.EncryptedValue).HasColumnName("totp_secret").IsRequired();
    });
});
```

`Citizen.ProfileImage` ise owned type olmasına rağmen ayrı tabloya (`citizen_profile_images`) yazılır — `bytea` payload'ı liste sorgularını şişirmesin diye `AsSplitQuery` ile lazy yüklenir:

```csharp theme={null}
builder.OwnsOne(v => v.ProfileImage, img =>
{
    img.ToTable("citizen_profile_images", DiyanetCleanArchitectureDbContext.DEFAULT_SCHEMA);
    img.Property(p => p.Veri).HasColumnName("veri").HasColumnType("bytea");
    img.Property(p => p.ContentType).HasColumnName("content_type").HasMaxLength(50);
});
```

### DB-generated referans numarası — IDENTITY kolon

`User.ReferenceNumber` PostgreSQL IDENTITY kolonudur (insan-okuyabilir, artan numara), 100'den başlar ve insert sonrası salt-okunur:

```csharp theme={null}
builder.Property(u => u.ReferenceNumber)
    .HasColumnName("reference_number")
    .HasColumnType("bigint")
    .ValueGeneratedOnAdd()
    .UseIdentityAlwaysColumn()          // PostgreSQL GENERATED ALWAYS AS IDENTITY
    .HasIdentityOptions(startValue: 100);

builder.Metadata
    .FindProperty(nameof(User.ReferenceNumber))!
    .SetAfterSaveBehavior(PropertySaveBehavior.Ignore); // read-only after insert
```

### Optimistic concurrency

`users` tablosu shadow `row_version` kolonuyla optimistic concurrency control yapar:

```csharp theme={null}
builder.Property<byte[]>("row_version").IsRowVersion();
```

## Repository'ler

İki ayrı path var: **write** (`IRepository<T>`) ve **read** (`IReadRepository<T>`). İkisi de `Ardalis.Specification` tabanlıdır.

### EFRepository — write tarafı

```csharp theme={null}
public class EFRepository<T> : RepositoryBase<T>, IRepository<T> where T : class, IAggregateRoot
{
    protected readonly DiyanetCleanArchitectureDbContext _context;
    public IUnitOfWork UnitOfWork => _context;

    public EFRepository(DiyanetCleanArchitectureDbContext context) : base(context) { /* ... */ }

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        throw new Exception("Dont use this SaveChangesAsync() It's forbidden!");
    }
}
```

<Warning>
  `EFRepository.SaveChangesAsync` **bilerek exception fırlatır**. Kayıt repository üzerinden değil, `UnitOfWork.SaveEntitiesAsync` (yani DbContext) üzerinden yapılır — böylece domain event dispatch atlanmaz. Repository'ler sadece change tracker'ı düzenler (`AddAsync`, `UpdateAsync`, `DeleteAsync`).
</Warning>

`UnitOfWork` property'si `_context`'in ta kendisidir; handler `_repository.UnitOfWork.SaveEntitiesAsync(...)` ile kaydeder.

### CachedRepository — read tarafı (HybridCache decorator)

`IReadRepository<T>` implementasyonu, gerçek sorguyu `EFCacheRepository<T>`'ye delege eder ve spec cache-enabled ise HybridCache (L1 memory + L2 Redis) ile sarmalar:

```csharp theme={null}
public async Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken ct = default)
{
    if (CacheDisabled(spec))
        return await _source.ListAsync(spec, ct);

    var key = $"{spec.CacheKey}:List";
    return await _cache.GetOrCreateAsync(
        key,
        async c => await _source.ListAsync(spec, c),
        BuildOptions(spec),
        TagsOf(spec),
        ct);
}
```

Cache yalnızca iki şart birden sağlanınca devreye girer: global `Cache:Enabled` açık **ve** spec'te `EnableCache(...)` zinciri kurulmuş olmalı:

```csharp theme={null}
private bool CacheDisabled<TSpec>(TSpec spec) where TSpec : ISpecification<T>
    => !_options.CurrentValue.Enabled
       || !spec.CacheEnabled
       || string.IsNullOrWhiteSpace(spec.CacheKey);
```

TTL ve tag'ler spec'ten okunur; TTL belirtilmemişse config'teki `Cache:L2:Ttl` kullanılır. Handler/query cache'ten habersizdir — cache tamamen decorator'ın işidir.

## DI kaydı

`AddInfrastructureEFCore` (`DependencyInjection.cs`) tüm bağımlılıkları kurar:

```csharp theme={null}
services.AddDbContext<DiyanetCleanArchitectureDbContext>((sp, options) =>
{
    options.UseNpgsql(configuration.GetConnectionString("DiyanetCleanArchitectureDatabaseConnection"))
           .UseSnakeCaseNamingConvention();
    // AuditInterceptor scoped olduğu için buraya EKLENMIYOR — OnConfiguring'de ekleniyor.
});

services.AddScoped(typeof(IReadRepository<>), typeof(CachedRepository<>));
services.AddScoped(typeof(IRepository<>),     typeof(EFRepository<>));
services.AddScoped(typeof(EFRepository<>));
services.AddScoped(typeof(EFCacheRepository<>));

// UnitOfWork = DbContext'in kendisi
services.AddScoped<IUnitOfWork>(sp => sp.GetRequiredService<DiyanetCleanArchitectureDbContext>());
```

<Note>
  Connection string adı sabittir: **`DiyanetCleanArchitectureDatabaseConnection`**. Aynı isim `DesignTimeDbContextFactoryBase` tarafından da kullanılır (migration araçları için).
</Note>

## İlgili

<CardGroup cols={2}>
  <Card title="Audit Interceptor" href="/data/audit-interceptor">
    AuditInterceptor'ın SaveChanges anında nasıl çalıştığı.
  </Card>

  <Card title="Soft Delete" href="/data/soft-delete">
    Global query filter ve restore senaryoları.
  </Card>

  <Card title="Migrations" href="/data/migrations">
    Bu DbContext'ten migration üretme ve uygulama.
  </Card>

  <Card title="Data Genel Bakış" href="/data/overview">
    Katmanın rolü, şemalar, klasör yapısı.
  </Card>
</CardGroup>
