Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
Bulk Import with a Validation Report: Upload Excel/CSV, Staging Table, Row-Level Error File
The safe way to bring in a large, dirty Excel/CSV file is to load it into a text-column staging table with SqlBulkCopy, apply the rules to every row with set-based SQL, give the user a row-level error file and make the commit a separate, confirmed step. This post walks through a 20,000-row import with real output, including 202 Accepted plus a status endpoint, idempotency via a file hash and escaping against CSV injection.
Pagination Strategies in Depth: Offset, Keyset and Opaque Cursor Tokens
On large, changing tables keyset pagination removes offset's depth-dependent cost and page drift: in a 2-million-row test, page 5000 read 375 logical pages with offset and 6 with keyset. This post shows, with real measurements, the tie-breaker keyset condition on SQL Server, an HMAC-signed opaque cursor, the previous page and the nullable sort column trap.
Optimistic Concurrency End to End: From rowversion to ETag, If-Match, 412 and a Conflict UI
Building optimistic concurrency end to end on the web means exposing rowversion as an ETag, requiring If-Match on updates (428 otherwise), returning 412 with the current values on a mismatch and doing a field-level three-way merge on the client. As a follow-up to the EF Core Concurrency post, this post shows the HTTP and Angular side with real output.
Idempotency Keys: Not Creating the Same Order Twice on Double Clicks and Retries
The reliable way to stop the same order from being created twice on double clicks and retries is for the server to store a client-generated Idempotency-Key per user together with a request hash, and to write the work and the 'completed' record in the same transaction. This post covers the current status of the IETF draft (draft-08, not an RFC), the 400/409/422 rules and an ASP.NET Core endpoint filter + SQL Server implementation with real output.
Time and Time Zones: UTC Instants, Local Business Dates, DateOnly and TimeProvider
Storing time correctly means separating three concepts: event moments are UTC instants (DateTimeOffset), calendar days are DateOnly, and future local events are wall-clock time plus an IANA zone id. This post shows, with real output, how to turn a tenant's day into a UTC range, a testable clock with TimeProvider, the AT TIME ZONE traps in SQL Server and the date shift in Angular.
Money and Rounding: decimal, a Money Value Object, VAT and Distributing Cents
Modeling money correctly takes a Money value object that carries the amount with its currency, the same explicit rounding rule in C# and SQL, and Allocate instead of division so no cent is lost. The post shows Math.Round and SQL ROUND disagreeing on 0.125, the 1-cent difference between per-line and total VAT, an effective-dated VAT rate table (20%, 10%, 1% as of November 2026), EF Core complex type mapping and string amounts in JSON, with real output.
Numbering: Invoice and Order Numbers, Gapless Sequences and Yearly Resets
An invoice number that must be consecutive shouldn't come from an identity, MAX + 1 or a SEQUENCE, but from a locked UPDATE on a counter table keyed by (Series, Year) inside the approval transaction; for order numbers where gaps are acceptable, a SEQUENCE is enough. The post shows GİB's 16-character document number format, gapless numbering under 50 concurrent approvals, the effect of lock duration on throughput and yearly resets by invoice date, with real output.
Designing Feature Flags in Code: Toggle Types, Microsoft.FeatureManagement and Flag Debt
Designing a feature flag well means identifying its type first (release, experiment, ops, permission), keeping the decision in a single toggle router and giving every flag an owner and an expiration date. The post shows a tenant filter with Microsoft.FeatureManagement 4.8.0, in-request consistency with snapshots, a kill switch with WithFeatureGate and a test that catches expired flags, with real output.
Settings Tables: A Global → Tenant → User Override Hierarchy
For settings that change at runtime and are overridden per tenant and per user, the most robust approach is a definition per setting (type, default, range and lowest override level) plus typed key-value rows tied to it, with the precedence rule in one service and validated, audited writes. The post compares this with EAV, typed columns, a JSON document and an external store, and shows the hidden IS NOT NULL filter EF Core adds to unique indexes on nullable columns.
Data Isolation in Multi-Tenancy: Database per Tenant, Shared Schema and Row-Level Security
For most SaaS products the best balance is shared tables with a TenantId column, a named tenant query filter in EF Core and, behind it, SQL Server row-level security fed by SESSION_CONTEXT. The post compares database per tenant, schema per tenant and shared tables, and shows tenant resolution, background jobs and automated isolation tests with real output.
Multilingual Data: Storing Names in Turkish and English — Translation Tables, JSON Columns and Fallback
For multilingual data managed by users, the most flexible option is a translation table keyed by (EntityId, Culture, Name) plus a fallback chain such as en-GB → en → tr built in one place. The post compares one column per language, translation tables, SQL Server 2025 json columns and a separate translation service, and shows the fallback with EF Core 10 LeftJoin and collation choice for the Turkish I with real output.
Polymorphic Associations: Attaching Comments and Files to Multiple Tables
Linking attachments to several tables with an EntityType/EntityId pair loses FKs, cascade delete and safe authorization; when owner types are few and stable, use an exclusive arc with one nullable FK per owner and a check constraint requiring exactly one. Derive authorization from the owner's policy, not from the attachment itself.