EFEF Core Handbook

REFERANS

Custom Conventions — EF Core 7+

Modeldeki tüm entity'lere otomatik uygulanan kurallar. "Her string en fazla 256 karakter olsun", "Her tabloda CreatedAt shadow property olsun", "Her FK'ya index koy" gibi DRY kurallar bir kere yazılır, tüm entity'lere uygulanır — her config'te tekrar yazmaya gerek kalmaz.

IModelFinalizingConvention — Tüm String'lere MaxLength

public class MaxStringLengthConvention : IModelFinalizingConvention
{
    private readonly int _maxLength;

    public MaxStringLengthConvention(int maxLength = 256)
    {
        _maxLength = maxLength;
    }

    public void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())
        {
            foreach (var property in entity.GetProperties()
                .Where(p => p.ClrType == typeof(string)))
            {
                // Sadece açıkça MaxLength belirtilmemiş olanları ayarla
                if (!property.GetMaxLength().HasValue)
                {
                    property.Builder.HasMaxLength(_maxLength);
                }
            }
        }
    }
}

Convention ile Otomatik Soft Delete Filter

public class SoftDeleteConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())
        {
            var isDeletedProperty = entity.FindProperty("IsDeleted");
            if (isDeletedProperty is not null && isDeletedProperty.ClrType == typeof(bool))
            {
                var parameter = Expression.Parameter(entity.ClrType, "e");
                var prop = Expression.Property(parameter, "IsDeleted");
                var condition = Expression.Equal(prop, Expression.Constant(false));
                var lambda = Expression.Lambda(condition, parameter);

                entity.SetQueryFilter(lambda);
            }
        }
    }
}

Convention ile Otomatik Index (FK'lara)

public class ForeignKeyIndexConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entity in modelBuilder.Metadata.GetEntityTypes())
        {
            foreach (var fk in entity.GetForeignKeys())
            {
                // FK property'lerine index oluştur (yoksa)
                var properties = fk.Properties;
                var existingIndex = entity.FindIndex(properties);
                if (existingIndex is null)
                {
                    entity.AddIndex(properties);
                }
            }
        }
    }
}

Kayıt

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    // Custom conventions
    configurationBuilder.Conventions.Add(_ => new MaxStringLengthConvention(256));
    configurationBuilder.Conventions.Add(_ => new SoftDeleteConvention());

    // Property type conventions (daha basit)
    configurationBuilder.Properties<string>().HaveMaxLength(256);
    configurationBuilder.Properties<decimal>().HavePrecision(18, 2);
    
    // Tüm DateTime'lar → datetime2
    configurationBuilder.Properties<DateTime>().HaveColumnType("datetime2");
}

ConfigureConventions vs OnModelCreating

ConfigureConventions OnModelCreating
Çalışma sırası Önce Sonra (override eder)
Amaç Genel kurallar Entity-specific config
Öncelik Düşük — explicit config kazanır Yüksek — son söz
Kullanım DRY global kurallar Tek entity'e özel detay

ConfigureConventions'da belirlenen kural, IEntityTypeConfiguration'da override edilebilir. Yani önce genel kural yazılır, istisnalar entity config'de belirtilir.