yakupeyisan/codeigniter4-entity-framework
最新稳定版本:1.3.3
Composer 安装命令:
composer require yakupeyisan/codeigniter4-entity-framework
包简介
Entity Framework Core for CodeIgniter 4 - A comprehensive ORM solution inspired by .NET Entity Framework Core
README 文档
README
Entity Framework Core for CodeIgniter 4 - A comprehensive ORM solution inspired by .NET Entity Framework Core.
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 for details.
Installation
composer require yakupeyisan/codeigniter4-entity-framework
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
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
✅ Completed Features
-
Core ORM Features
- Code First approach
- Database First compatibility
- Fluent API support
- Data Annotations (Attributes) support
-
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
-
Key Management
- Primary Key
- Composite Key
- Foreign Key
- Concurrency Tokens
-
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
- Lazy Loading (Proxies) - Otomatik navigation property yükleme
-
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)
- 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 (===, ==, !==, !=, <, >, <=, >=)
- 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
-
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
-
Transaction and Concurrency
- BeginTransaction / Commit / Rollback
- Optimistic Concurrency
- RowVersion / Timestamp
-
Advanced Features
- Value Converters
- Owned Types (Complex Types)
- Query Filters (Global Filters)
- Change Tracking
-
Repository Pattern
- Generic Repository
- Unit of Work
- Specification Pattern
-
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
-
Audit and Soft Delete
- Audit fields (CreatedAt, UpdatedAt, DeletedAt)
- Soft Delete pattern
Usage Examples
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
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
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
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
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(); // Özel JOIN koşulu ile reference navigation (varsayılan foreign key yerine) // Örnek: AccessEvent.DeviceSerial = Terminals.SerialNumber $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();
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:
// 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
'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
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)
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:
joinConditionsağlanmazsa, sistem varsayılan foreign key ilişkisini kullanır{alias}parent entity'nin alias'ını (örn:AccessEventiçinu2){relatedAlias}related entity'nin alias'ını (örn:Terminalsiçint) temsil eder- JOIN condition SQL Server syntax'ına uygun olmalıdır
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
- WHERE Clause: WHERE clause SQL Server syntax'ına uygun olmalıdır
- Placeholder:
{alias}ve{relatedAlias}placeholder'ları otomatik olarak doğru alias'larla değiştirilir - JOIN Type: Collection navigation'lar için LEFT veya INNER JOIN seçilebilir
- Custom JOIN Condition:
joinConditionparametresi ile varsayılan foreign key ilişkisi yerine özel JOIN koşulları kullanabilirsiniz - Performance: WHERE clause ile filtreleme yaparak gereksiz veri yüklemeyi önleyebilirsiniz
- 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)
- Geriye Dönük Uyumluluk:
joinConditionparametresi opsiyoneldir, sağlanmazsa varsayılan foreign key ilişkisi kullanılır
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
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 <= 65) ->toList(); // Eşitsizlik $users = $context->Users() ->where(fn($u) => $u->Status !== 'Inactive') ->toList();
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ü $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
// 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ı
// 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ı
// 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() <= 17) ->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ı
// 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() <= 99) ->toList();
Aritmetik İş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 <= 200) ->toList();
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
// 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 ü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ı
- Expression Tree Parsing otomatik olarak SQL'e çevrilir
Lambda Expression'larda 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:
-
SQL Server Uyumluluğu: Sistem SQL Server ile uyumlu olması için
?placeholder'larını kullanır. Named parameter'lar (:param_0gibi) desteklenmez. -
Değişken Değerleri: Closure'ın
useclause'undaki değişkenlerin değerleri otomatik olarak yakalanır ve SQL sorgusuna güvenli bir şekilde eklenir. -
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.
-
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
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->propertygibi 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
Karşılaştırma Operatörleri:
| Operatör | SQL Karşılığı | Örnek |
|---|---|---|
===, == |
= |
$u->Id === 1 |
!==, != |
!= |
$u->Status !== 'Deleted' |
< |
< |
$u->Age < 18 |
> |
> |
$u->Age > 65 |
<= |
<= |
$u->Age <= 18 |
>= |
>= |
$u->Age >= 18 |
Mantıksal Operatörler:
| Operatör | SQL Karşılığı | Örnek |
|---|---|---|
&&, and |
AND |
$u->A && $u->B |
||, or |
OR |
$u->A || $u->B |
! |
NOT |
!($u->IsDeleted) |
Null Operatörleri:
| Operatör | SQL Karşılığı | Örnek |
|---|---|---|
=== null |
IS NULL |
$u->DeletedAt === null |
!== null |
IS NOT NULL |
$u->Email !== null |
Aritmetik Operatörler:
| Operatör | SQL 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ı:
| Metod | SQL 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ı:
| Metod | SQL 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ı:
| Metod | SQL 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ör | SQL Karşılığı | Örnek |
|---|---|---|
in_array() |
IN (...) |
in_array($u->Id, [1,2,3]) |
İpuçları
- Parantez Kullanımı: Karmaşık expression'larda parantez kullanarak öncelik sırasını belirleyin
- Aritmetik Öncelik: Çarpma, bölme ve modulo işlemleri toplama ve çıkarmadan önce yapılır
- Null Kontrolleri: Null değerler için
=== nullveya!== nullkullanın - String Arama: Büyük/küçük harf duyarlılığı veritabanı ayarlarına bağlıdır
- Method Chaining: Method'ları birleştirerek karmaşık ifadeler oluşturabilirsiniz
- Performance: Navigation property filtrelemeleri JOIN gerektirir, performansı etkileyebilir
- Index Kullanımı: Sık kullanılan filtreleme alanları için index oluşturun
- Date/Time Methods: Date/Time method'ları sadece DateTime alanlarında kullanılabilir
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 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 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
// 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 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
- Alias Kullanımı: Raw join'de verdiğiniz alias ile kolonlara erişebilirsiniz (format:
alias_ColumnName) - Join Condition: Join koşulunda ana tabloya
mainalias'ı ile veya tablo adı ile referans verebilirsiniz - Join Type: 'LEFT', 'INNER', 'RIGHT', 'FULL' join tiplerini kullanabilirsiniz
- Parametreler: Raw SQL'de parametreler kullanıyorsanız,
joinRawmetodunun son parametresine dizi olarak geçebilirsiniz - SQL Injection: Parametreli sorgular kullanarak SQL injection'dan korunun
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
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
- maskChar: Maskeleme için kullanılacak karakter (varsayılan:
'*') - visibleStart: Baştan gösterilecek karakter sayısı (varsayılan:
0)- Örnek:
visibleStart: 2-> "John" -> "Jo**"
- Örnek:
- visibleEnd: Sondan gösterilecek karakter sayısı (varsayılan:
4)- Örnek:
visibleEnd: 2-> "John" -> "**hn"
- Örnek:
- customMask: Özel SQL ifadesi (diğer seçenekleri geçersiz kılar)
Örnekler:
visibleStart: 2, visibleEnd: 0-> "John" -> "Jo**", "Fatma" -> "Fa***"visibleStart: 0, visibleEnd: 4-> "John" -> "****", "1234-5678-9012-3456" -> "************3456"visibleStart: 2, visibleEnd: 2-> "John" -> "John", "Fatma" -> "Fama"
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() ç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
// 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
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
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
Maskeleme tüm desteklenen veritabanlarında çalışır:
- SQL Server:
LEFT,REPLICATE,RIGHTfonksiyonları - MySQL:
SUBSTRING,REPEAT,CONCATfonksiyonları - PostgreSQL:
SUBSTRING,REPEAT,CONCATfonksiyonları - SQLite:
substr,zeroblob,replacefonksiyonları
Güvenlik Notları
- Varsayılan Davranış: Varsayılan olarak hassas veriler maskelenmiş gelir
- Açık İzin:
disableSensitive()açıkça çağrılmadığı sürece maskeleme aktif kalır - SQL Seviyesi: Maskeleme SQL sorgusu seviyesinde yapılır, uygulama katmanında değil
- Performans: Maskeleme SQL'de yapıldığı için ek performans maliyeti minimaldir
- Audit Log: Maskelenmemiş verilere erişim için
disableSensitive()kullanımını loglayın
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
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
Tekil İş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)
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)
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
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
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
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öntem | Neden |
|---|---|---|
| 1-10 entity işlemi | Change Tracker | Entity state ve audit field'ları için |
| 10-100 entity işlemi | Change Tracker veya Batch | İhtiyaca göre |
| 100+ entity işlemi | Batch Operations | Performans için |
| Audit field'ları önemli | Change Tracker | Otomatik güncelleme |
| Sadece hız önemli | Batch Operations | En hızlı yöntem |
| Transaction gerekiyor | Her 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:
- Küçük işlemler (< 50 entity): Change Tracker kullanın
- Orta işlemler (50-200 entity): İhtiyaca göre seçin
- Büyük işlemler (> 200 entity): Batch operations kullanın
- Audit gereksinimi varsa: Change Tracker kullanın
- Sadece hız önemliyse: Batch operations kullanın
- Her zaman transaction kullanın: Veri bütünlüğü için
Bulk Operations Optimization
Bulk operations artık optimize edilmiş algoritmalar kullanıyor:
Optimizasyonlar:
- Chunking: Büyük veri setleri otomatik olarak chunk'lara bölünür (default: 1000)
- Transaction Batching: Çoklu chunk'lar transaction içinde çalışır
- CASE WHEN Updates: MySQL/PostgreSQL için optimize edilmiş batch update
- MERGE Statements: SQL Server için optimize edilmiş batch update
- 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 ö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
- ✅ 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
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
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
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ı
// 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
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ı?
✅ 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
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
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
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
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
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
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
- Index Hints Dikkatli Kullanın: Index hints, query optimizer'ın kararlarını override eder. Sadece gerektiğinde kullanın.
- Lock Hints: SQL Server'da NOLOCK kullanırken dirty reads olabileceğini unutmayın.
- Timeout Ayarlayın: Uzun süren query'ler için timeout ayarlayın.
- Query Cache: Sık değişen data için noCache() kullanın.
- Optimizer Hints: Database-specific optimizer hints kullanırken dikkatli olun.
Ö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 ö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
- ✅ 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
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 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
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
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
- Full-Text Search: Index'leri oluşturmayı unutmayın
- JSON Functions: JSON kolonlarını index'leyin
- Window Functions: Partition ve order by kullanırken index'leri optimize edin
- Array Functions: PostgreSQL array functions için GIN index kullanın
- Database-Specific: Her veritabanı için özel optimizasyonları kullanın
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
- ✅ 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
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
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
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ı
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
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
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
- Düzenli Analiz: Yavaş query'leri düzenli olarak analiz edin
- Index Kullanımı: Index önerilerini uygulayın
- Query Optimizasyonu: Uyarıları dikkate alın ve query'leri optimize edin
- AsNoTracking: Change tracking gerekmeyen query'lerde
asNoTracking()kullanın - Select Specific Columns:
SELECT *yerine sadece ihtiyaç duyulan kolonları seçin - Avoid Functions in WHERE: WHERE clause'da function kullanmayın
- Index for JOINs: JOIN kolonlarına index ekleyin
Ö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 ö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
- ✅ 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
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
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)
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)
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
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
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ü
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:
// 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ı
⚠️ 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
- Küçük Veri Setleri: Lazy loading kullanın
- Büyük Veri Setleri: Eager loading (Include) kullanın
- N+1 Problemi: Döngülerde Include kullanın
- Performans Kritik: Lazy loading'i kapatın ve sadece Include kullanın
- Basit Senaryolar: Lazy loading kullanın (daha kolay kod)
Lazy Loading'i 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 ö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ı
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 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)
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)
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
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ünREAD COMMITTED- Default (çoğu database), dirty reads önlenirREPEATABLE READ- Phantom reads mümkünSERIALIZABLE- En yüksek izolasyon, tüm anomalies önlenir
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
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
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ü
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
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
- Her zaman try-catch kullanın: Exception durumunda rollback yapın
- Transaction Scope kullanın: Otomatik yönetim için
- Nested transactions dikkatli kullanın: Performans etkisi olabilir
- Isolation level seçimi: İhtiyaca göre uygun isolation level seçin
- Transaction timeout: Uzun süren işlemler için timeout ayarlayın
- Statistics monitoring: Transaction istatistiklerini düzenli kontrol edin
Örnek Senaryolar
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
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)
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
- ✅ 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
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?
- Entity Keşfi: ApplicationDbContext'teki public metodları (Users, Companies, vb.) analiz eder
- Reflection Analizi: Her entity için Reflection kullanarak attribute'ları ve property'leri inceler
- Ş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
- Mevcut Şema Kontrolü: Veritabanındaki mevcut tabloları kontrol eder
- Akıllı Migration:
- Yeni tablolar için
createTablekullanır - Mevcut tablolar için sadece yeni kolonlar, indexler ve foreign key'ler ekler
- Yeni tablolar için
- 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
// 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)
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
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ı
-
İlk Migration: İlk migration'ınızı oluştururken tüm entity'lerinizi ApplicationDbContext'e eklediğinizden emin olun.
-
Yeni Entity Ekleme: Yeni bir entity eklediğinizde, ApplicationDbContext'e ilgili DbSet metodunu ekleyin:
public function Products() { return $this->set(Product::class); }
-
Mevcut Tablolar: MigrationGenerator mevcut tabloları kontrol eder, bu yüzden aynı tabloyu tekrar oluşturmaz.
-
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.
-
Hata Ayıklama: Migration oluşturma sırasında hata olursa,
error_logdosyalarını kontrol edin. MigrationGenerator detaylı log mesajları üretir.
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)
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
All entities must extend the Entity base class:
use Yakupeyisan\CodeIgniter4\EntityFramework\Core\Entity; class User extends Entity { // Properties with attributes }
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
- PHP 8.1 or higher
- CodeIgniter 4.0 or higher
License
MIT
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
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ı
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, entity property'leri ile veritabanı kolonları arasında değer dönüşümü yapmanızı sağlar.
Temel Kullanım
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, 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
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, 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
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, aynı anda birden fazla kullanıcının aynı entity'yi güncellemesini önlemek için kullanılır.
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
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\Timestamp; class User extends Entity { #[Timestamp] public ?string $RowVersion = null; // Otomatik güncellenen concurrency token }
Soft Delete
Soft Delete, entity'leri fiziksel olarak silmek yerine DeletedAt kolonunu işaretleyerek silindi olarak işaretler.
Temel Kullanım
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, property'leri JSON kolonları olarak işaretlemenizi sağlar.
Temel Kullanım
use Yakupeyisan\CodeIgniter4\EntityFramework\Attributes\JsonColumn; class User extends Entity { #[JsonColumn] public array $Metadata = []; // JSON kolon olarak saklanır }
NotMapped
NotMapped, property veya class'ları veritabanı mapping'inden hariç tutar.
Temel Kullanım
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
- 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
✅ 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 & 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
统计信息
- 总下载量: 10
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-11-28