PHPackages                             yakupeyisan/codeigniter4-entity-framework - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. yakupeyisan/codeigniter4-entity-framework

ActiveLibrary[Database &amp; ORM](/categories/database)

yakupeyisan/codeigniter4-entity-framework
=========================================

Entity Framework Core for CodeIgniter 4 - A comprehensive ORM solution inspired by .NET Entity Framework Core

1.3.3(5mo ago)010MITPHPPHP ^8.1

Since Nov 28Pushed 1mo agoCompare

[ Source](https://github.com/yakupeyisan/codeigniter4-entity-framework)[ Packagist](https://packagist.org/packages/yakupeyisan/codeigniter4-entity-framework)[ RSS](/packages/yakupeyisan-codeigniter4-entity-framework/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (8)Dependencies (1)Versions (11)Used By (0)

CodeIgniter 4 Entity Framework
==============================

[](#codeigniter-4-entity-framework)

Entity Framework Core for CodeIgniter 4 - A comprehensive ORM solution inspired by .NET Entity Framework Core.

VSCode Extension
----------------

[](#vscode-extension)

A VSCode extension is available to enhance your Entity Framework development experience with C#-like property outline, navigation properties, and attribute highlighting. See [vscode-extension/README.md](vscode-extension/README.md) for details.

Installation
------------

[](#installation)

```
composer require yakupeyisan/codeigniter4-entity-framework
```

Supported Database Providers
----------------------------

[](#supported-database-providers)

This package supports multiple database providers with optimized implementations:

- ✅ **MySQL / MariaDB** - Full support with CASE WHEN batch updates
- ✅ **SQL Server** - Full support with MERGE statements
- ✅ **PostgreSQL** - Full support with CASE WHEN batch updates
- ✅ **SQLite** - Full support with optimized queries

Each provider has database-specific optimizations for:

- Batch operations (INSERT, UPDATE, DELETE)
- Query plan analysis (EXPLAIN)
- SQL generation (LIMIT, OFFSET, string concatenation)
- Data type mapping
- Identifier escaping

### Custom Database Provider

[](#custom-database-provider)

You can register custom database providers:

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Providers\DatabaseProviderFactory;
use Yakupeyisan\CodeIgniter4\EntityFramework\Providers\DatabaseProvider;

// Create custom provider
class OracleProvider implements DatabaseProvider
{
    // Implement all required methods
}

// Register provider
DatabaseProviderFactory::register(new OracleProvider());
```

Features
--------

[](#features)

### ✅ Completed Features

[](#-completed-features)

1. **Core ORM Features**

    - Code First approach
    - Database First compatibility
    - Fluent API support
    - Data Annotations (Attributes) support
2. **Relationship Types**

    - One-to-One relationships
    - One-to-Many relationships
    - Many-to-Many relationships (Join entity and skip navigation)
    - Self-referencing relationships
    - Optional and Required relationships
3. **Key Management**

    - Primary Key
    - Composite Key
    - Foreign Key
    - Concurrency Tokens
4. **Loading Strategies**

    - **Lazy Loading (Proxies)** - Otomatik navigation property yükleme
        - Proxy-based lazy loading
        - İlk erişimde otomatik yükleme
        - Reference ve collection navigation desteği
        - Enable/disable lazy loading
    - Eager Loading (Include / ThenInclude)
    - Explicit Loading
5. **LINQ Features**

    - IQueryable support
    - AsNoTracking / AsTracking
    - Projection (Select)
    - GroupBy
    - Join / Left Join
    - **JoinRaw** - Join with raw SQL (derived tables, CTEs, date generation queries, etc.)
    - Raw SQL (FromSqlRaw)
    - **Table-Valued Functions** - SQL Server ve PostgreSQL table-valued function desteği
    - **SensitiveValue** - SQL seviyesinde hassas veri maskeleme (kredi kartı, SSN vb.)
    - **Advanced Expression Tree Parsing**
        - Complex WHERE clause support (AND, OR, NOT)
        - **String Methods**: Contains, StartsWith, EndsWith, ToLower, ToUpper, Length, Substring, Trim, LTrim, RTrim, Replace
        - **Date/Time Methods**: Year, Month, Day, Hour, Minute, Second
        - **Math Methods**: Abs, Round, Ceiling, Floor
        - **Arithmetic Operations**: +, -, \*, /, % (with operator precedence)
        - Comparison operators (===, ==, !==, !=, &lt;, &gt;, &lt;=, &gt;=)
        - IN operator support
        - Null checking (IS NULL, IS NOT NULL)
        - Nested expressions and parentheses
    - **Compiled Queries (Performance Optimization)**
        - Query compilation and caching
        - SQL query plan caching
        - Parameterized query optimization
        - Automatic query cache management
        - Cache statistics and monitoring
6. **Migration System**

    - Add-Migration
    - Update-Database
    - Remove-Migration
    - Migration rollback
    - **Otomatik Migration Üretimi (MigrationGenerator)**
        - ApplicationDbContext'ten otomatik migration oluşturma
        - Mevcut tabloları kontrol ederek sadece yeni/değişiklikleri ekleme
        - Entity attribute'larından otomatik şema analizi
7. **Transaction and Concurrency**

    - BeginTransaction / Commit / Rollback
    - Optimistic Concurrency
    - RowVersion / Timestamp
8. **Advanced Features**

    - Value Converters
    - Owned Types (Complex Types)
    - Query Filters (Global Filters)
    - Change Tracking
9. **Repository Pattern**

    - Generic Repository
    - Unit of Work
    - Specification Pattern
10. **Add, Update, Delete Operations**

    - Single entity operations (Add, Update, Remove)
    - Change Tracker integration
    - Batch operations with Change Tracker (addRange, updateRange, removeRange)
    - Direct database batch operations (batchInsert, batchUpdate, batchDelete)
    - Transaction support for batch operations
    - Auto-increment ID handling
    - Entity state management
11. **Audit and Soft Delete**

    - Audit fields (CreatedAt, UpdatedAt, DeletedAt)
    - Soft Delete pattern

Usage Examples
--------------

[](#usage-examples)

### Creating DbContext

[](#creating-dbcontext)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\CoreDbContext;
use App\Models\User;
use App\Models\Company;

class ApplicationDbContext extends DbContext
{
    protected function onModelCreating(): void
    {
        $this->entity(User::class)
            ->hasKey('Id')
            ->toTable('Users')
            ->property('Id')
                ->valueGeneratedOnAdd()
                ->entity()
            ->property('FirstName')
                ->hasMaxLength(100)
                ->isRequired()
                ->entity();
    }

    public function Users()
    {
        return $this->set(User::class);
    }
}
```

### Query Examples

[](#query-examples)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\CoreDbContext;
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Query examples
$users = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->include('Company')
    ->include('UserDepartments')
        ->thenInclude('Department')
    ->orderBy(fn($u) => $u->LastName)
    ->toList();

// Include with WHERE clause and JOIN type
// Sadece en son AccessEvent'i getirmek için
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] WHERE [ac].EmployeeID = {alias}.EmployeeID GROUP BY [ac].EmployeeID)',
        'INNER'
    )
    ->toList();

// ThenInclude with WHERE clause and JOIN type
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        '{alias}.Status = 1',
        'INNER'
    )
    ->toList();

// First or default
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// AsNoTracking
$users = $context->Users()
    ->asNoTracking()
    ->toList();

// Count
$count = $context->Users()->count();

// Any
$hasUsers = $context->Users()->any();
```

### Eager Loading (Include / ThenInclude) with WHERE Clause and JOIN Type

[](#eager-loading-include--theninclude-with-where-clause-and-join-type)

`include()` ve `thenInclude()` metodlarına WHERE clause ve JOIN type desteği eklendi. Bu özellik sayesinde collection navigation'lar için filtreleme yapabilir ve JOIN tipini (LEFT veya INNER) belirleyebilirsiniz.

#### Include with WHERE Clause and Custom JOIN Condition

[](#include-with-where-clause-and-custom-join-condition)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Sadece en son AccessEvent'i getirmek için
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] WHERE [ac].EmployeeID = {alias}.EmployeeID GROUP BY [ac].EmployeeID)',
        'INNER'
    )
    ->toList();

// WHERE clause'da {alias} placeholder'ı otomatik olarak entity alias'ı ile değiştirilir
// JOIN type: 'LEFT' (varsayılan) veya 'INNER'

// Reference navigation için özel JOIN koşulu (örn: Company için)
$employees = $context->Employees()
    ->include('Company',
        null,  // WHERE clause (opsiyonel)
        'LEFT',  // JOIN type
        '{alias}.CompanyCode = {relatedAlias}.Code'  // Özel JOIN koşulu
    )
    ->toList();
```

#### ThenInclude with WHERE Clause and Custom JOIN Condition

[](#theninclude-with-where-clause-and-custom-join-condition)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Collection navigation için nested collection subquery'ye WHERE clause ekleme
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        '{alias}.Status = 1',  // WHERE clause
        'INNER'                // JOIN type
    )
    ->toList();

// Reference navigation için JOIN'e WHERE clause ekleme
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        '{alias}.ReaderID IN (SELECT ReaderID FROM Terminals WHERE Status = 1)',
        'INNER'
    )
    ->toList();
```

#### AndInclude - Multiple Navigation Properties from Same Parent

[](#andinclude---multiple-navigation-properties-from-same-parent)

`andInclude()` metodu, son eklenen `thenInclude` ile aynı parent'a başka bir navigation property eklemenizi sağlar. Bu, aynı seviyedeki birden fazla navigation property'yi include etmek için kullanışlıdır.

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// AccessGroupReaders'dan hem Terminal hem de TimeZone include et
$employees = $context->Employees()
    ->include('EmployeeAccessGroups')
        ->thenInclude('AccessGroup')
            ->thenInclude('AccessGroupReaders')
                ->thenInclude('Terminal')      // Terminal eklenir
                ->andInclude('TimeZone');      // TimeZone, Terminal ile aynı seviyede (AccessGroupReaders altında) eklenir

// Birden fazla andInclude kullanımı
$employees = $context->Employees()
    ->include('EmployeeAccessGroups')
        ->thenInclude('AccessGroup')
            ->thenInclude('AccessGroupReaders')
                ->thenInclude('Terminal')
                ->andInclude('TimeZone')       // Terminal ile aynı seviyede
                ->andInclude('ReaderSettings'); // Terminal ve TimeZone ile aynı seviyede

// WHERE clause ve JOIN type ile birlikte kullanım
$employees = $context->Employees()
    ->include('EmployeeAccessGroups')
        ->thenInclude('AccessGroup')
            ->thenInclude('AccessGroupReaders')
                ->thenInclude('Terminal', '{alias}.Status = 1', 'INNER')
                ->andInclude('TimeZone', '{alias}.IsActive = 1', 'LEFT');
```

**Önemli Notlar:**

1. **ThenInclude Sonrası**: `andInclude()` mutlaka bir `thenInclude()` çağrısından sonra kullanılmalıdır
2. **Aynı Seviye**: `andInclude()` son eklenen `thenInclude` ile aynı parent'a ekler (kardeş navigation property)
3. **WHERE Clause**: `andInclude()` da `whereClause` ve `joinType` parametrelerini destekler
4. **Custom JOIN Condition**: `andInclude()` da `joinCondition` parametresini destekler

// Özel JOIN koşulu ile reference navigation (varsayılan foreign key yerine) // Örnek: AccessEvent.DeviceSerial = Terminals.SerialNumber $employees = $context-&gt;Employees() -&gt;include('AccessEvents', '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS \[ac\] WHERE \[ac\].EmployeeID = {alias}.EmployeeID GROUP BY \[ac\].EmployeeID)', 'LEFT') -&gt;thenInclude('Terminal', null, // WHERE clause (opsiyonel) 'INNER', // JOIN type '{alias}.DeviceSerial = {relatedAlias}.SerialNumber' // Özel JOIN koşulu ) -&gt;toList();

```

#### Placeholder Kullanımı

WHERE clause ve JOIN condition'da aşağıdaki placeholder'ları kullanabilirsiniz:

- **`{alias}`**: Collection subquery'deki entity alias'ı (örn: `u2`, `u1`) veya reference navigation için parent entity alias'ı
- **`{relatedAlias}`**: Nested collection subquery'lerde related entity alias'ı (örn: `o`) veya reference navigation için related entity alias'ı

**Örnek:**

```php
// Collection subquery için
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] GROUP BY [ac].EmployeeID)'
    )
    ->toList();

// Nested collection subquery için
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        '{alias}.Status = 1 AND {relatedAlias}.IsActive = 1'
    )
    ->toList();

// Özel JOIN koşulu ile (reference navigation)
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        null,
        'INNER',
        '{alias}.DeviceSerial = {relatedAlias}.SerialNumber'  // Özel JOIN koşulu
    )
    ->toList();

```

#### JOIN Type Seçimi

[](#join-type-seçimi)

- **`'LEFT'`** (varsayılan): LEFT JOIN kullanır, eşleşmeyen kayıtlar da gelir
- **`'INNER'`**: INNER JOIN kullanır, sadece eşleşen kayıtlar gelir

**Örnek:**

```
// LEFT JOIN (varsayılan) - Tüm employee'ler gelir, AccessEvent olmayanlar da
$employees = $context->Employees()
    ->include('AccessEvents', null, 'LEFT')
    ->toList();

// INNER JOIN - Sadece AccessEvent'i olan employee'ler gelir
$employees = $context->Employees()
    ->include('AccessEvents', null, 'INNER')
    ->toList();
```

#### Geriye Dönük Uyumluluk

[](#geriye-dönük-uyumluluk)

Mevcut `include()` ve `thenInclude()` çağrıları geriye dönük uyumludur. WHERE clause ve JOIN type parametreleri opsiyoneldir:

```
// Eski kullanım hala çalışır
$users = $context->Users()
    ->include('Company')
    ->include('UserDepartments')
        ->thenInclude('Department')
    ->toList();

// Yeni özellikler opsiyonel
$users = $context->Users()
    ->include('Company')  // WHERE clause ve JOIN type yok
    ->include('UserDepartments', '{alias}.IsActive = 1', 'INNER')  // Yeni özellikler
        ->thenInclude('Department')  // WHERE clause ve JOIN type yok
    ->toList();
```

#### Özel JOIN Koşulu (Custom Join Condition)

[](#özel-join-koşulu-custom-join-condition)

Varsayılan foreign key ilişkisi yerine özel bir JOIN koşulu kullanmak istediğinizde `joinCondition` parametresini kullanabilirsiniz. Bu özellik özellikle standart foreign key ilişkisi olmayan durumlarda (örn: `DeviceSerial` ile `SerialNumber` gibi) kullanışlıdır.

**Örnek:**

```
// AccessEvent.DeviceSerial = Terminals.SerialNumber ile JOIN
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] WHERE [ac].EmployeeID = {alias}.EmployeeID GROUP BY [ac].EmployeeID)',
        'LEFT')
    ->thenInclude('Terminal',
        null,  // WHERE clause (opsiyonel)
        'INNER',  // JOIN type
        '{alias}.DeviceSerial = {relatedAlias}.SerialNumber'  // Özel JOIN koşulu
    )
    ->toList();
```

**Notlar:**

- `joinCondition` sağlanmazsa, sistem varsayılan foreign key ilişkisini kullanır
- `{alias}` parent entity'nin alias'ını (örn: `AccessEvent` için `u2`)
- `{relatedAlias}` related entity'nin alias'ını (örn: `Terminals` için `t`) temsil eder
- JOIN condition SQL Server syntax'ına uygun olmalıdır

#### Kullanım Senaryoları

[](#kullanım-senaryoları)

**Senaryo 1: Sadece En Son Kayıtları Getirme**

```
// Her çalışan için sadece en son geçtiği kapının AccessEvent bilgilerini getir
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] WHERE [ac].EmployeeID = {alias}.EmployeeID GROUP BY [ac].EmployeeID)',
        'INNER'
    )
    ->toList();
```

**Senaryo 2: Aktif Kayıtları Filtreleme**

```
// Sadece aktif terminal'leri getir
$employees = $context->Employees()
    ->include('AccessEvents')
    ->thenInclude('Terminal',
        '{alias}.Status = 1 AND {alias}.IsActive = 1',
        'INNER'
    )
    ->toList();
```

**Senaryo 3: Tarih Aralığı Filtreleme**

```
// Son 30 gün içindeki AccessEvent'leri getir
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.EventTime >= DATEADD(DAY, -30, GETDATE())',
        'INNER'
    )
    ->toList();
```

**Senaryo 4: Özel JOIN Koşulu ile İlişki**

```
// DeviceSerial ile SerialNumber arasında JOIN (standart foreign key yok)
$employees = $context->Employees()
    ->include('AccessEvents',
        '{alias}.AccessEventID IN (SELECT max(AccessEventID) FROM AccessEvent AS [ac] WHERE [ac].EmployeeID = {alias}.EmployeeID GROUP BY [ac].EmployeeID)',
        'LEFT')
    ->thenInclude('Terminal',
        null,
        'INNER',
        '{alias}.DeviceSerial = {relatedAlias}.SerialNumber'
    )
    ->toList();
```

#### Notlar

[](#notlar)

1. **WHERE Clause**: WHERE clause SQL Server syntax'ına uygun olmalıdır
2. **Placeholder**: `{alias}` ve `{relatedAlias}` placeholder'ları otomatik olarak doğru alias'larla değiştirilir
3. **JOIN Type**: Collection navigation'lar için LEFT veya INNER JOIN seçilebilir
4. **Custom JOIN Condition**: `joinCondition` parametresi ile varsayılan foreign key ilişkisi yerine özel JOIN koşulları kullanabilirsiniz
5. **Performance**: WHERE clause ile filtreleme yaparak gereksiz veri yüklemeyi önleyebilirsiniz
6. **Güvenlik**: WHERE clause ve JOIN condition'da SQL injection'dan korunmak için parametreli sorgular kullanın (şu an için raw SQL string kullanılıyor)
7. **Geriye Dönük Uyumluluk**: `joinCondition` parametresi opsiyoneldir, sağlanmazsa varsayılan foreign key ilişkisi kullanılır

### Advanced WHERE Clause (Expression Tree Parsing)

[](#advanced-where-clause-expression-tree-parsing)

Expression Tree Parsing özelliği sayesinde karmaşık WHERE koşullarını lambda expression'lar ile yazabilirsiniz. Sistem bu expression'ları otomatik olarak SQL WHERE clause'larına çevirir.

#### Basit Karşılaştırmalar

[](#basit-karşılaştırmalar)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Eşitlik karşılaştırması
$users = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->toList();

// Büyük/küçük karşılaştırmaları
$users = $context->Users()
    ->where(fn($u) => $u->Age >= 18)
    ->where(fn($u) => $u->Age toList();

// Eşitsizlik
$users = $context->Users()
    ->where(fn($u) => $u->Status !== 'Inactive')
    ->toList();
```

#### Mantıksal Operatörler (AND, OR)

[](#mantıksal-operatörler-and-or)

```
// AND operatörü (&&)
$users = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1 && $u->Status === 'Active')
    ->toList();

// OR operatörü (||)
$users = $context->Users()
    ->where(fn($u) => $u->Status === 'Active' || $u->Status === 'Pending')
    ->toList();

// Karmaşık mantıksal ifadeler
$users = $context->Users()
    ->where(fn($u) => ($u->CompanyId === 1 || $u->CompanyId === 2) && $u->Age >= 18)
    ->toList();
```

#### NOT Operatörü

[](#not-operatörü)

```
// NOT operatörü
$users = $context->Users()
    ->where(fn($u) => !($u->Status === 'Deleted'))
    ->toList();

// NOT ile kombinasyon
$users = $context->Users()
    ->where(fn($u) => !($u->IsAdmin) && $u->Status === 'Active')
    ->toList();
```

#### Null Kontrolleri

[](#null-kontrolleri)

```
// IS NULL
$users = $context->Users()
    ->where(fn($u) => $u->DeletedAt === null)
    ->toList();

// IS NOT NULL
$users = $context->Users()
    ->where(fn($u) => $u->Email !== null)
    ->toList();
```

#### String Metodları

[](#string-metodları)

```
// Contains - LIKE '%value%'
$users = $context->Users()
    ->where(fn($u) => $u->FirstName->contains('John'))
    ->toList();

// StartsWith - LIKE 'value%'
$users = $context->Users()
    ->where(fn($u) => $u->Email->startsWith('admin@'))
    ->toList();

// EndsWith - LIKE '%value'
$users = $context->Users()
    ->where(fn($u) => $u->Email->endsWith('.com'))
    ->toList();

// ToLower - LOWER()
$users = $context->Users()
    ->where(fn($u) => $u->Email->toLower() === 'admin@example.com')
    ->toList();

// ToUpper - UPPER()
$users = $context->Users()
    ->where(fn($u) => $u->Status->toUpper() === 'ACTIVE')
    ->toList();

// Length - LENGTH()
$users = $context->Users()
    ->where(fn($u) => $u->FirstName->length() > 5)
    ->toList();

// Substring - SUBSTRING()
$users = $context->Users()
    ->where(fn($u) => $u->Email->substring(0, 5) === 'admin')
    ->toList();

// Trim - TRIM()
$users = $context->Users()
    ->where(fn($u) => $u->FirstName->trim() === 'John')
    ->toList();

// LTrim - LTRIM()
$users = $context->Users()
    ->where(fn($u) => $u->FirstName->lTrim() === 'John')
    ->toList();

// RTrim - RTRIM()
$users = $context->Users()
    ->where(fn($u) => $u->FirstName->rTrim() === 'John')
    ->toList();

// Replace - REPLACE()
$users = $context->Users()
    ->where(fn($u) => $u->Email->replace('@', '_at_')->contains('example'))
    ->toList();
```

#### Date/Time Metodları

[](#datetime-metodları)

```
// Year - YEAR()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->year() === 2024)
    ->toList();

// Month - MONTH()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->month() === 12)
    ->toList();

// Day - DAY()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->day() === 25)
    ->toList();

// Hour - HOUR()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->hour() >= 9 && $u->CreatedAt->hour() toList();

// Minute - MINUTE()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->minute() === 0)
    ->toList();

// Second - SECOND()
$users = $context->Users()
    ->where(fn($u) => $u->CreatedAt->second() < 30)
    ->toList();
```

#### Math Metodları

[](#math-metodları)

```
// Abs - ABS()
$users = $context->Users()
    ->where(fn($u) => $u->Balance->abs() > 100)
    ->toList();

// Round - ROUND()
$users = $context->Users()
    ->where(fn($u) => $u->Price->round(2) === 99.99)
    ->toList();

// Ceiling - CEILING()
$users = $context->Users()
    ->where(fn($u) => $u->Price->ceiling() >= 100)
    ->toList();

// Floor - FLOOR()
$users = $context->Users()
    ->where(fn($u) => $u->Price->floor() toList();
```

#### Aritmetik İşlemler

[](#aritmetik-i̇şlemler)

```
// Toplama (+)
$users = $context->Users()
    ->where(fn($u) => $u->Age + 5 >= 25)
    ->toList();

// Çıkarma (-)
$users = $context->Users()
    ->where(fn($u) => $u->Age - 5 < 18)
    ->toList();

// Çarpma (*)
$users = $context->Users()
    ->where(fn($u) => $u->Price * 1.2 > 100)
    ->toList();

// Bölme (/)
$users = $context->Users()
    ->where(fn($u) => $u->Total / $u->Quantity > 10)
    ->toList();

// Modulo (%)
$users = $context->Users()
    ->where(fn($u) => $u->Id % 2 === 0)
    ->toList();

// Karmaşık aritmetik ifadeler
$users = $context->Users()
    ->where(fn($u) => ($u->Price * $u->Quantity) - $u->Discount > 1000)
    ->toList();

// Parantez ile öncelik
$users = $context->Users()
    ->where(fn($u) => ($u->Price + $u->Tax) * 1.1 toList();
```

#### IN Operatörü

[](#in-operatörü)

```
// IN operatörü - değer listesi
$users = $context->Users()
    ->where(fn($u) => in_array($u->CompanyId, [1, 2, 3, 4, 5]))
    ->toList();

// IN ile string değerler
$users = $context->Users()
    ->where(fn($u) => in_array($u->Status, ['Active', 'Pending', 'Approved']))
    ->toList();
```

#### Karmaşık Örnekler

[](#karmaşık-örnekler)

```
// Birden fazla koşul kombinasyonu
$users = $context->Users()
    ->where(fn($u) =>
        ($u->CompanyId === 1 || $u->CompanyId === 2) &&
        $u->Status === 'Active' &&
        $u->Age >= 18 &&
        $u->Email !== null &&
        $u->FirstName->contains('John')
    )
    ->toList();

// Nested expressions
$users = $context->Users()
    ->where(fn($u) =>
        ($u->IsAdmin || ($u->Department === 'IT' && $u->Level >= 5)) &&
        !($u->Status === 'Suspended')
    )
    ->toList();
```

#### Navigation Property ile WHERE

[](#navigation-property-ile-where)

```
// Navigation property üzerinde filtreleme
$users = $context->Users()
    ->where(fn($u) => $u->Company->Name === 'Acme Corp')
    ->include('Company')
    ->toList();

// Nested navigation property
$users = $context->Users()
    ->where(fn($u) => $u->Company->Country->Name === 'Turkey')
    ->include('Company')
        ->thenInclude('Country')
    ->toList();
```

#### Performans Notları

[](#performans-notları)

- Expression Tree Parsing otomatik olarak SQL'e çevrilir

#### Lambda Expression'larda Değişken Kullanımı

[](#lambda-expressionlarda-değişken-kullanımı)

Lambda expression'larda closure'ın `use` clause'undaki değişkenleri kullanabilirsiniz:

```
$id = 123;
$status = 'Active';

$users = $context->Users()
    ->where(fn($u) => $u->Id === $id)
    ->where(fn($u) => $u->Status === $status)
    ->toList();
```

**Önemli Notlar:**

1. **SQL Server Uyumluluğu**: Sistem SQL Server ile uyumlu olması için `?` placeholder'larını kullanır. Named parameter'lar (`:param_0` gibi) desteklenmez.
2. **Değişken Değerleri**: Closure'ın `use` clause'undaki değişkenlerin değerleri otomatik olarak yakalanır ve SQL sorgusuna güvenli bir şekilde eklenir.
3. **Hata Ayıklama**: SQL sorgularında hata oluştuğunda, hatalı SQL sorgusu otomatik olarak log'a yazılır. Bu sayede sorunları daha kolay tespit edebilirsiniz.
4. **Güvenlik**: Tüm değişken değerleri SQL injection saldırılarına karşı korunur. String değerler otomatik olarak escape edilir.

**Örnek Hata Log'u:**

```
ERROR - SQL Query Error: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Incorrect syntax near ':'.
ERROR - Failed SQL Query: SELECT [main].[Id] FROM [Users] AS [main] WHERE main.Id = :param_0

```

Bu tür hatalarda, sistem otomatik olarak `?` placeholder'larını kullanır ve değerleri güvenli bir şekilde SQL'e ekler.

#### Dinamik Property Access ve Type Casting

[](#dinamik-property-access-ve-type-casting)

Lambda expression'larda dinamik property access ve type casting kullanabilirsiniz:

```
// Dinamik property access
$primaryKey = 'Id';
$users = $context->Users()
    ->where(fn($u) => $u->{$primaryKey} === 1)
    ->toList();

// Class property ile dinamik access
class UserRepository {
    protected $primaryKey = 'Id';

    public function getById($id) {
        return $this->context->Users()
            ->where(fn($u) => $u->{$this->primaryKey} === (int)$id)
            ->firstOrDefault();
    }
}

// Type casting desteği
$id = "123"; // String olarak geliyor
$user = $context->Users()
    ->where(fn($u) => $u->Id === (int)$id)
    ->firstOrDefault();

// Farklı type casting'ler
$users = $context->Users()
    ->where(fn($u) => $u->Age === (float)$age)
    ->where(fn($u) => $u->IsActive === (bool)$status)
    ->where(fn($u) => $u->Name === (string)$name)
    ->toList();
```

**Desteklenen Type Casting'ler:**

- `(int)` - Integer'a çevirme
- `(string)` - String'e çevirme
- `(float)` - Float'a çevirme
- `(bool)` - Boolean'a çevirme

**Notlar:**

- Type casting'ler SQL'e çevrilirken göz ardı edilir (SQL type conversion kullanılır)
- Dinamik property access'lerde `$this->property` gibi ifadeler otomatik olarak parse edilir
- Çok satırlı expression'lar desteklenir
- Karmaşık expression'lar optimize edilmiş SQL üretir
- Navigation property'ler için otomatik JOIN'ler oluşturulur
- Index'ler kullanılarak performans optimize edilir

#### Desteklenen Operatörler ve Metodlar

[](#desteklenen-operatörler-ve-metodlar)

**Karşılaştırma Operatörleri:**

OperatörSQL KarşılığıÖrnek`===`, `==``=``$u->Id === 1``!==`, `!=``!=``$u->Status !== 'Deleted'````>``$u->Age > 65``=``$u->Age >= 18`**Mantıksal Operatörler:**

OperatörSQL KarşılığıÖrnek`&&`, `and``AND``$u->A && $u->B``||`, `or``OR``$u->A || $u->B``!``NOT``!($u->IsDeleted)`**Null Operatörleri:**

OperatörSQL KarşılığıÖrnek`=== null``IS NULL``$u->DeletedAt === null``!== null``IS NOT NULL``$u->Email !== null`**Aritmetik Operatörler:**

OperatörSQL KarşılığıÖrnekÖncelik`+``+``$u->Age + 5`Düşük`-``-``$u->Age - 5`Düşük`*``*``$u->Price * 2`Yüksek`/``/``$u->Total / 2`Yüksek`%``%``$u->Id % 2`Yüksek**String Metodları:**

MetodSQL KarşılığıÖrnek`->contains()``LIKE '%value%'``$u->Name->contains('John')``->startsWith()``LIKE 'value%'``$u->Email->startsWith('admin')``->endsWith()``LIKE '%value'``$u->Email->endsWith('.com')``->toLower()``LOWER()``$u->Email->toLower()``->toUpper()``UPPER()``$u->Status->toUpper()``->length()``LENGTH()``$u->FirstName->length()``->substring(start, length)``SUBSTRING()``$u->Email->substring(0, 5)``->trim()``TRIM()``$u->FirstName->trim()``->lTrim()``LTRIM()``$u->FirstName->lTrim()``->rTrim()``RTRIM()``$u->FirstName->rTrim()``->replace(old, new)``REPLACE()``$u->Email->replace('@', '_')`**Date/Time Metodları:**

MetodSQL KarşılığıÖrnek`->year()``YEAR()``$u->CreatedAt->year()``->month()``MONTH()``$u->CreatedAt->month()``->day()``DAY()``$u->CreatedAt->day()``->hour()``HOUR()``$u->CreatedAt->hour()``->minute()``MINUTE()``$u->CreatedAt->minute()``->second()``SECOND()``$u->CreatedAt->second()`**Math Metodları:**

MetodSQL KarşılığıÖrnek`->abs()``ABS()``$u->Balance->abs()``->round(decimals)``ROUND()``$u->Price->round(2)``->ceiling()``CEILING()``$u->Price->ceiling()``->floor()``FLOOR()``$u->Price->floor()`**Diğer Operatörler:**

OperatörSQL KarşılığıÖrnek`in_array()``IN (...)``in_array($u->Id, [1,2,3])`#### İpuçları

[](#i̇puçları)

1. **Parantez Kullanımı**: Karmaşık expression'larda parantez kullanarak öncelik sırasını belirleyin
2. **Aritmetik Öncelik**: Çarpma, bölme ve modulo işlemleri toplama ve çıkarmadan önce yapılır
3. **Null Kontrolleri**: Null değerler için `=== null` veya `!== null` kullanın
4. **String Arama**: Büyük/küçük harf duyarlılığı veritabanı ayarlarına bağlıdır
5. **Method Chaining**: Method'ları birleştirerek karmaşık ifadeler oluşturabilirsiniz
6. **Performance**: Navigation property filtrelemeleri JOIN gerektirir, performansı etkileyebilir
7. **Index Kullanımı**: Sık kullanılan filtreleme alanları için index oluşturun
8. **Date/Time Methods**: Date/Time method'ları sadece DateTime alanlarında kullanılabilir

### JoinRaw - Raw SQL ile Join

[](#joinraw---raw-sql-ile-join)

`joinRaw` metodu ile raw SQL sorgularını (derived table, CTE, date generation query vb.) mevcut tablolarla birleştirebilirsiniz. Bu özellik, özellikle tarih aralığı oluşturma gibi durumlarda kullanışlıdır.

#### SQL Server'da Tarih Aralığı ile Join

[](#sql-serverda-tarih-aralığı-ile-join)

SQL Server'da `master..spt_values` kullanarak tarih aralığı oluşturup mevcut tabloyla birleştirme:

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Tarih aralığı oluşturan raw SQL sorgusu
$dateRangeSql = "SELECT CONVERT(DATE, DATEADD(DAY, number, '2024-01-01')) AS [Date]
                 FROM master..spt_values
                 WHERE type = 'P'
                 AND DATEADD(DAY, number, '2024-01-01') < '2024-02-01'";

// Mevcut tabloyu tarih aralığı ile birleştirme
$results = $context->Orders()
    ->joinRaw(
        $dateRangeSql,                    // Raw SQL sorgusu
        'dates',                          // Alias (JOIN edilen tablo için)
        'dates.[Date] = main.[OrderDate]', // Join koşulu
        'LEFT'                            // Join tipi (LEFT, INNER, RIGHT, FULL)
    )
    ->where(fn($o) => $o->Status === 'Active')
    ->toList();

// Sonuçlarda hem Order bilgileri hem de dates.Date kolonu bulunur
foreach ($results as $order) {
    echo $order->OrderDate; // Order'ın tarihi
    // dates_Date özelliği de mevcut olacaktır (alias_ColumnName formatında)
}
```

#### Parametreli Raw SQL ile Join

[](#parametreli-raw-sql-ile-join)

```
// Parametreli sorgu ile tarih aralığı
$startDate = '2024-01-01';
$endDate = '2024-02-01';

$dateRangeSql = "SELECT CONVERT(DATE, DATEADD(DAY, number, ?)) AS [Date]
                 FROM master..spt_values
                 WHERE type = 'P'
                 AND DATEADD(DAY, number, ?) < ?";

$results = $context->Orders()
    ->joinRaw(
        $dateRangeSql,
        'dates',
        'dates.[Date] = main.[OrderDate]',
        'LEFT',
        [$startDate, $startDate, $endDate] // Parametreler
    )
    ->toList();
```

#### INNER JOIN Örneği

[](#inner-join-örneği)

```
// Sadece eşleşen kayıtları getirmek için INNER JOIN
$results = $context->Orders()
    ->joinRaw(
        "SELECT DISTINCT CONVERT(DATE, OrderDate) AS [Date] FROM Orders",
        'distinctDates',
        'distinctDates.[Date] = main.[OrderDate]',
        'INNER'
    )
    ->toList();
```

#### Birden Fazla Raw Join

[](#birden-fazla-raw-join)

```
// Birden fazla raw join kullanımı
$results = $context->Orders()
    ->joinRaw(
        $dateRangeSql,
        'dates',
        'dates.[Date] = main.[OrderDate]',
        'LEFT'
    )
    ->joinRaw(
        "SELECT * FROM (VALUES (1, 'New'), (2, 'Processing')) AS Status(Id, Name)",
        'statusLookup',
        'statusLookup.Id = main.StatusId',
        'LEFT'
    )
    ->toList();
```

#### Notlar

[](#notlar-1)

1. **Alias Kullanımı**: Raw join'de verdiğiniz alias ile kolonlara erişebilirsiniz (format: `alias_ColumnName`)
2. **Join Condition**: Join koşulunda ana tabloya `main` alias'ı ile veya tablo adı ile referans verebilirsiniz
3. **Join Type**: 'LEFT', 'INNER', 'RIGHT', 'FULL' join tiplerini kullanabilirsiniz
4. **Parametreler**: Raw SQL'de parametreler kullanıyorsanız, `joinRaw` metodunun son parametresine dizi olarak geçebilirsiniz
5. **SQL Injection**: Parametreli sorgular kullanarak SQL injection'dan korunun

### Table-Valued Functions - SQL Server ve PostgreSQL Function Desteği

[](#table-valued-functions---sql-server-ve-postgresql-function-desteği)

SQL Server ve PostgreSQL'deki table-valued function'ları object-oriented bir şekilde çağırabilirsiniz. Function sonuçları entity class'larına map edilir ve normal query'ler gibi işlenebilir.

#### Desteklenen Veritabanları

[](#desteklenen-veritabanları)

- ✅ **SQL Server**: Tam destek
- ✅ **PostgreSQL**: Tam destek
- ❌ **MySQL**: Desteklenmiyor (stored procedure veya view kullanın)
- ❌ **SQLite**: Desteklenmiyor (view veya subquery kullanın)

#### Entity Class Oluşturma

[](#entity-class-oluşturma)

Function sonuçlarını temsil eden bir entity class'ı oluşturun:

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Column;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\NotMapped;

#[NotMapped]
class CafeteriaSummary extends Entity
{
    #[Column(name: 'ApplicationName')]
    public ?string $ApplicationName = null;

    #[Column(name: 'CafeteriaGroupName')]
    public ?string $CafeteriaGroupName = null;

    #[Column(name: 'Subscription')]
    public ?int $Subscription = null;

    #[Column(name: 'FirstPass')]
    public ?int $FirstPass = null;

    #[Column(name: 'FirstPassPrice')]
    public ?float $FirstPassPrice = null;

    #[Column(name: 'SecondPass')]
    public ?int $SecondPass = null;

    #[Column(name: 'SecondPassPrice')]
    public ?float $SecondPassPrice = null;
}
```

**Önemli Notlar:**

- `#[NotMapped]` attribute'u kullanın çünkü bu bir tablo değil, function sonucudur
- Her property için `#[Column]` attribute'u ile veritabanı kolon adını belirtin
- Primary key tanımlamaya gerek yoktur (function sonuçları genellikle primary key içermez)

#### Function Çağırma

[](#function-çağırma)

DbContext'in `fromFunction` method'unu kullanarak function'ı çağırabilirsiniz:

```
use App\EntityFramework\ApplicationDbContext;
use App\Entities\CafeteriaSummary;

$context = new ApplicationDbContext();

$startDate = new \DateTime('2024-01-01');
$endDate = new \DateTime('2024-12-31');

// Function'ı çağır
$results = $context->fromFunction(
    CafeteriaSummary::class,      // Entity class
    'dbo',                         // Schema name (SQL Server için 'dbo', PostgreSQL için 'public')
    'fnCafeteriaSummary',          // Function name
    [                              // Function parameters
        'StartDate' => $startDate->format('Y-m-d H:i:s'),
        'EndDate' => $endDate->format('Y-m-d H:i:s')
    ]
)->toList();

// Sonuçları kullan
foreach ($results as $summary) {
    echo "Application: {$summary->ApplicationName}\n";
    echo "Subscription: {$summary->Subscription}\n";
    echo "FirstPass: {$summary->FirstPass}\n";
}
```

#### SQL Server Function Örneği

[](#sql-server-function-örneği)

Aşağıdaki SQL Server function'ınız için:

```
ALTER FUNCTION [dbo].[fnCafeteriaSummary]
(
    @StartDate DATETIME,
    @EndDate   DATETIME
)
RETURNS TABLE
AS
RETURN
(
    SELECT
        ApplicationName,
        CafeteriaGroupName,
        SUM(CASE WHEN TransactionType = 2 THEN 1 ELSE 0 END) AS Subscription,
        SUM(CASE WHEN TransactionType = 3 THEN 1 ELSE 0 END) AS FirstPass,
        -- ... diğer kolonlar
    FROM CafeteriaEvent
    WHERE EventTime BETWEEN @StartDate AND @EndDate
    GROUP BY ApplicationName, CafeteriaGroupName
);
```

PHP'de kullanım:

```
$results = $context->fromFunction(
    CafeteriaSummary::class,
    'dbo',
    'fnCafeteriaSummary',
    [
        'StartDate' => '2024-01-01 00:00:00',
        'EndDate' => '2024-12-31 23:59:59'
    ]
)->toList();
```

#### Function Sonuçlarını İşleme

[](#function-sonuçlarını-i̇şleme)

Function sonuçları, normal entity query'leri gibi işlenebilir:

```
// Liste olarak al
$results = $context->fromFunction(...)->toList();

// Array olarak al
$results = $context->fromFunction(...)->toArray();

// Filtreleme
$filtered = $context->fromFunction(...)
    ->where(fn($s) => $s->Subscription > 10)
    ->toList();

// Sıralama
$ordered = $context->fromFunction(...)
    ->orderBy(fn($s) => $s->ApplicationName)
    ->toList();

// Pagination
$paged = $context->fromFunction(...)
    ->skip(0)
    ->take(10)
    ->toList();

// Aggregation
$total = $context->fromFunction(...)
    ->sum(fn($s) => $s->Subscription);

$average = $context->fromFunction(...)
    ->average(fn($s) => $s->FirstPassPrice);
```

#### Parametre İsimlendirme

[](#parametre-i̇simlendirme)

Function parametreleri, SQL Server'daki parametre isimleriyle eşleşmelidir. `@` işareti olmadan kullanın:

```
-- SQL Server'da
@StartDate DATETIME,
@EndDate   DATETIME
```

```
// PHP'de (@ işareti olmadan)
[
    'StartDate' => '2024-01-01 00:00:00',
    'EndDate' => '2024-12-31 23:59:59'
]
```

#### PostgreSQL Function Örneği

[](#postgresql-function-örneği)

PostgreSQL'de table-valued function'lar farklı syntax kullanır:

```
CREATE OR REPLACE FUNCTION public.get_sales_summary(
    start_date DATE,
    end_date DATE
)
RETURNS TABLE (
    product_id INTEGER,
    total_sales NUMERIC,
    order_count INTEGER
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        p.id,
        SUM(o.amount) as total_sales,
        COUNT(o.id) as order_count
    FROM products p
    JOIN orders o ON o.product_id = p.id
    WHERE o.order_date BETWEEN start_date AND end_date
    GROUP BY p.id;
END;
$$ LANGUAGE plpgsql;
```

PHP'de kullanım:

```
$results = $context->fromFunction(
    SalesSummary::class,
    'public',
    'get_sales_summary',
    [
        'start_date' => '2024-01-01',
        'end_date' => '2024-12-31'
    ]
)->toList();
```

#### Best Practices

[](#best-practices)

1. **Entity Naming**: Function sonuçları için entity class'ları açıklayıcı isimler kullanın (örn: `CafeteriaSummary`, `SalesReport`)
2. **Nullable Properties**: Function sonuçlarında null değerler olabilir, bu yüzden property'leri nullable yapın (`?string`, `?int`, vb.)
3. **Type Safety**: Property tiplerini function'ın döndürdüğü veri tipleriyle eşleştirin
4. **Error Handling**: Function çağrılarını try-catch blokları içine alın
5. **Performance**: Büyük sonuç setleri için pagination kullanın

#### Sorun Giderme

[](#sorun-giderme)

**Function Bulunamıyor Hatası:**

- Schema adını kontrol edin (SQL Server için genellikle 'dbo', PostgreSQL için 'public')
- Function adının doğru olduğundan emin olun
- Veritabanı bağlantısının doğru olduğunu kontrol edin

**Parametre Hatası:**

- Parametre isimlerinin SQL Server'daki isimlerle eşleştiğinden emin olun
- Parametre değerlerinin doğru formatta olduğunu kontrol edin (DateTime için 'Y-m-d H:i:s' formatı)

**Mapping Hatası:**

- Entity class'ındaki property isimlerinin `#[Column]` attribute'larıyla eşleştiğinden emin olun
- Property tiplerinin function sonuçlarıyla uyumlu olduğunu kontrol edin

### SensitiveValue - Hassas Veri Maskeleme

[](#sensitivevalue---hassas-veri-maskeleme)

`SensitiveValue` attribute'u ile hassas verileri (kredi kartı, SSN, telefon numarası vb.) SQL sorgusu seviyesinde maskelayabilirsiniz. Maskeleme doğrudan SQL'de yapılır, böylece hassas veriler uygulama koduna hiç maskelenmemiş olarak gelmez.

#### Entity'de Kullanım

[](#entityde-kullanım)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\SensitiveValue;

class User
{
    public int $Id;

    public string $FirstName;
    public string $LastName;

    // Kredi kartı: İlk 0 karakter, son 4 karakter gösterilir
    // Örnek: "1234-5678-9012-3456" -> "************3456"
    #[SensitiveValue(maskChar: '*', visibleStart: 0, visibleEnd: 4)]
    public string $CreditCard;

    // SSN: İlk 2 karakter, son 2 karakter gösterilir
    // Örnek: "123-45-6789" -> "12*****89"
    #[SensitiveValue(maskChar: '*', visibleStart: 2, visibleEnd: 2)]
    public string $SSN;

    // İsim: İlk 2 karakter gösterilir, gerisi maskelenir
    // Örnek: "John" -> "Jo**", "Fatma" -> "Fa***"
    #[SensitiveValue(maskChar: '*', visibleStart: 2, visibleEnd: 0)]
    public string $FirstName;

    // Telefon numarası: Sadece son 4 rakam gösterilir
    #[SensitiveValue(maskChar: 'X', visibleStart: 0, visibleEnd: 4)]
    public string $PhoneNumber;
}
```

#### Maskeleme Parametreleri

[](#maskeleme-parametreleri)

- **maskChar**: Maskeleme için kullanılacak karakter (varsayılan: `'*'`)
- **visibleStart**: Baştan gösterilecek karakter sayısı (varsayılan: `0`)
    - Örnek: `visibleStart: 2` -&gt; "John" -&gt; "Jo\*\*"
- **visibleEnd**: Sondan gösterilecek karakter sayısı (varsayılan: `4`)
    - Örnek: `visibleEnd: 2` -&gt; "John" -&gt; "\*\*hn"
- **customMask**: Özel SQL ifadesi (diğer seçenekleri geçersiz kılar)

**Örnekler:**

- `visibleStart: 2, visibleEnd: 0` -&gt; "John" -&gt; "Jo\*\*", "Fatma" -&gt; "Fa\*\*\*"
- `visibleStart: 0, visibleEnd: 4` -&gt; "John" -&gt; "\*\*\*\*", "1234-5678-9012-3456" -&gt; "\*\*\*\*\*\*\*\*\*\*\*\*3456"
- `visibleStart: 2, visibleEnd: 2` -&gt; "John" -&gt; "Jo**hn", "Fatma" -&gt; "Fa**ma"

#### Normal Sorgu - Maskelenmiş Veri

[](#normal-sorgu---maskelenmiş-veri)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Normal sorgu - hassas veriler maskelenmiş gelir
$users = $context->Users()->toList();

foreach ($users as $user) {
    echo $user->CreditCard; // "************3456"
    echo $user->SSN;        // "12*****89"
    echo $user->PhoneNumber; // "XXXXXXX1234"
}
```

#### disableSensitive() ile Maskelenmemiş Veri

[](#disablesensitive-ile-maskelenmemiş-veri)

```
// disableSensitive() çağrıldığında maskeleme devre dışı kalır
$users = $context->Users()
    ->disableSensitive()
    ->toList();

foreach ($users as $user) {
    echo $user->CreditCard; // "1234-5678-9012-3456" (maskelenmemiş)
    echo $user->SSN;        // "123-45-6789" (maskelenmemiş)
}
```

#### Filtreleme ile Birlikte Kullanım

[](#filtreleme-ile-birlikte-kullanım)

```
// Maskeleme aktif, filtreleme yapılabilir
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Sonuçta CreditCard maskelenmiş gelir
echo $user->CreditCard; // "************3456"

// disableSensitive() ile maskelenmemiş veri
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->disableSensitive()
    ->firstOrDefault();

echo $user->CreditCard; // "1234-5678-9012-3456"
```

#### Repository Pattern ile Kullanım

[](#repository-pattern-ile-kullanım)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Repository\UnitOfWork;

$context = new ApplicationDbContext();
$unitOfWork = new UnitOfWork($context);
$userRepo = $unitOfWork->getRepository(User::class);

// Normal sorgu - maskelenmiş
$users = $userRepo->getAll()->toList();

// getAllDisableSensitive() ile maskelenmemiş
$users = $userRepo->getAllDisableSensitive()->toList();
```

#### Özel Maskeleme SQL'i

[](#özel-maskeleme-sqli)

```
class User
{
    // Özel SQL maskeleme ifadesi
    #[SensitiveValue(customMask: "SUBSTRING({column}, 1, 1) + REPLICATE('*', LEN({column}) - 2) + SUBSTRING({column}, LEN({column}), 1)")]
    public string $Email;
}
```

**Not**: `{column}` placeholder'ı kolon adı ile değiştirilir.

#### Veritabanı Desteği

[](#veritabanı-desteği)

Maskeleme tüm desteklenen veritabanlarında çalışır:

- **SQL Server**: `LEFT`, `REPLICATE`, `RIGHT` fonksiyonları
- **MySQL**: `SUBSTRING`, `REPEAT`, `CONCAT` fonksiyonları
- **PostgreSQL**: `SUBSTRING`, `REPEAT`, `CONCAT` fonksiyonları
- **SQLite**: `substr`, `zeroblob`, `replace` fonksiyonları

#### Güvenlik Notları

[](#güvenlik-notları)

1. **Varsayılan Davranış**: Varsayılan olarak hassas veriler maskelenmiş gelir
2. **Açık İzin**: `disableSensitive()` açıkça çağrılmadığı sürece maskeleme aktif kalır
3. **SQL Seviyesi**: Maskeleme SQL sorgusu seviyesinde yapılır, uygulama katmanında değil
4. **Performans**: Maskeleme SQL'de yapıldığı için ek performans maliyeti minimaldir
5. **Audit Log**: Maskelenmemiş verilere erişim için `disableSensitive()` kullanımını loglayın

#### Maskeleme Örnekleri

[](#maskeleme-örnekleri)

```
// İlk 2 karakter göster, gerisi maskelenir
#[SensitiveValue(visibleStart: 2, visibleEnd: 0)]
public string $FirstName; // "John" -> "Jo**", "Fatma" -> "Fa***"

// Sadece son 4 karakter göster
#[SensitiveValue(visibleStart: 0, visibleEnd: 4)]
public string $AccountNumber; // "1234567890" -> "******7890"

// İlk 3 ve son 3 karakter göster
#[SensitiveValue(visibleStart: 3, visibleEnd: 3)]
public string $Password; // "mySecretPassword123" -> "myS***********123"

// Tamamen maskele
#[SensitiveValue(visibleStart: 0, visibleEnd: 0)]
public string $SecretKey; // "abc123xyz" -> "*********"

// Farklı maskeleme karakteri
#[SensitiveValue(maskChar: 'X', visibleStart: 0, visibleEnd: 4)]
public string $PIN; // "123456" -> "XX3456"
```

### Repository Pattern

[](#repository-pattern)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Repository\UnitOfWork;
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();
$unitOfWork = new UnitOfWork($context);

$userRepo = $unitOfWork->getRepository(User::class);

// Get by ID
$user = $userRepo->getById(1);

// Add
$newUser = new User();
$newUser->FirstName = "John";
$newUser->LastName = "Doe";
$userRepo->add($newUser);

// Update
$user->FirstName = "Jane";
$userRepo->update($user);

// Remove
$userRepo->remove($user);

// Save changes
$unitOfWork->saveChanges();
```

### Add, Update, Delete İşlemleri

[](#add-update-delete-i̇şlemleri)

#### Tekil İşlemler (Change Tracker ile)

[](#tekil-i̇şlemler-change-tracker-ile)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Add - Entity'yi context'e ekle (henüz veritabanına kaydedilmez)
$newUser = new User();
$newUser->FirstName = "John";
$newUser->LastName = "Doe";
$newUser->Email = "john@example.com";
$context->add($newUser);

// Update - Entity'yi güncelle (henüz veritabanına kaydedilmez)
$user = $context->Users()->where(fn($u) => $u->Id === 1)->firstOrDefault();
if ($user) {
    $user->FirstName = "Jane";
    $context->update($user);
}

// Remove - Entity'yi sil (henüz veritabanından silinmez)
$context->remove($user);

// SaveChanges - Tüm değişiklikleri veritabanına kaydet
$affectedRows = $context->saveChanges();
echo "{$affectedRows} satır etkilendi";
```

#### Toplu İşlemler (Change Tracker ile)

[](#toplu-i̇şlemler-change-tracker-ile)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Toplu ekleme
$users = [
    new User(['FirstName' => 'John', 'LastName' => 'Doe']),
    new User(['FirstName' => 'Jane', 'LastName' => 'Smith']),
    new User(['FirstName' => 'Bob', 'LastName' => 'Johnson'])
];
$context->addRange($users);

// Toplu güncelleme
$usersToUpdate = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->toList();

foreach ($usersToUpdate as $user) {
    $user->Status = 'Active';
}
$context->updateRange($usersToUpdate);

// Toplu silme
$usersToDelete = $context->Users()
    ->where(fn($u) => $u->Status === 'Inactive')
    ->toList();
$context->removeRange($usersToDelete);

// Tüm değişiklikleri kaydet
$affectedRows = $context->saveChanges();
```

#### Toplu İşlemler (Doğrudan Veritabanı - Change Tracker Bypass)

[](#toplu-i̇şlemler-doğrudan-veritabanı---change-tracker-bypass)

Change Tracker'ı bypass ederek doğrudan veritabanına yazma işlemleri. Bu yöntem daha hızlıdır ancak change tracking özelliklerini kullanmaz.

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Toplu ekleme (doğrudan veritabanına)
$users = [
    new User(['FirstName' => 'John', 'LastName' => 'Doe', 'Email' => 'john@example.com']),
    new User(['FirstName' => 'Jane', 'LastName' => 'Smith', 'Email' => 'jane@example.com']),
    new User(['FirstName' => 'Bob', 'LastName' => 'Johnson', 'Email' => 'bob@example.com'])
];
$insertedCount = $context->batchInsert(User::class, $users);
echo "{$insertedCount} kullanıcı eklendi";

// Toplu güncelleme (doğrudan veritabanına)
$usersToUpdate = [
    new User(['Id' => 1, 'FirstName' => 'John Updated', 'LastName' => 'Doe']),
    new User(['Id' => 2, 'FirstName' => 'Jane Updated', 'LastName' => 'Smith'])
];
$updatedCount = $context->batchUpdate(User::class, $usersToUpdate);
echo "{$updatedCount} kullanıcı güncellendi";

// Toplu silme (ID'lere göre)
$userIds = [1, 2, 3, 4, 5];
$deletedCount = $context->batchDelete(User::class, $userIds);
echo "{$deletedCount} kullanıcı silindi";
```

#### Repository ile Toplu İşlemler

[](#repository-ile-toplu-i̇şlemler)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Repository\UnitOfWork;
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();
$unitOfWork = new UnitOfWork($context);
$userRepo = $unitOfWork->getRepository(User::class);

// Change Tracker ile toplu işlemler
$users = [
    new User(['FirstName' => 'John', 'LastName' => 'Doe']),
    new User(['FirstName' => 'Jane', 'LastName' => 'Smith'])
];
$userRepo->addRange($users);
$unitOfWork->saveChanges();

// Doğrudan veritabanı işlemleri (daha hızlı)
$users = [
    new User(['FirstName' => 'Bob', 'LastName' => 'Johnson']),
    new User(['FirstName' => 'Alice', 'LastName' => 'Williams'])
];
$insertedCount = $userRepo->batchInsert($users);

$usersToUpdate = [
    new User(['Id' => 1, 'FirstName' => 'John Updated']),
    new User(['Id' => 2, 'FirstName' => 'Jane Updated'])
];
$updatedCount = $userRepo->batchUpdate($usersToUpdate);

$deletedCount = $userRepo->batchDelete([3, 4, 5]);
```

#### Transaction ile Güvenli Toplu İşlemler

[](#transaction-ile-güvenli-toplu-i̇şlemler)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

try {
    $context->beginTransaction();

    // Toplu ekleme
    $users = [
        new User(['FirstName' => 'John', 'LastName' => 'Doe']),
        new User(['FirstName' => 'Jane', 'LastName' => 'Smith'])
    ];
    $context->addRange($users);

    // Toplu güncelleme
    $companies = $context->Companies()
        ->where(fn($c) => $c->Status === 'Pending')
        ->toList();
    foreach ($companies as $company) {
        $company->Status = 'Active';
    }
    $context->updateRange($companies);

    // Tüm değişiklikleri kaydet
    $affectedRows = $context->saveChanges();

    // İşlem başarılı - commit
    $context->commit();
    echo "{$affectedRows} satır başarıyla işlendi";

} catch (\Exception $e) {
    // Hata durumunda rollback
    $context->rollback();
    echo "Hata: " . $e->getMessage();
    throw $e;
}
```

#### Performans Karşılaştırması ve Öneriler

[](#performans-karşılaştırması-ve-öneriler)

**Change Tracker ile (addRange/updateRange/removeRange + saveChanges):**

✅ **Avantajlar:**

- Change tracking özellikleri aktif
- Entity state yönetimi (Added, Modified, Deleted, Unchanged)
- Audit field'ları otomatik güncellenir (CreatedAt, UpdatedAt)
- Navigation property'ler otomatik yüklenir
- Concurrency token kontrolü
- Validation ve business logic hook'ları

⚠️ **Dezavantajlar:**

- Daha yavaş (her entity için ayrı SQL işlemi)
- Daha fazla bellek kullanımı (entity tracking)
- Küçük işlemler için ideal

**Doğrudan Veritabanı (batchInsert/batchUpdate/batchDelete):**

✅ **Avantajlar:**

- Çok daha hızlı (toplu SQL işlemleri - INSERT/UPDATE/DELETE batch)
- Daha az bellek kullanımı (entity tracking yok)
- Büyük veri setleri için optimize edilmiş
- Transaction içinde çalışabilir

⚠️ **Dezavantajlar:**

- Change tracking yok
- Audit field'ları manuel güncellenmeli
- Navigation property'ler yüklenmez
- Entity state yönetimi yok

**Kullanım Önerileri:**

SenaryoÖnerilen YöntemNeden1-10 entity işlemiChange TrackerEntity state ve audit field'ları için10-100 entity işlemiChange Tracker veya Batchİhtiyaca göre100+ entity işlemiBatch OperationsPerformans içinAudit field'ları önemliChange TrackerOtomatik güncellemeSadece hız önemliBatch OperationsEn hızlı yöntemTransaction gerekiyorHer ikisi deİkisi de transaction destekler**Örnek Performans Testi:**

```
// 1000 entity ekleme testi
$users = []; // 1000 User entity

// Change Tracker ile: ~2-3 saniye
$context->addRange($users);
$context->saveChanges();

// Batch Insert ile: ~0.1-0.2 saniye
$context->batchInsert(User::class, $users);
```

**En İyi Pratikler:**

1. **Küçük işlemler (&lt; 50 entity)**: Change Tracker kullanın
2. **Orta işlemler (50-200 entity)**: İhtiyaca göre seçin
3. **Büyük işlemler (&gt; 200 entity)**: Batch operations kullanın
4. **Audit gereksinimi varsa**: Change Tracker kullanın
5. **Sadece hız önemliyse**: Batch operations kullanın
6. **Her zaman transaction kullanın**: Veri bütünlüğü için

#### Bulk Operations Optimization

[](#bulk-operations-optimization)

Bulk operations artık optimize edilmiş algoritmalar kullanıyor:

**Optimizasyonlar:**

1. **Chunking**: Büyük veri setleri otomatik olarak chunk'lara bölünür (default: 1000)
2. **Transaction Batching**: Çoklu chunk'lar transaction içinde çalışır
3. **CASE WHEN Updates**: MySQL/PostgreSQL için optimize edilmiş batch update
4. **MERGE Statements**: SQL Server için optimize edilmiş batch update
5. **Batch Size Control**: İhtiyaca göre batch size ayarlanabilir

**Örnek Kullanım:**

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Özel batch size ile insert
$users = [/* 5000 user */];
$inserted = $context->batchInsert(User::class, $users, 2000); // 2000'lik chunk'lar

// Özel batch size ile update
$updated = $context->batchUpdate(User::class, $users, 1500); // 1500'lük chunk'lar

// Özel batch size ile delete
$deleted = $context->batchDelete(User::class, $ids, 3000); // 3000'lük chunk'lar
```

**Performans İyileştirmeleri:**

- **Batch Insert**: %50-70 daha hızlı (chunking sayesinde)
- **Batch Update**: %80-90 daha hızlı (CASE WHEN/MERGE sayesinde)
- **Batch Delete**: %30-50 daha hızlı (chunking sayesinde)

**Database-Specific Optimizations:**

- **MySQL/PostgreSQL**: CASE WHEN statements kullanarak tek sorguda çoklu update
- **SQL Server**: MERGE statements kullanarak optimize edilmiş update
- **Tüm Database'ler**: Chunking ve transaction batching

### Compiled Queries (Performance Optimization)

[](#compiled-queries-performance-optimization)

Compiled Queries özelliği, sık kullanılan sorguları derleyip cache'leyerek performansı önemli ölçüde artırır. Bu özellik özellikle aynı sorgunun farklı parametrelerle tekrar tekrar çalıştırıldığı durumlarda çok etkilidir.

#### Avantajlar

[](#avantajlar)

- ✅ **Query Plan Cache**: SQL query plan'ları cache'lenir, tekrar oluşturulmaz
- ✅ **SQL Cache**: Derlenmiş SQL sorguları cache'lenir
- ✅ **Performans Artışı**: %30-70 arası performans artışı (sorgu tipine göre)
- ✅ **Otomatik Cache Yönetimi**: LRU (Least Recently Used) cache stratejisi
- ✅ **Cache İstatistikleri**: Cache hit/miss oranları takip edilir

#### Temel Kullanım

[](#temel-kullanım)

```
use App\EntityFramework\ApplicationDbContext;
use Yakupeyisan\CodeIgniter4\EntityFramework\Query\CompiledQuery;

$context = new ApplicationDbContext();

// Query'yi compile et (ilk çalıştırmada compile edilir, sonraki çalıştırmalarda cache'den gelir)
$compiledQuery = CompiledQuery::compile(function(DbContext $context, int $companyId) {
    return $context->Users()
        ->where(fn($u) => $u->CompanyId === $companyId)
        ->where(fn($u) => $u->Status === 'Active')
        ->include('Company')
        ->orderBy(fn($u) => $u->LastName);
});

// Farklı parametrelerle sorguyu çalıştır (cache'den hızlıca gelir)
$users1 = CompiledQuery::execute($compiledQuery, $context, 1);
$users2 = CompiledQuery::execute($compiledQuery, $context, 2);
$users3 = CompiledQuery::execute($compiledQuery, $context, 3);
```

#### DbContext ile Kullanım

[](#dbcontext-ile-kullanım)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// DbContext üzerinden compile et
$compiledQuery = $context->compileQuery(function(DbContext $context, int $companyId, string $status) {
    return $context->Users()
        ->where(fn($u) => $u->CompanyId === $companyId)
        ->where(fn($u) => $u->Status === $status)
        ->include('Company');
});

// Execute
$activeUsers = CompiledQuery::execute($compiledQuery, $context, 1, 'Active');
$pendingUsers = CompiledQuery::execute($compiledQuery, $context, 1, 'Pending');
```

#### Özel Cache Key ile Kullanım

[](#özel-cache-key-ile-kullanım)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Query\CompiledQuery;

// Özel cache key belirle
$compiledQuery = CompiledQuery::compile(
    function(DbContext $context, int $id) {
        return $context->Users()
            ->where(fn($u) => $u->Id === $id);
    },
    'get_user_by_id' // Özel cache key
);
```

#### Performans Karşılaştırması

[](#performans-karşılaştırması)

```
// Normal Query (her seferinde SQL oluşturulur)
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    $users = $context->Users()
        ->where(fn($u) => $u->CompanyId === $i)
        ->toList();
}
$normalTime = microtime(true) - $start;

// Compiled Query (ilk seferinde compile, sonra cache'den)
$compiledQuery = CompiledQuery::compile(function(DbContext $context, int $companyId) {
    return $context->Users()
        ->where(fn($u) => $u->CompanyId === $companyId);
});

$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
    $users = CompiledQuery::execute($compiledQuery, $context, $i);
}
$compiledTime = microtime(true) - $start;

echo "Normal Query: " . round($normalTime, 4) . "s\n";
echo "Compiled Query: " . round($compiledTime, 4) . "s\n";
echo "Performance Gain: " . round((($normalTime - $compiledTime) / $normalTime) * 100, 2) . "%\n";
```

#### Cache Yönetimi

[](#cache-yönetimi)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Query\CompiledQuery;
use Yakupeyisan\CodeIgniter4\EntityFramework\Query\QueryCache;

// Compiled query cache istatistikleri
$stats = CompiledQuery::getCacheStats();
echo "Cached Queries: " . $stats['cached_queries'] . "\n";
echo "Hit Rate: " . $stats['hit_rate'] . "\n";

// Query cache istatistikleri
$cacheStats = QueryCache::getStats();
echo "SQL Cache Size: " . $cacheStats['sql_cache_size'] . "\n";

// Cache'i temizle
CompiledQuery::clearCache();
QueryCache::clear();
```

#### Ne Zaman Kullanılmalı?

[](#ne-zaman-kullanılmalı)

✅ **Kullanın:**

- Aynı query yapısı farklı parametrelerle sık çalıştırılıyorsa
- Performans kritikse
- Parametreli query'ler için

❌ **Kullanmayın:**

- Her seferinde farklı query yapısı kullanılıyorsa
- Tek seferlik query'ler için

```

### Advanced Query Hints and Optimizations

Advanced Query Hints and Optimizations özelliği, SQL sorgularınıza database-specific hints ve optimizasyonlar eklemenizi sağlar. Bu özellik sayesinde query performansını optimize edebilir, index kullanımını kontrol edebilir ve database-specific optimizasyonlar uygulayabilirsiniz.

#### Özellikler

- ✅ **Query Timeout**: Query execution timeout ayarlama
- ✅ **Index Hints**: USE INDEX, FORCE INDEX, IGNORE INDEX desteği
- ✅ **Lock Hints**: SQL Server için NOLOCK, READPAST, vb. lock hints
- ✅ **Optimizer Hints**: Database-specific optimizer hints
- ✅ **Query Cache Control**: Query cache'i devre dışı bırakma
- ✅ **Max Rows**: Maksimum döndürülecek satır sayısı
- ✅ **Database-Specific**: MySQL, SQL Server, PostgreSQL için özel optimizasyonlar

#### Temel Kullanım

```php
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Query timeout ayarla
$users = $context->Users()
    ->timeout(30) // 30 saniye timeout
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// Index hint kullan
$users = $context->Users()
    ->useIndex('idx_status') // Belirli index kullan
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// Force index
$users = $context->Users()
    ->forceIndex('idx_status') // Index'i zorla kullan
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// Ignore index
$users = $context->Users()
    ->ignoreIndex('idx_status') // Index'i ignore et
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

```

#### SQL Server Lock Hints

[](#sql-server-lock-hints)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// NOLOCK hint (dirty reads)
$users = $context->Users()
    ->withLock('NOLOCK')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// READPAST hint (skip locked rows)
$users = $context->Users()
    ->withLock('READPAST')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// READCOMMITTED hint
$users = $context->Users()
    ->withLock('READCOMMITTED')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

#### Query Hints Builder

[](#query-hints-builder)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Multiple hints
$users = $context->Users()
    ->withHints(function($hints) {
        $hints->timeout(30)
              ->useIndex('idx_status')
              ->noCache();
    })
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

#### Optimizer Hints

[](#optimizer-hints)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// MySQL optimizer hints
$users = $context->Users()
    ->optimizerHint('STRAIGHT_JOIN')
    ->include('Company')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// SQL Server query hints
$users = $context->Users()
    ->optimizerHint('MAXDOP 4') // Maximum degree of parallelism
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();

// Multiple optimizer hints
$users = $context->Users()
    ->optimizerHint('STRAIGHT_JOIN')
    ->optimizerHint('USE_INDEX_MERGE')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

#### Query Cache Control

[](#query-cache-control)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Query cache'i devre dışı bırak
$users = $context->Users()
    ->noCache()
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

#### Max Rows

[](#max-rows)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Maksimum 100 satır döndür
$users = $context->Users()
    ->withHints(function($hints) {
        $hints->maxRows(100);
    })
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

#### Database-Specific Hints

[](#database-specific-hints)

**MySQL/MariaDB:**

```
$users = $context->Users()
    ->useIndex('idx_status')
    ->optimizerHint('STRAIGHT_JOIN')
    ->timeout(30)
    ->toList();
```

**SQL Server:**

```
$users = $context->Users()
    ->withLock('NOLOCK')
    ->optimizerHint('MAXDOP 4')
    ->optimizerHint('OPTIMIZE FOR UNKNOWN')
    ->timeout(30)
    ->toList();
```

**PostgreSQL:**

```
$users = $context->Users()
    ->optimizerHint('SeqScan(users)') // pg_hint_plan extension required
    ->timeout(30)
    ->toList();
```

#### Best Practices

[](#best-practices-1)

1. **Index Hints Dikkatli Kullanın**: Index hints, query optimizer'ın kararlarını override eder. Sadece gerektiğinde kullanın.
2. **Lock Hints**: SQL Server'da NOLOCK kullanırken dirty reads olabileceğini unutmayın.
3. **Timeout Ayarlayın**: Uzun süren query'ler için timeout ayarlayın.
4. **Query Cache**: Sık değişen data için noCache() kullanın.
5. **Optimizer Hints**: Database-specific optimizer hints kullanırken dikkatli olun.

#### Örnek Senaryolar

[](#örnek-senaryolar)

**Senaryo 1: Performance Optimization**

```
$users = $context->Users()
    ->withHints(function($hints) {
        $hints->timeout(30)
              ->useIndex('idx_status_company')
              ->noCache();
    })
    ->where(fn($u) => $u->Status === 'Active')
    ->where(fn($u) => $u->CompanyId === 1)
    ->toList();
```

**Senaryo 2: SQL Server Read-Only Query**

```
$users = $context->Users()
    ->withLock('NOLOCK')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

**Senaryo 3: Complex Query with Multiple Hints**

```
$users = $context->Users()
    ->withHints(function($hints) {
        $hints->timeout(60)
              ->forceIndex('idx_status')
              ->optimizerHint('STRAIGHT_JOIN')
              ->maxRows(1000);
    })
    ->include('Company')
    ->where(fn($u) => $u->Status === 'Active')
    ->orderBy(fn($u) => $u->CreatedAt)
    ->toList();
```

### Additional Database-Specific Features

[](#additional-database-specific-features)

Additional Database-Specific Features özelliği, farklı veritabanı sağlayıcıları için özel özellikler sağlar. Full-text search, JSON functions, window functions ve array functions gibi database-specific özellikleri kullanabilirsiniz.

#### Özellikler

[](#özellikler)

- ✅ **Full-Text Search**: MySQL, PostgreSQL, SQL Server için full-text search
- ✅ **JSON Functions**: JSON\_EXTRACT, JSON\_CONTAINS, JSON\_LENGTH
- ✅ **Window Functions**: ROW\_NUMBER, RANK, DENSE\_RANK
- ✅ **Array Functions**: PostgreSQL array functions
- ✅ **Database-Specific**: Her veritabanı için özel optimizasyonlar

#### Full-Text Search

[](#full-text-search)

**MySQL:**

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Full-text search
$users = $context->Users()
    ->databaseSpecific()
    ->fullTextSearch('FirstName', 'John', 'natural')
    ->toList();

// Boolean mode
$users = $context->Users()
    ->databaseSpecific()
    ->fullTextSearch('Description', 'search term', 'boolean')
    ->toList();
```

**PostgreSQL:**

```
$users = $context->Users()
    ->databaseSpecific()
    ->fullTextSearch('FirstName', 'John')
    ->toList();
```

**SQL Server:**

```
$users = $context->Users()
    ->databaseSpecific()
    ->fullTextSearch('FirstName', 'John')
    ->toList();
```

#### JSON Functions

[](#json-functions)

**JSON Extract:**

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Extract JSON value
$users = $context->Users()
    ->databaseSpecific()
    ->jsonExtract('Metadata', '$.email')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

**JSON Contains:**

```
// Check if JSON contains value
$users = $context->Users()
    ->databaseSpecific()
    ->jsonContains('Metadata', '$.tags', 'premium')
    ->toList();
```

**JSON Array Length:**

```
// Get JSON array length
$users = $context->Users()
    ->databaseSpecific()
    ->jsonArrayLength('Tags')
    ->toList();
```

#### Window Functions

[](#window-functions)

**ROW\_NUMBER:**

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Add row number
$users = $context->Users()
    ->databaseSpecific()
    ->rowNumber('CompanyId', 'CreatedAt DESC')
    ->where(fn($u) => $u->Status === 'Active')
    ->toList();
```

**RANK:**

```
// Add rank
$users = $context->Users()
    ->databaseSpecific()
    ->rank('CompanyId', 'Score DESC')
    ->toList();
```

**DENSE\_RANK:**

```
// Add dense rank
$users = $context->Users()
    ->databaseSpecific()
    ->denseRank('CompanyId', 'Score DESC')
    ->toList();
```

**Custom Window Function:**

```
// Custom window function
$users = $context->Users()
    ->databaseSpecific()
    ->windowFunction('SUM(Score)', 'CompanyId', 'CreatedAt')
    ->toList();
```

#### PostgreSQL Array Functions

[](#postgresql-array-functions)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Array contains
$users = $context->Users()
    ->databaseSpecific()
    ->arrayContains('Tags', 'premium')
    ->toList();

// Array length
$users = $context->Users()
    ->databaseSpecific()
    ->arrayLength('Tags')
    ->toList();
```

#### Best Practices

[](#best-practices-2)

1. **Full-Text Search**: Index'leri oluşturmayı unutmayın
2. **JSON Functions**: JSON kolonlarını index'leyin
3. **Window Functions**: Partition ve order by kullanırken index'leri optimize edin
4. **Array Functions**: PostgreSQL array functions için GIN index kullanın
5. **Database-Specific**: Her veritabanı için özel optimizasyonları kullanın

### Query Plan Optimization

[](#query-plan-optimization)

Query Plan Optimization özelliği, SQL sorgularınızın performansını analiz eder ve optimizasyon önerileri sunar. Bu özellik sayesinde yavaş çalışan sorguları tespit edip optimize edebilirsiniz.

#### Özellikler

[](#özellikler-1)

- ✅ **EXPLAIN Plan Analizi**: MySQL, PostgreSQL, SQL Server için EXPLAIN plan analizi
- ✅ **Index Önerileri**: Eksik index'leri tespit eder ve önerir
- ✅ **Performans Skoru**: Query'nin performans skorunu hesaplar (0-100)
- ✅ **Uyarılar ve Öneriler**: Yavaş query'ler için uyarılar ve optimizasyon önerileri
- ✅ **Query İstatistikleri**: Execution time, rows returned, rows affected
- ✅ **Query Karşılaştırması**: İki query'yi karşılaştırıp hangisinin daha iyi olduğunu gösterir

#### Temel Kullanım

[](#temel-kullanım-1)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Query oluştur
$query = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->where(fn($u) => $u->Status === 'Active');

// Query plan analizi
$analysis = $query->analyzePlan();

echo "Performance Score: " . $analysis['performance_score'] . "\n";
echo "Performance Rating: " . $analysis['performance_rating'] . "\n";

// Uyarıları göster
foreach ($analysis['warnings'] as $warning) {
    echo "⚠️ Warning: " . $warning . "\n";
}

// Önerileri göster
foreach ($analysis['recommendations'] as $recommendation) {
    echo "✅ Recommendation: " . $recommendation . "\n";
}

// Index önerileri
foreach ($analysis['index_suggestions'] as $suggestion) {
    echo "📊 Index Suggestion: " . $suggestion . "\n";
}
```

#### Query İstatistikleri

[](#query-i̇statistikleri)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$query = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1);

// Query istatistiklerini al
$stats = $query->getStats();

echo "Execution Time: " . $stats['execution_time'] . " ms\n";
echo "Rows Returned: " . $stats['rows_returned'] . "\n";
echo "Rows Affected: " . ($stats['rows_affected'] ?? 'N/A') . "\n";
```

#### Detaylı Analiz

[](#detaylı-analiz)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$query = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->orderBy(fn($u) => $u->LastName);

$analysis = $query->analyzePlan();

// SQL sorgusu
echo "SQL: " . $analysis['sql'] . "\n";

// EXPLAIN plan
echo "EXPLAIN Plan:\n";
print_r($analysis['explain_plan']);

// Performans skoru
echo "Performance Score: " . $analysis['performance_score'] . "/100\n";
echo "Performance Rating: " . $analysis['performance_rating'] . "\n";

// Tüm uyarılar
if (!empty($analysis['warnings'])) {
    echo "\n⚠️ Warnings:\n";
    foreach ($analysis['warnings'] as $warning) {
        echo "  - " . $warning . "\n";
    }
}

// Tüm öneriler
if (!empty($analysis['recommendations'])) {
    echo "\n✅ Recommendations:\n";
    foreach ($analysis['recommendations'] as $recommendation) {
        echo "  - " . $recommendation . "\n";
    }
}

// Index önerileri
if (!empty($analysis['index_suggestions'])) {
    echo "\n📊 Index Suggestions:\n";
    foreach ($analysis['index_suggestions'] as $suggestion) {
        echo "  - " . $suggestion . "\n";
    }
}
```

#### Query Karşılaştırması

[](#query-karşılaştırması)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// İlk query
$query1 = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1);

// İkinci query (optimize edilmiş)
$query2 = $context->Users()
    ->where(fn($u) => $u->CompanyId === 1)
    ->asNoTracking(); // Change tracking'i kapat

// Query'leri karşılaştır
$analyzer = new \Yakupeyisan\CodeIgniter4\EntityFramework\Query\QueryPlanAnalyzer(
    $context->getConnection()
);

$comparison = $analyzer->comparePlans($query1->toSql(), $query2->toSql());

echo "Performance Score Difference: " . $comparison['comparison']['performance_score_diff'] . "\n";
echo "Execution Time Difference: " . $comparison['comparison']['execution_time_diff'] . " ms\n";
echo "Better Query: " . $comparison['comparison']['better_query'] . "\n";
```

#### Tespit Edilen Sorunlar

[](#tespit-edilen-sorunlar)

Query Plan Analyzer şu sorunları tespit eder:

**1. Full Table Scan**

```
// Uyarı: Full table scan detected - consider adding an index
$query = $context->Users()
    ->where(fn($u) => $u->Email === 'test@example.com');
// Çözüm: Email kolonuna index ekleyin
```

**2. File Sort**

```
// Uyarı: File sort detected - consider adding index for ORDER BY columns
$query = $context->Users()
    ->orderBy(fn($u) => $u->LastName);
// Çözüm: LastName kolonuna index ekleyin
```

**3. Temporary Table**

```
// Uyarı: Temporary table detected - query may be inefficient
$query = $context->Users()
    ->groupBy(fn($u) => $u->CompanyId);
// Çözüm: Query'yi optimize edin veya index ekleyin
```

**4. Functions in WHERE Clause**

```
// Uyarı: Functions in WHERE clause detected - may prevent index usage
$query = $context->Users()
    ->where(fn($u) => $u->Email->toLower() === 'test@example.com');
// Çözüm: WHERE koşulunda function kullanmayın, veriyi normalize edin
```

**5. LIKE with Leading Wildcard**

```
// Uyarı: LIKE with leading wildcard detected - cannot use index efficiently
$query = $context->Users()
    ->where(fn($u) => $u->Email->contains('@example.com'));
// Çözüm: Full-text search kullanın veya farklı bir yaklaşım deneyin
```

#### Performans Skoru

[](#performans-skoru)

Query Plan Analyzer, query'nin performansını 0-100 arası bir skorla değerlendirir:

- **80-100**: Excellent - Query optimize edilmiş
- **60-79**: Good - Query iyi durumda, küçük iyileştirmeler yapılabilir
- **40-59**: Fair - Query orta seviyede, optimizasyon gerekli
- **0-39**: Poor - Query yavaş, ciddi optimizasyon gerekli

#### En İyi Pratikler

[](#en-i̇yi-pratikler)

1. **Düzenli Analiz**: Yavaş query'leri düzenli olarak analiz edin
2. **Index Kullanımı**: Index önerilerini uygulayın
3. **Query Optimizasyonu**: Uyarıları dikkate alın ve query'leri optimize edin
4. **AsNoTracking**: Change tracking gerekmeyen query'lerde `asNoTracking()` kullanın
5. **Select Specific Columns**: `SELECT *` yerine sadece ihtiyaç duyulan kolonları seçin
6. **Avoid Functions in WHERE**: WHERE clause'da function kullanmayın
7. **Index for JOINs**: JOIN kolonlarına index ekleyin

#### Örnek Optimizasyon Senaryosu

[](#örnek-optimizasyon-senaryosu)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Önceki query (yavaş)
$slowQuery = $context->Users()
    ->where(fn($u) => $u->Email->toLower() === 'test@example.com')
    ->orderBy(fn($u) => $u->LastName);

$analysis = $slowQuery->analyzePlan();
echo "Initial Score: " . $analysis['performance_score'] . "\n";
// Output: Initial Score: 60

// Optimize edilmiş query
$fastQuery = $context->Users()
    ->where(fn($u) => $u->Email === 'test@example.com') // Function kaldırıldı
    ->orderBy(fn($u) => $u->LastName)
    ->asNoTracking(); // Change tracking kapatıldı

$analysis = $fastQuery->analyzePlan();
echo "Optimized Score: " . $analysis['performance_score'] . "\n";
// Output: Optimized Score: 85
```

### Lazy Loading (Proxy Implementation)

[](#lazy-loading-proxy-implementation)

Lazy Loading özelliği, navigation property'lere ilk erişildiğinde otomatik olarak veritabanından yüklenmesini sağlar. Bu özellik sayesinde Include kullanmadan navigation property'lere erişebilirsiniz.

#### Avantajlar

[](#avantajlar-1)

- ✅ **Otomatik Yükleme**: Navigation property'lere erişildiğinde otomatik yüklenir
- ✅ **Performans**: Sadece ihtiyaç duyulduğunda yüklenir
- ✅ **Kolay Kullanım**: Include kullanmadan navigation property'lere erişim
- ✅ **Proxy Tabanlı**: Entity Framework Core'daki gibi proxy pattern kullanır
- ✅ **Enable/Disable**: İstediğiniz zaman açıp kapatabilirsiniz

#### Temel Kullanım

[](#temel-kullanım-2)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// User entity'sini yükle (Company navigation property yüklenmez)
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Company navigation property'sine erişildiğinde otomatik yüklenir
echo $user->Company->Name; // Otomatik olarak Company yüklenir ve Name'e erişilir

// Collection navigation property'ler de otomatik yüklenir
foreach ($user->UserDepartments as $userDept) {
    echo $userDept->Department->Name; // Nested lazy loading
}
```

#### Lazy Loading'i Açma/Kapama

[](#lazy-loadingi-açmakapama)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Lazy loading varsayılan olarak açıktır
// Kapatmak için:
$context->disableLazyLoading();

// Tekrar açmak için:
$context->enableLazyLoading();

// Durumu kontrol etmek için:
if ($context->isLazyLoadingEnabled()) {
    echo "Lazy loading is enabled";
}
```

#### Reference Navigation (Many-to-One, One-to-One)

[](#reference-navigation-many-to-one-one-to-one)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// User yükle (Company yüklenmez)
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Company'ye ilk erişimde otomatik yüklenir
$companyName = $user->Company->Name; // Lazy loading tetiklenir

// Aynı navigation property'ye tekrar erişimde cache'den gelir
$companyDescription = $user->Company->Description; // Yeni sorgu yapılmaz
```

#### Collection Navigation (One-to-Many)

[](#collection-navigation-one-to-many)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Company yükle (Users collection yüklenmez)
$company = $context->Companies()
    ->where(fn($c) => $c->Id === 1)
    ->firstOrDefault();

// Users collection'ına ilk erişimde otomatik yüklenir
foreach ($company->Users as $user) {
    echo $user->FirstName . "\n"; // Lazy loading tetiklenir
}
```

#### Nested Lazy Loading

[](#nested-lazy-loading)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Company lazy loading
$company = $user->Company; // Company yüklenir

// Company'nin navigation property'si de lazy loading ile yüklenir
$country = $company->Country; // Country yüklenir
```

#### Explicit Loading ile Birlikte Kullanım

[](#explicit-loading-ile-birlikte-kullanım)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Explicit loading - Company'yi manuel yükle
$context->entry($user)->reference('Company')->load();

// Artık Company yüklü, lazy loading tetiklenmez
$companyName = $user->Company->Name;
```

#### Navigation Property Yükleme Durumu Kontrolü

[](#navigation-property-yükleme-durumu-kontrolü)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->firstOrDefault();

// Navigation property'nin yüklenip yüklenmediğini kontrol et
if ($user->isNavigationPropertyLoaded('Company')) {
    echo "Company is already loaded";
} else {
    echo "Company will be loaded on first access";
    $company = $user->Company; // Lazy loading tetiklenir
}
```

#### Lazy Loading vs Eager Loading

[](#lazy-loading-vs-eager-loading)

**Lazy Loading:**

```
// Company yüklenmez
$user = $context->Users()->where(fn($u) => $u->Id === 1)->firstOrDefault();

// İlk erişimde yüklenir (ekstra SQL sorgusu)
$company = $user->Company;
```

**Eager Loading (Include):**

```
// Company tek sorguda yüklenir
$user = $context->Users()
    ->where(fn($u) => $u->Id === 1)
    ->include('Company')
    ->firstOrDefault();

// Zaten yüklü, ekstra sorgu yok
$company = $user->Company;
```

#### Performans Notları

[](#performans-notları-1)

⚠️ **N+1 Query Problemi:**

```
// Bu kod N+1 query problemi yaratır
$users = $context->Users()->toList();
foreach ($users as $user) {
    echo $user->Company->Name; // Her user için ayrı Company sorgusu
}
```

✅ **Çözüm - Eager Loading:**

```
// Tek sorguda tüm Company'ler yüklenir
$users = $context->Users()
    ->include('Company')
    ->toList();
foreach ($users as $user) {
    echo $user->Company->Name; // Ekstra sorgu yok
}
```

#### En İyi Pratikler

[](#en-i̇yi-pratikler-1)

1. **Küçük Veri Setleri**: Lazy loading kullanın
2. **Büyük Veri Setleri**: Eager loading (Include) kullanın
3. **N+1 Problemi**: Döngülerde Include kullanın
4. **Performans Kritik**: Lazy loading'i kapatın ve sadece Include kullanın
5. **Basit Senaryolar**: Lazy loading kullanın (daha kolay kod)

#### Lazy Loading'i Ne Zaman Kullanmalı?

[](#lazy-loadingi-ne-zaman-kullanmalı)

✅ **Kullanın:**

- Tek entity üzerinde çalışırken
- Navigation property'lerin sadece bir kısmına ihtiyaç duyulduğunda
- Basit senaryolarda

❌ **Kullanmayın:**

- Döngülerde (N+1 problemi)
- Büyük veri setlerinde
- Performans kritik durumlarda
- Tüm navigation property'lere ihtiyaç duyulduğunda (Include kullanın)

### Transaction Management

[](#transaction-management)

Transaction Management özelliği, gelişmiş transaction yönetimi sağlar. Nested transactions, savepoints, isolation levels ve otomatik transaction scope desteği içerir.

#### Temel Transaction Kullanımı

[](#temel-transaction-kullanımı)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$context->beginTransaction();
try {
    $user = new User();
    $user->FirstName = "John";
    $user->LastName = "Doe";
    $context->add($user);

    $company = new Company();
    $company->Name = "New Company";
    $context->add($company);

    $context->saveChanges();
    $context->commit();
} catch (\Exception $e) {
    $context->rollback();
    throw $e;
}
```

#### Transaction Scope (Otomatik Yönetim)

[](#transaction-scope-otomatik-yönetim)

Transaction scope, otomatik olarak commit/rollback yapar:

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Otomatik commit/rollback
$scope = $context->transactionScope();
try {
    $user = new User();
    $user->FirstName = "John";
    $context->add($user);
    $context->saveChanges();

    $scope->complete(); // Commit
} catch (\Exception $e) {
    // Otomatik rollback (destructor)
    throw $e;
}
```

#### ExecuteInTransaction (Kolay Kullanım)

[](#executeintransaction-kolay-kullanım)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Tek satırda transaction
$result = $context->executeInTransaction(function($ctx) {
    $user = new User();
    $user->FirstName = "John";
    $ctx->add($user);
    $ctx->saveChanges();
    return $user;
});

// Otomatik commit (exception durumunda rollback)
```

#### Nested Transactions (Savepoints)

[](#nested-transactions-savepoints)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Root transaction
$context->beginTransaction();

try {
    $user = new User();
    $user->FirstName = "John";
    $context->add($user);
    $context->saveChanges();

    // Nested transaction (savepoint)
    $context->beginTransaction();
    try {
        $company = new Company();
        $company->Name = "New Company";
        $context->add($company);
        $context->saveChanges();

        $context->commit(); // Release savepoint
    } catch (\Exception $e) {
        $context->rollback(); // Rollback to savepoint
        throw $e;
    }

    $context->commit(); // Commit root transaction
} catch (\Exception $e) {
    $context->rollback(); // Rollback root transaction
    throw $e;
}
```

#### Transaction Isolation Levels

[](#transaction-isolation-levels)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Isolation level ile transaction başlat
$context->beginTransaction('READ COMMITTED');

// Veya mevcut transaction'da isolation level değiştir
$context->setTransactionIsolationLevel('SERIALIZABLE');

try {
    // Transaction işlemleri
    $context->saveChanges();
    $context->commit();
} catch (\Exception $e) {
    $context->rollback();
    throw $e;
}
```

**Desteklenen Isolation Levels:**

- `READ UNCOMMITTED` - En düşük izolasyon, dirty reads mümkün
- `READ COMMITTED` - Default (çoğu database), dirty reads önlenir
- `REPEATABLE READ` - Phantom reads mümkün
- `SERIALIZABLE` - En yüksek izolasyon, tüm anomalies önlenir

#### Transaction Scope ile Isolation Level

[](#transaction-scope-ile-isolation-level)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Isolation level ve timeout ile scope
$scope = $context->transactionScope('REPEATABLE READ', 30); // 30 saniye timeout

try {
    // Transaction işlemleri
    $scope->complete();
} catch (\Exception $e) {
    // Otomatik rollback
    throw $e;
}
```

#### Transaction Callbacks

[](#transaction-callbacks)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$scope = $context->transactionScope();

// Commit sonrası callback
$scope->onComplete(function() {
    echo "Transaction committed successfully!";
});

try {
    // Transaction işlemleri
    $scope->complete();
} catch (\Exception $e) {
    throw $e;
}
```

#### Transaction Statistics

[](#transaction-statistics)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Transaction istatistiklerini al
$stats = $context->getTransactionStatistics();

echo "Total Transactions: " . $stats['total_transactions'] . "\n";
echo "Committed: " . $stats['committed'] . "\n";
echo "Rolled Back: " . $stats['rolled_back'] . "\n";
echo "Nested Transactions: " . $stats['nested_transactions'] . "\n";
echo "Savepoints Created: " . $stats['savepoints_created'] . "\n";
echo "Current Level: " . $stats['current_level'] . "\n";
```

#### Transaction Level Kontrolü

[](#transaction-level-kontrolü)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

// Transaction level kontrolü
if ($context->getTransactionLevel() > 0) {
    echo "Transaction is active (level: " . $context->getTransactionLevel() . ")";
}

// Transaction aktif mi?
if ($context->isTransactionActive()) {
    echo "Transaction is active";
}
```

#### Rollback to Specific Savepoint

[](#rollback-to-specific-savepoint)

```
use App\EntityFramework\ApplicationDbContext;

$context = new ApplicationDbContext();

$context->beginTransaction();

try {
    // İşlem 1
    $user = new User();
    $context->add($user);
    $context->saveChanges();

    // Savepoint oluştur
    $context->beginTransaction(); // Creates savepoint

    try {
        // İşlem 2
        $company = new Company();
        $context->add($company);
        $context->saveChanges();

        $context->commit(); // Release savepoint
    } catch (\Exception $e) {
        // Belirli savepoint'e rollback
        $context->rollback('sp_1'); // Rollback to savepoint
        throw $e;
    }

    $context->commit();
} catch (\Exception $e) {
    $context->rollback();
    throw $e;
}
```

#### Best Practices

[](#best-practices-3)

1. **Her zaman try-catch kullanın**: Exception durumunda rollback yapın
2. **Transaction Scope kullanın**: Otomatik yönetim için
3. **Nested transactions dikkatli kullanın**: Performans etkisi olabilir
4. **Isolation level seçimi**: İhtiyaca göre uygun isolation level seçin
5. **Transaction timeout**: Uzun süren işlemler için timeout ayarlayın
6. **Statistics monitoring**: Transaction istatistiklerini düzenli kontrol edin

#### Örnek Senaryolar

[](#örnek-senaryolar-1)

**Senaryo 1: Basit Transaction**

```
$context->beginTransaction();
try {
    // İşlemler
    $context->saveChanges();
    $context->commit();
} catch (\Exception $e) {
    $context->rollback();
    throw $e;
}
```

**Senaryo 2: Transaction Scope**

```
$result = $context->executeInTransaction(function($ctx) {
    // İşlemler
    return $result;
});
```

**Senaryo 3: Nested Transaction**

```
$context->beginTransaction();
try {
    // Root işlemler
    $context->beginTransaction(); // Savepoint
    try {
        // Nested işlemler
        $context->commit();
    } catch (\Exception $e) {
        $context->rollback();
        throw $e;
    }
    $context->commit();
} catch (\Exception $e) {
    $context->rollback();
    throw $e;
}
```

**Senaryo 4: Isolation Level ile**

```
$scope = $context->transactionScope('SERIALIZABLE', 60);
try {
    // Kritik işlemler
    $scope->complete();
} catch (\Exception $e) {
    throw $e;
}
```

### Migration Usage

[](#migration-usage)

#### Manuel Migration Oluşturma

[](#manuel-migration-oluşturma)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Migrations\MigrationManager;

$migrationManager = new MigrationManager();

// Add migration
$migrationManager->addMigration('AddUserTable', function($builder) {
    $builder->createTable('Users', function($columns) {
        $columns->integer('Id')->primaryKey()->autoIncrement();
        $columns->integer('CompanyId')->notNull();
        $columns->string('FirstName', 100)->notNull();
        $columns->string('LastName', 100)->notNull();
        $columns->dateTime('CreatedAt')->nullable();
    });
}, function($builder) {
    $builder->dropTable('Users');
});

// Update database
$migrationManager->updateDatabase();

// Rollback
$migrationManager->rollbackMigration(1);
```

#### Otomatik Migration Üretimi (MigrationGenerator)

[](#otomatik-migration-üretimi-migrationgenerator)

MigrationGenerator, ApplicationDbContext'inizi analiz ederek otomatik olarak migration kodları üretir. Bu özellik sayesinde entity'lerinizdeki değişiklikleri manuel olarak migration'a dönüştürmenize gerek kalmaz.

##### Özellikler

[](#özellikler-2)

- ✅ **Otomatik Entity Analizi**: ApplicationDbContext'teki tüm entity'leri otomatik olarak bulur
- ✅ **Attribute Desteği**: Entity attribute'larından (Table, Key, Column, ForeignKey, vb.) şema bilgilerini çıkarır
- ✅ **Akıllı Migration**: Mevcut tabloları kontrol eder, sadece yeni tabloları veya değişiklikleri ekler
- ✅ **Bağımlılık Yönetimi**: Foreign key bağımlılıklarına göre tabloları doğru sırada oluşturur
- ✅ **Rollback Desteği**: Down migration'ları otomatik olarak üretir

##### Kullanım

[](#kullanım)

**1. ApplicationDbContext Hazırlama**

Önce entity'lerinizi ve ApplicationDbContext'inizi hazırlayın:

```
// app/EntityFramework/ApplicationDbContext.php
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\DbContext;
use App\Models\User;
use App\Models\Company;

class ApplicationDbContext extends DbContext
{
    protected function onModelCreating(): void
    {
        // Fluent API ile entity konfigürasyonları
        $this->entity(User::class)
            ->hasKey('Id')
            ->toTable('Users');
    }

    public function Users()
    {
        return $this->set(User::class);
    }

    public function Companies()
    {
        return $this->set(Company::class);
    }
}
```

**2. Entity Tanımlamaları (Attribute ile)**

```
// app/Models/User.php
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Table;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Key;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\DatabaseGenerated;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Required;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\MaxLength;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\ForeignKey;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Index;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\AuditFields;

#[Table("Users")]
#[Index("CompanyId")]
#[AuditFields(createdAt: true, updatedAt: true, deletedAt: true)]
class User extends Entity
{
    #[Key]
    #[DatabaseGenerated(DatabaseGenerated::IDENTITY)]
    public int $Id;

    #[Required]
    #[MaxLength(100)]
    public string $FirstName;

    #[Required]
    #[MaxLength(100)]
    public string $LastName;

    #[ForeignKey("Company")]
    public int $CompanyId;

    public ?Company $Company = null;
}
```

```
// app/Models/Company.php
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Table;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Key;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\DatabaseGenerated;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Required;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\MaxLength;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\AuditFields;

#[Table("Companies")]
#[AuditFields(createdAt: true, updatedAt: true)]
class Company extends Entity
{
    #[Key]
    #[DatabaseGenerated(DatabaseGenerated::IDENTITY)]
    public int $Id;

    #[Required]
    #[MaxLength(255)]
    public string $Name;

    public ?string $Description = null;
}
```

**3. Otomatik Migration Oluşturma**

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Migrations\MigrationManager;

$migrationManager = new MigrationManager();

// Otomatik olarak ApplicationDbContext'ten migration üret
$fileName = $migrationManager->addMigration('InitialCreate');
// Dosya: 20240101120000_InitialCreate.php

// Migration dosyası otomatik olarak oluşturulur:
// - Yeni tablolar için createTable
// - Mevcut tablolar için addColumn, createIndex, addForeignKey
// - Down migration'ları otomatik olarak üretilir
```

**4. Migration'ı Veritabanına Uygulama**

```
// Tüm bekleyen migration'ları uygula
$migrationManager->updateDatabase();

// Belirli bir migration'a kadar uygula
$migrationManager->updateDatabase('20240101120000_InitialCreate');
```

**5. Migration Rollback**

```
// Son migration'ı geri al
$migrationManager->rollbackMigration(1);

// Son 3 migration'ı geri al
$migrationManager->rollbackMigration(3);
```

##### MigrationGenerator Nasıl Çalışır?

[](#migrationgenerator-nasıl-çalışır)

1. **Entity Keşfi**: ApplicationDbContext'teki public metodları (Users, Companies, vb.) analiz eder
2. **Reflection Analizi**: Her entity için Reflection kullanarak attribute'ları ve property'leri inceler
3. **Şema Çıkarımı**:
    - Table attribute'undan tablo adını alır
    - Key attribute'undan primary key'i belirler
    - Column attribute'undan kolon tipini ve özelliklerini çıkarır
    - ForeignKey attribute'undan ilişkileri tespit eder
    - Index attribute'undan index'leri belirler
    - AuditFields attribute'undan audit kolonlarını ekler
4. **Mevcut Şema Kontrolü**: Veritabanındaki mevcut tabloları kontrol eder
5. **Akıllı Migration**:
    - Yeni tablolar için `createTable` kullanır
    - Mevcut tablolar için sadece yeni kolonlar, indexler ve foreign key'ler ekler
6. **Bağımlılık Sıralaması**: Foreign key bağımlılıklarına göre tabloları doğru sırada oluşturur

##### Örnek: Üretilen Migration Kodu

[](#örnek-üretilen-migration-kodu)

```
// Otomatik üretilen migration dosyası örneği
namespace App\Database\Migrations;

use Yakupeyisan\CodeIgniter4\EntityFramework\Migrations\Migration;
use Yakupeyisan\CodeIgniter4\EntityFramework\Migrations\MigrationBuilder;
use Yakupeyisan\CodeIgniter4\EntityFramework\Migrations\ColumnBuilder;

class Migration_20240101120000_InitialCreate extends Migration
{
    public function up(): void
    {
        $builder = new MigrationBuilder($this->connection);

        // Companies table (foreign key bağımlılığı olmadığı için önce)
        $builder->createTable('Companies', function(ColumnBuilder $columns) {
            $columns->integer('Id')->primaryKey()->autoIncrement()->notNull();
            $columns->string('Name', 255)->notNull();
            $columns->string('Description', 255)->nullable();
            $columns->datetime('CreatedAt')->nullable();
            $columns->datetime('UpdatedAt')->nullable();
        });

        // Users table (Companies'e bağımlı)
        $builder->createTable('Users', function(ColumnBuilder $columns) {
            $columns->integer('Id')->primaryKey()->autoIncrement()->notNull();
            $columns->string('FirstName', 100)->notNull();
            $columns->string('LastName', 100)->notNull();
            $columns->integer('CompanyId')->notNull();
            $columns->datetime('CreatedAt')->nullable();
            $columns->datetime('UpdatedAt')->nullable();
            $columns->datetime('DeletedAt')->nullable();
        });

        // Index oluşturma
        $builder->createIndex('Users', 'IX_Users_CompanyId', ['CompanyId'], false);

        // Foreign key oluşturma
        $builder->addForeignKey(
            'Users',
            'FK_Users_Companies',
            ['CompanyId'],
            'Companies',
            ['Id'],
            'CASCADE'
        );

        $builder->execute();
    }

    public function down(): void
    {
        $builder = new MigrationBuilder($this->connection);

        // Rollback işlemleri (ters sırada)
        $builder->dropTable('Users');
        $builder->dropTable('Companies');

        $builder->execute();
    }
}
```

##### İkinci Migration Örneği (Mevcut Tablolara Yeni Kolon Ekleme)

[](#i̇kinci-migration-örneği-mevcut-tablolara-yeni-kolon-ekleme)

Entity'nize yeni bir property eklediğinizde:

```
// User entity'sine Email eklendi
#[Required]
#[MaxLength(255)]
public string $Email;
```

Yeni migration oluşturulduğunda:

```
// Otomatik üretilen migration
public function up(): void
{
    $builder = new MigrationBuilder($this->connection);

    // Companies tablosu zaten var, değişiklik yok

    // Users tablosuna yeni kolon ekle
    $builder->addColumn('Users', 'Email', 'VARCHAR(255)', ['null' => false]);

    $builder->execute();
}

public function down(): void
{
    $builder = new MigrationBuilder($this->connection);

    // Yeni eklenen kolonu kaldır
    $builder->dropColumn('Users', 'Email');

    $builder->execute();
}
```

##### Desteklenen Attribute'lar

[](#desteklenen-attributelar)

MigrationGenerator aşağıdaki attribute'ları destekler:

- `#[Table("TableName")]` - Tablo adı
- `#[Key]` - Primary key
- `#[DatabaseGenerated(DatabaseGenerated::IDENTITY)]` - Auto increment
- `#[Column("ColumnName", "VARCHAR(255)")]` - Kolon adı ve tipi
- `#[Required]` - NOT NULL
- `#[MaxLength(255)]` - Maksimum uzunluk
- `#[ForeignKey("NavigationProperty")]` - Foreign key ilişkisi
- `#[Index("ColumnName")]` veya `#[Index(["Col1", "Col2"], isUnique: true)]` - Index
- `#[AuditFields(createdAt: true, updatedAt: true, deletedAt: true)]` - Audit kolonları

##### İpuçları

[](#i̇puçları-1)

1. **İlk Migration**: İlk migration'ınızı oluştururken tüm entity'lerinizi ApplicationDbContext'e eklediğinizden emin olun.
2. **Yeni Entity Ekleme**: Yeni bir entity eklediğinizde, ApplicationDbContext'e ilgili DbSet metodunu ekleyin:

    ```
    public function Products()
    {
        return $this->set(Product::class);
    }
    ```
3. **Mevcut Tablolar**: MigrationGenerator mevcut tabloları kontrol eder, bu yüzden aynı tabloyu tekrar oluşturmaz.
4. **Foreign Key Bağımlılıkları**: Entity'leriniz arasındaki foreign key ilişkileri otomatik olarak tespit edilir ve doğru sırada oluşturulur.
5. **Hata Ayıklama**: Migration oluşturma sırasında hata olursa, `error_log` dosyalarını kontrol edin. MigrationGenerator detaylı log mesajları üretir.

### Fluent API Configuration

[](#fluent-api-configuration)

```
protected function onModelCreating(): void
{
    $this->entity(User::class)
        ->hasKey('Id')
        ->toTable('Users')
        ->property('Id')
            ->valueGeneratedOnAdd()
            ->entity()
        ->property('FirstName')
            ->hasMaxLength(100)
            ->isRequired()
            ->entity()
        ->hasOne('Company')
            ->hasForeignKey('CompanyId')
            ->withMany('Users')
            ->onDelete('CASCADE')
            ->entity()
        ->hasIndex('CompanyId');
}
```

### Data Annotations (Attributes)

[](#data-annotations-attributes)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Table;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Key;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\DatabaseGenerated;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Column;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Required;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\MaxLength;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\ForeignKey;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\InverseProperty;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Index;
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\AuditFields;

#[Table("Users")]
#[Index("CompanyId")]
#[AuditFields(createdAt: true, updatedAt: true)]
class User extends Entity
{
    #[Key]
    #[DatabaseGenerated(DatabaseGenerated::IDENTITY)]
    #[Column("Id", "INT")]
    public int $Id;

    #[Required]
    #[MaxLength(100)]
    #[Column("FirstName", "VARCHAR(100)")]
    public string $FirstName;

    #[ForeignKey("Company")]
    #[Column("CompanyId", "INT")]
    public int $CompanyId;

    #[InverseProperty("Users")]
    public ?Company $Company = null;
}
```

Entity Structure
----------------

[](#entity-structure)

All entities must extend the `Entity` base class:

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity;

class User extends Entity
{
    // Properties with attributes
}
```

Package Structure
-----------------

[](#package-structure)

```
src/
├── Attributes/          # Data Annotations (Attributes)
│   ├── Table.php
│   ├── Key.php
│   ├── Column.php
│   ├── ForeignKey.php
│   └── ...
├── Configuration/       # Fluent API
│   ├── EntityTypeBuilder.php
│   ├── PropertyBuilder.php
│   └── ...
├── Core/               # Core classes
│   ├── Entity.php
│   ├── DbContext.php
│   └── ...
├── Migrations/         # Migration system
│   ├── Migration.php
│   ├── MigrationBuilder.php
│   └── MigrationManager.php
├── Query/              # Query building
│   ├── IQueryable.php
│   ├── Queryable.php
│   └── AdvancedQueryBuilder.php
├── Repository/         # Repository pattern
│   ├── IRepository.php
│   ├── Repository.php
│   ├── UnitOfWork.php
│   └── Specification/
└── Support/            # Supporting classes
    ├── ValueConverter.php
    └── OwnedType.php

```

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- CodeIgniter 4.0 or higher

License
-------

[](#license)

MIT

### Specification Pattern

[](#specification-pattern)

Specification Pattern, query koşullarını yeniden kullanılabilir specification'lar olarak tanımlamanızı sağlar. Bu pattern sayesinde karmaşık query koşullarını modüler hale getirebilirsiniz.

#### Temel Kullanım

[](#temel-kullanım-3)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Repository\Specification\Specification;
use App\EntityFramework\ApplicationDbContext;

class ActiveUserSpecification extends Specification
{
    public function apply(IQueryable $query): IQueryable
    {
        return $query->where(fn($u) => $u->Status === 'Active');
    }

    public function isSatisfiedBy(object $entity): bool
    {
        return $entity->Status === 'Active';
    }
}

// Kullanım
$context = new ApplicationDbContext();
$spec = new ActiveUserSpecification();
$activeUsers = $spec->apply($context->Users())->toList();
```

#### Specification Kombinasyonları

[](#specification-kombinasyonları)

```
class CompanyUserSpecification extends Specification
{
    private int $companyId;

    public function __construct(int $companyId)
    {
        $this->companyId = $companyId;
    }

    public function apply(IQueryable $query): IQueryable
    {
        return $query->where(fn($u) => $u->CompanyId === $this->companyId);
    }

    public function isSatisfiedBy(object $entity): bool
    {
        return $entity->CompanyId === $this->companyId;
    }
}

// AND kombinasyonu
$activeCompanyUsers = (new ActiveUserSpecification())
    ->and(new CompanyUserSpecification(1))
    ->apply($context->Users())
    ->toList();

// OR kombinasyonu
$users = (new ActiveUserSpecification())
    ->or(new CompanyUserSpecification(2))
    ->apply($context->Users())
    ->toList();

// NOT kombinasyonu
$inactiveUsers = (new ActiveUserSpecification())
    ->not()
    ->apply($context->Users())
    ->toList();
```

### Value Converters

[](#value-converters)

Value Converters, entity property'leri ile veritabanı kolonları arasında değer dönüşümü yapmanızı sağlar.

#### Temel Kullanım

[](#temel-kullanım-4)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Support\ValueConverter;

// JSON string'i array'e çevir
$converter = new ValueConverter(
    // To database (entity -> database)
    fn($value) => json_encode($value),
    // From database (database -> entity)
    fn($value) => json_decode($value, true)
);

// Fluent API ile kullanım
$this->entity(User::class)
    ->property('Metadata')
    ->hasConversion($converter);
```

### Owned Types (Complex Types)

[](#owned-types-complex-types)

Owned Types, bir entity'nin başka bir entity'yi kendi parçası olarak sahip olmasını sağlar. Bu, complex type'lar için kullanılır.

#### Temel Kullanım

[](#temel-kullanım-5)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Owned;

#[Owned]
class Address
{
    public string $Street;
    public string $City;
    public string $Country;
}

class User extends Entity
{
    public Address $Address; // Owned type
}

// Fluent API ile
$this->entity(User::class)
    ->ownsOne('Address', Address::class, function($builder) {
        $builder->property('Street')->toColumn('Address_Street');
        $builder->property('City')->toColumn('Address_City');
    });
```

### Query Filters (Global Filters)

[](#query-filters-global-filters)

Query Filters, tüm query'lere otomatik olarak uygulanan global filtrelerdir. Örneğin, soft delete için tüm query'lere `DeletedAt IS NULL` koşulu eklenebilir.

#### Temel Kullanım

[](#temel-kullanım-6)

```
use App\EntityFramework\ApplicationDbContext;

class ApplicationDbContext extends DbContext
{
    protected function onModelCreating(): void
    {
        // Soft delete filter
        $this->addQueryFilter(User::class, function($query) {
            return $query->where(fn($u) => $u->DeletedAt === null);
        });

        // Multi-tenant filter
        $this->addQueryFilter(User::class, function($query) {
            $tenantId = $this->getCurrentTenantId();
            return $query->where(fn($u) => $u->TenantId === $tenantId);
        });
    }
}
```

### Concurrency Control

[](#concurrency-control)

Concurrency Control, aynı anda birden fazla kullanıcının aynı entity'yi güncellemesini önlemek için kullanılır.

#### ConcurrencyCheck Attribute

[](#concurrencycheck-attribute)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\ConcurrencyCheck;

class User extends Entity
{
    #[ConcurrencyCheck]
    public string $Email; // Concurrency token olarak kullanılır
}
```

#### Timestamp (RowVersion) Attribute

[](#timestamp-rowversion-attribute)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Timestamp;

class User extends Entity
{
    #[Timestamp]
    public ?string $RowVersion = null; // Otomatik güncellenen concurrency token
}
```

### Soft Delete

[](#soft-delete)

Soft Delete, entity'leri fiziksel olarak silmek yerine `DeletedAt` kolonunu işaretleyerek silindi olarak işaretler.

#### Temel Kullanım

[](#temel-kullanım-7)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\SoftDelete;

#[SoftDelete]
class User extends Entity
{
    public ?\DateTime $DeletedAt = null;
}

// Soft delete
$user = $context->Users()->where(fn($u) => $u->Id === 1)->firstOrDefault();
$context->remove($user); // DeletedAt otomatik olarak işaretlenir
$context->saveChanges();

// Soft delete'lenmiş kayıtları görmezden gel (otomatik)
$users = $context->Users()->toList(); // DeletedAt IS NULL olanlar

// Soft delete'lenmiş kayıtları dahil et
$allUsers = $context->Users()
    ->where(fn($u) => true) // Filter'ı bypass et
    ->toList();
```

### JSON Column

[](#json-column)

JSON Column, property'leri JSON kolonları olarak işaretlemenizi sağlar.

#### Temel Kullanım

[](#temel-kullanım-8)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\JsonColumn;

class User extends Entity
{
    #[JsonColumn]
    public array $Metadata = []; // JSON kolon olarak saklanır
}
```

### NotMapped

[](#notmapped)

NotMapped, property veya class'ları veritabanı mapping'inden hariç tutar.

#### Temel Kullanım

[](#temel-kullanım-9)

```
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\NotMapped;

class User extends Entity
{
    public int $Id;

    #[NotMapped]
    public string $FullName; // Veritabanında kolon yok, sadece computed property
}
```

Notes
-----

[](#notes)

- This system is compatible with CodeIgniter 4
- All features are designed to be 100% compatible with EF Core
- Production-ready code structure
- Both Data Annotations and Fluent API are supported

Development Status
------------------

[](#development-status)

✅ Core infrastructure completed ✅ All entities updated ✅ Query builder implementation completed ✅ Repository and Unit of Work patterns added ✅ Migration system ready ✅ Add, Update, Delete operations implemented ✅ Batch operations (Change Tracker &amp; Direct Database) implemented ✅ Advanced Expression Tree Parsing for WHERE clauses implemented ✅ Expression Tree Parsing improvements (more methods, arithmetic operations) completed ✅ Compiled Queries (Performance Optimization) implemented ✅ Lazy Loading Proxy Implementation completed ✅ Bulk Operations Optimization completed ✅ Query Plan Optimization improvements completed ✅ Additional Database Provider Support completed ✅ Transaction Management Improvements completed ✅ Advanced Query Hints and Optimizations completed ✅ Additional Database-Specific Features completed

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance82

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

8

Last Release

168d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2fc4d03f19a14def3cc8986116de2b2bff08483493d7b01db908658e1a07a1c6?d=identicon)[yakupeyisan](/maintainers/yakupeyisan)

---

Top Contributors

[![yakupeyisan](https://avatars.githubusercontent.com/u/47880402?v=4)](https://github.com/yakupeyisan "yakupeyisan (54 commits)")

---

Tags

databaseormquery buildercodeigniter4entity-framework

### Embed Badge

![Health badge](/badges/yakupeyisan-codeigniter4-entity-framework/health.svg)

```
[![Health](https://phpackages.com/badges/yakupeyisan-codeigniter4-entity-framework/health.svg)](https://phpackages.com/packages/yakupeyisan-codeigniter4-entity-framework)
```

###  Alternatives

[cycle/database

DBAL, schema introspection, migration and pagination

64690.9k31](/packages/cycle-database)[tommyknocker/pdo-database-class

Framework-agnostic PHP database library with unified API for MySQL, MariaDB, PostgreSQL, SQLite, MSSQL, and Oracle. Query Builder, caching, sharding, window functions, CTEs, JSON, migrations, ActiveRecord, CLI tools, AI-powered analysis. Zero external dependencies.

845.7k](/packages/tommyknocker-pdo-database-class)[wayofdev/laravel-cycle-orm-adapter

🔥 A Laravel adapter for CycleORM, providing seamless integration of the Cycle DataMapper ORM for advanced database handling and object mapping in PHP applications.

3516.7k3](/packages/wayofdev-laravel-cycle-orm-adapter)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
