⌨️ Dev Notebook

36+ years of ASP.NET, SQL Server, and MS Dynamics. Real code, real war stories, no fluff.

ALL
C#
ASP.NET
SQL
T-SQL
DYNAMICS
PERFORMANCE
GIT
WMS
ASP.NET Core
C# / .NET 8
SQL Server
MS Dynamics 365
WMS Support
find-slow-queries.sql — top CPU/IO hogs in SQL Server
T-SQLPERFORMANCE
Query the plan cache to find worst-performing queries by CPU, elapsed time, or logical reads. Runs on any SQL Server 2012+.
-- Top 20 queries by total CPU time
SELECT TOP 20
    SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset
            WHEN -1 THEN DATALENGTH(qt.text)
            ELSE qs.statement_end_offset END
        - qs.statement_start_offset)/2)+1)           AS query_text,
    qs.execution_count,
    qs.total_worker_time / 1000                     AS total_cpu_ms,
    qs.total_worker_time / qs.execution_count / 1000  AS avg_cpu_ms,
    qs.total_elapsed_time / 1000                    AS total_elapsed_ms,
    qs.total_logical_reads,
    qp.query_plan
FROM   sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle)  qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY qs.total_worker_time DESC;
index-fragmentation.sql — check and fix index health
T-SQLPERFORMANCE
Check index fragmentation and generate the correct maintenance command. Reorganize under 30%, rebuild above.
SELECT
    OBJECT_NAME(i.object_id)          AS table_name,
    i.name                            AS index_name,
    s.avg_fragmentation_in_percent,
    s.page_count,
    CASE
        WHEN s.avg_fragmentation_in_percent > 30 THEN 'REBUILD'
        WHEN s.avg_fragmentation_in_percent > 10 THEN 'REORGANIZE'
        ELSE 'OK'
    END                               AS recommended_action
FROM   sys.dm_db_index_physical_stats(
           DB_ID(), NULL, NULL, NULL, 'LIMITED') s
JOIN   sys.indexes i
       ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE  s.page_count > 100
  AND  i.name IS NOT NULL
ORDER BY s.avg_fragmentation_in_percent DESC;
OrderRepository.cs — async repository pattern with Dapper
C#ASP.NET
Clean async service + repository pattern using Dapper for SQL Server. Lightweight, testable, and doesn't fight the ORM.
public interface IOrderRepository
{
    Task<IEnumerable<Order>> GetPendingOrdersAsync(CancellationToken ct);
    Task<Order?> GetByIdAsync(int orderId, CancellationToken ct);
}

public class OrderRepository(IConfiguration cfg) : IOrderRepository
{
    private readonly string _conn = cfg.GetConnectionString("WmsDb")!;

    public async Task<IEnumerable<Order>> GetPendingOrdersAsync(CancellationToken ct)
    {
        await using var db = new SqlConnection(_conn);
        return await db.QueryAsync<Order>(new CommandDefinition(
            """
            SELECT OrderId, CustomerId, Status, CreatedAt
            FROM   dbo.Orders
            WHERE  Status = 'PENDING'
            ORDER BY CreatedAt ASC
            """, cancellationToken: ct));
    }

    public async Task<Order?> GetByIdAsync(int orderId, CancellationToken ct)
    {
        await using var db = new SqlConnection(_conn);
        return await db.QueryFirstOrDefaultAsync<Order>(new CommandDefinition(
            "SELECT * FROM dbo.Orders WHERE OrderId = @OrderId",
            new { OrderId = orderId }, cancellationToken: ct));
    }
}
GlobalExceptionMiddleware.cs — centralized error handling
C#ASP.NET
Global exception middleware for ASP.NET Core APIs. Catches unhandled exceptions, logs them, and returns a consistent JSON error response.
public class GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext ctx)
    {
        try
        {
            await next(ctx);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unhandled exception on {Method} {Path}",
                ctx.Request.Method, ctx.Request.Path);

            ctx.Response.StatusCode  = StatusCodes.Status500InternalServerError;
            ctx.Response.ContentType = "application/json";

            await ctx.Response.WriteAsJsonAsync(new {
                status  = 500,
                title   = "An unexpected error occurred",
                traceId = Activity.Current?.Id ?? ctx.TraceIdentifier
            });
        }
    }
}

// Register in Program.cs — must come first
app.UseMiddleware<GlobalExceptionMiddleware>();
deadlock-monitor.sql — catch deadlocks from system_health XE
T-SQLPERFORMANCE
Extract deadlock graphs from SQL Server's always-on system_health Extended Events session. No trace flags needed.
WITH DeadlockData AS (
    SELECT
        xdr.value('(//deadlock/process-list/process/@lastbatchstarted)[1]', 'datetime2')
            AS deadlock_time,
        xdr.value('(//deadlock/process-list/process/@hostname)[1]', 'nvarchar(256)')
            AS hostname,
        xdr.query('.')  AS deadlock_xml
    FROM (
        SELECT CAST(target_data AS xml) AS target_data
        FROM   sys.dm_xe_session_targets  t
        JOIN   sys.dm_xe_sessions         s ON t.event_session_address = s.address
        WHERE  s.name = 'system_health'
          AND  t.target_name = 'ring_buffer'
    ) AS src
    CROSS APPLY target_data.nodes('//RingBufferTarget/event[@name="xml_deadlock_report"]') AS n(xdr)
)
SELECT * FROM DeadlockData
ORDER BY deadlock_time DESC;
PluginBase.cs — MS Dynamics 365 plugin base class
DYNAMICSC#
Boilerplate base class for Dynamics 365 plugins — wraps the service provider setup so you don't repeat it every time.
public abstract class PluginBase : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)
            serviceProvider.GetService(typeof(IPluginExecutionContext));
        var factory  = (IOrganizationServiceFactory)
            serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        var service  = factory.CreateOrganizationService(context.UserId);
        var tracer   = (ITracingService)
            serviceProvider.GetService(typeof(ITracingService));
        try
        {
            ExecutePlugin(context, service, tracer);
        }
        catch (InvalidPluginExecutionException) { throw; }
        catch (Exception ex)
        {
            tracer.Trace($"Plugin error: {ex.Message}");
            throw new InvalidPluginExecutionException($"Unexpected: {ex.Message}", ex);
        }
    }

    protected abstract void ExecutePlugin(
        IPluginExecutionContext context,
        IOrganizationService    service,
        ITracingService         tracer);
}
git-rescue.sh — commands you need when things go sideways
GIT
The git commands I reach for constantly — not the basics, the ones that save you when things go wrong.
# Undo last commit but keep changes staged
git reset --soft HEAD~1

# Undo last commit AND unstage (still keep files)
git reset --mixed HEAD~1

# Completely discard last commit (DESTRUCTIVE)
git reset --hard HEAD~1

# Stash with a descriptive label
git stash push -m "WIP: order processor refactor"

# Cherry-pick a single commit from another branch
git cherry-pick abc1234

# Binary search for the commit that introduced a bug
git bisect start
git bisect bad          # current is broken
git bisect good v2.1.0  # last known good tag

# Rewrite last 3 commit messages interactively
git rebase -i HEAD~3
🔥
Always use SET NOCOUNT ON in stored procedures
Without it, SQL Server sends a row count message for every DML statement, which can confuse ORMs and adds needless network chatter. Put it at the top of every stored proc — it's free performance.
⚠️
Never use SELECT * in production queries
It prevents index-only scans, breaks when columns are added/reordered, and pulls unnecessary data. Always name your columns. In a WMS with wide tables (50+ columns), this is the difference between a 2ms and a 200ms query.
💡
Use IAsyncEnumerable for streaming large result sets
Instead of loading 100k rows into a List<T>, return IAsyncEnumerable<T>. ASP.NET Core will stream JSON as rows arrive. Dapper supports this via QueryUnbufferedAsync.
🔵
Use WITH (NOLOCK) carefully — it's not free
NOLOCK lets reads bypass locking but you can get dirty reads. Use it on reporting queries, never on anything that makes a business decision. The real answer is READ_COMMITTED_SNAPSHOT at the database level.
🏗️
In Dynamics plugins, always check InputParameters.ContainsKey
Plugins fire on multiple message types and the target entity may not always have the attribute you expect. Check context.InputParameters.ContainsKey("Target") defensively. Uncaught cast exceptions in a sync plugin break the user's form with a cryptic error.
⚠️
Avoid async void — it's an exception black hole
async void methods (other than event handlers) swallow exceptions silently and can crash your app in unpredictable ways. Always use async Task.
📐
Covering indexes beat query rewrites most of the time
Before rewriting a slow query, check if a covering index can solve it. Add filter columns as the key, INCLUDE the selected columns. SQL Server satisfies the whole query from the index without touching the base table.
🔵
Pass CancellationToken everywhere in ASP.NET Core
Forward the token all the way to your DB calls. When a client disconnects, the token fires and aborts the DB query. On a WMS with long-running batch queries this prevents thread pile-ups under load.