Data Integrity and Patterns

Back

Loading concept...

Entity Framework Core: Data Integrity and Patterns

The Story of the Magical Bank Vault 🏦

Imagine you run a magical bank. Every day, wizards come to deposit gold coins, transfer money, and check their balances. But here’s the tricky part—what happens if two wizards try to grab the same gold coin at the exact same moment? Or what if the lights go out in the middle of counting coins?

That’s exactly what Entity Framework Core’s Data Integrity and Patterns helps us solve! Think of it as the rulebook for keeping your database safe, consistent, and reliable—no matter what chaos happens.

Let’s explore this magical world together!


🎭 The Cast of Characters

Pattern Real-World Analogy
Transactions A sealed envelope—either everything inside is delivered, or nothing is
Concurrency Two kids grabbing the last cookie at the same time
Resiliency A phone that reconnects to WiFi automatically
Seeding Pre-filling a notebook with example entries
Data Patterns Different ways to organize your toy collection
Scaffolding Building blocks that create a house blueprint from an existing house

1. Transactions: The All-or-Nothing Promise 📦

What’s the Big Idea?

A transaction is like putting multiple tasks in a sealed envelope. Either ALL tasks succeed together, or NONE of them happen. No half-done work allowed!

Simple Example: Transferring Money

Imagine you want to move $100 from your piggy bank to your friend’s jar:

  1. Take $100 from your piggy bank
  2. Put $100 in friend’s jar

What if step 1 works but step 2 fails? Your money vanishes! 😱

With transactions, if step 2 fails, step 1 automatically undoes itself.

using var transaction =
    await context.Database
    .BeginTransactionAsync();

try
{
    // Step 1: Remove from your account
    myAccount.Balance -= 100;

    // Step 2: Add to friend's account
    friendAccount.Balance += 100;

    await context.SaveChangesAsync();

    // Everything worked! Seal the deal.
    await transaction.CommitAsync();
}
catch
{
    // Something broke! Undo everything.
    await transaction.RollbackAsync();
}

The Three Magic Words

Word Meaning
Begin Start the sealed envelope
Commit Seal and send it
Rollback Tear it up, pretend nothing happened

2. Concurrency Handling: Who Gets the Last Cookie? 🍪

What’s the Big Idea?

Concurrency happens when two people try to change the same thing at the same time. It’s like two kids reaching for the last cookie!

EF Core has a smart solution: version stamps. Every row gets a secret number that changes whenever someone edits it.

How It Works

graph TD A["🧑 User A reads product&lt;br/&gt;Price: $10, Version: 1"] --> B["🧑 User B reads same product&lt;br/&gt;Price: $10, Version: 1"] B --> C["User A changes price to $15&lt;br/&gt;Saves with Version 1"] C --> D["Database updates&lt;br/&gt;Version becomes 2"] D --> E["User B tries to save $12&lt;br/&gt;Still thinks Version is 1"] E --> F[❌ CONFLICT!<br/>Versions don't match]

The Code

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    // This magical timestamp catches conflicts!
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

When a conflict happens:

try
{
    await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    // Someone else changed it first!
    // Decide: keep theirs, keep ours, or merge?
}

Resolution Strategies

Strategy When to Use
Client Wins User’s changes always override
Database Wins Keep what’s already saved
Custom Merge Combine both changes smartly

3. Connection Resiliency: The Bounce-Back Power 🔄

What’s the Big Idea?

Sometimes the internet hiccups. Servers get busy. Connections drop for a split second. Resiliency means your app doesn’t crash—it automatically tries again!

Simple Analogy

Imagine calling your friend but the call drops. Instead of giving up, your phone redials automatically. That’s resiliency!

The Code

services.AddDbContext<MyContext>(options =>
    options.UseSqlServer(
        connectionString,
        sqlOptions => sqlOptions
            .EnableRetryOnFailure(
                maxRetryCount: 5,
                maxRetryDelay: TimeSpan
                    .FromSeconds(30),
                errorNumbersToAdd: null)
    )
);

What Gets Retried?

Retried Automatically NOT Retried
Network timeouts Wrong password
Server too busy Invalid SQL
Connection dropped Constraint violations

Custom Retry Strategy

options.UseSqlServer(
    connectionString,
    sqlOptions => sqlOptions
        .ExecutionStrategy(
            context => new MyRetryStrategy(
                context,
                maxRetryCount: 3,
                retryDelay: TimeSpan
                    .FromMilliseconds(500)
            )
        )
);

4. Database Seeding: Pre-Filling the Notebook 📝

What’s the Big Idea?

When you get a new notebook, sometimes it helps to have some example entries already written. Seeding fills your database with starter data!

Two Types of Seeds

Type When It Runs Best For
HasData During migrations Fixed data that rarely changes
Custom Seeding At app startup Dynamic or environment-specific data

Method 1: HasData (The Migration Way)

protected override void OnModelCreating(
    ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Category>()
        .HasData(
            new Category {
                Id = 1,
                Name = "Electronics"
            },
            new Category {
                Id = 2,
                Name = "Books"
            },
            new Category {
                Id = 3,
                Name = "Clothing"
            }
        );
}

Method 2: Custom Seeding (The Startup Way)

public static async Task SeedDatabase(
    MyContext context)
{
    // Only add if empty
    if (!context.Users.Any())
    {
        context.Users.AddRange(
            new User {
                Name = "Admin",
                Role = "Administrator"
            },
            new User {
                Name = "Guest",
                Role = "Viewer"
            }
        );
        await context.SaveChangesAsync();
    }
}

Pro Tips

  • Always check if data exists before seeding
  • Use fixed IDs for HasData (required!)
  • Keep seeding logic separate from business logic

5. Data Access Patterns: Organizing Your Toys 🧸

What’s the Big Idea?

There are many ways to organize toys—by color, by size, by type. Similarly, there are different patterns for organizing how your code talks to the database!

The Main Patterns

graph TD A["Your App"] --> B{Which Pattern?} B --> C["Repository Pattern&lt;br/&gt;🗄️ One box per toy type"] B --> D["Unit of Work&lt;br/&gt;📦 Track all changes together"] B --> E["CQRS&lt;br/&gt;📖 Read and Write are separate"] B --> F["Direct DbContext&lt;br/&gt;🎯 Simple and direct"]

Pattern 1: Repository Pattern

Think of it as having a separate toy box for each type of toy.

public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task<IEnumerable<Product>> GetAllAsync();
    Task AddAsync(Product product);
    Task UpdateAsync(Product product);
    Task DeleteAsync(int id);
}

public class ProductRepository
    : IProductRepository
{
    private readonly MyContext _context;

    public ProductRepository(MyContext context)
    {
        _context = context;
    }

    public async Task<Product> GetByIdAsync(
        int id)
    {
        return await _context.Products
            .FindAsync(id);
    }
}

Pattern 2: Unit of Work

Think of it as a shopping cart—add many items, then checkout all at once.

public interface IUnitOfWork
{
    IProductRepository Products { get; }
    IOrderRepository Orders { get; }
    Task<int> SaveChangesAsync();
}

Pattern 3: CQRS (Command Query Separation)

Reading and Writing use different paths—like having separate doors for entering and exiting.

// QUERY: Just reading, optimized for speed
public class GetProductsQuery
{
    public async Task<List<ProductDto>> Execute()
    {
        return await _context.Products
            .AsNoTracking()  // Faster reads!
            .Select(p => new ProductDto {...})
            .ToListAsync();
    }
}

// COMMAND: Making changes
public class CreateProductCommand
{
    public async Task Execute(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
    }
}

Which Pattern to Choose?

Scenario Recommended Pattern
Small app, few entities Direct DbContext
Medium app, need testing Repository
Large app, complex logic Repository + Unit of Work
High-traffic reads/writes CQRS

6. Scaffolding: Building Blueprints from Reality 🏗️

What’s the Big Idea?

Imagine you see a beautiful house and want to build one just like it. Scaffolding creates the blueprints (code) by looking at an existing building (database)!

This is called Database-First approach.

The Magic Command

dotnet ef dbcontext scaffold \
    "Server=.;Database=Store;..." \
    Microsoft.EntityFrameworkCore.SqlServer \
    --output-dir Models \
    --context StoreContext

What Gets Generated?

graph LR A["🗄️ Existing Database"] --> B["Scaffold Command"] B --> C["📄 Entity Classes"] B --> D["📄 DbContext"] B --> E["📄 Configuration"]

Example: Generated Product Class

// Auto-generated from database!
public partial class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal? Price { get; set; }
    public int? CategoryId { get; set; }

    public virtual Category Category
        { get; set; }
}

Scaffolding Options

Option What It Does
--output-dir Where to put the files
--context Name your DbContext
--tables Only scaffold specific tables
--force Overwrite existing files
--no-pluralize Keep table names as-is

Updating After Database Changes

# Re-scaffold with --force
dotnet ef dbcontext scaffold \
    "ConnectionString" \
    Provider \
    --force

Pro Tip: Partial Classes

Keep your custom code safe from re-scaffolding:

// Generated file: Product.cs
public partial class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// Your custom file: Product.Custom.cs
public partial class Product
{
    // Your additions here!
    public string DisplayName =>
        quot;{Name} (ID: {Id})";
}

🎯 Quick Reference Table

Concept One-Line Summary
Transactions All succeed or all fail together
Concurrency Detect when two users edit simultaneously
Resiliency Auto-retry on connection failures
Seeding Pre-populate database with starter data
Data Patterns Organized ways to structure data access
Scaffolding Generate code from existing database

🚀 You Did It!

You’ve just learned how Entity Framework Core keeps your data safe, consistent, and reliable! These patterns are like the guardrails on a mountain road—they keep everything from going off the cliff.

Remember:

  • Transactions = Sealed envelopes (all or nothing)
  • Concurrency = Cookie grabbing rules
  • Resiliency = Auto-redial on dropped calls
  • Seeding = Pre-filled notebooks
  • Patterns = Toy organization systems
  • Scaffolding = Building blueprints from existing houses

Now go build something amazing! 🌟

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.