Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
158752 stories
·
33 followers

Governance Is a Developer Experience Problem

1 Share

This is the third post of a 3-part series by Docker Captain Karan Verma. Catch up on Part 1: Your Laptop Is the New Production Environment and Part 2: Runtime Enforcement, Not Runtime Advice.

The conversation around AI governance often starts with security. That’s understandable. When autonomous systems can execute commands, access tools, and interact with production-adjacent environments, organizations naturally focus on risk. But after spending time thinking about agent workflows, I’ve become convinced that governance is about more than security. It’s also a developer experience problem.

The Trust Bottleneck

Most organizations don’t struggle to adopt new tools because the tools are incapable. They struggle because the organization doesn’t trust them yet. The history of software development is full of examples. Cloud adoption accelerated when organizations became comfortable with cloud governance. Containers accelerated when teams gained confidence in isolation and operational controls. CI/CD accelerated when organizations trusted automated deployment pipelines. The pattern repeats. Capability arrives first. Trust arrives later. Adoption follows trust. AI agents are no different.

image1 2

Caption: Capability alone does not drive adoption. Trust enables organizations to delegate work, expand usage, and realize productivity gains.

The Wrong Tradeoff

Governance is often framed as a choice between speed and control. Move fast and accept risk. Or add controls and slow everyone down. In practice, the most successful developer platforms rarely make this tradeoff. Instead, they create environments where developers can move quickly because boundaries already exist. A developer deploying through a mature platform doesn’t need to think about every networking rule, access policy, or infrastructure safeguard every time they ship code. The platform already provides those guarantees. The same principle applies to agent systems. The goal isn’t to force developers to manually approve every action. The goal is to create environments where useful actions can happen safely by default.

A Tale of Two Teams

Imagine two engineering teams using the same coding agent. The first team allows agent usage only in limited experiments because nobody is completely certain what the agent can access, execute, or modify. Every new workflow requires additional review. Every new capability triggers a discussion about risk.

The second team operates within clearly defined boundaries around execution, tools, and credentials. Developers understand where agents run, what systems they can access, and how activity is observed.

The underlying model is identical. The difference is trust. Over time, that difference may matter more than the model itself. Organizations rarely scale technology they do not trust.

Why Boundaries Create Freedom

This idea sounds counterintuitive at first. Boundaries feel restrictive. But in software systems, boundaries often enable autonomy rather than limiting it.

When organizations know:

  • where agents run,
  • what agents can access,
  • which tools agents can use,
  • how activity is observed,

They become more comfortable delegating work. Without those boundaries, every workflow becomes an exception process. Every deployment requires discussion. Every new capability triggers concern. Every new tool requires negotiation. Governance reduces uncertainty. Reducing uncertainty increases trust. And trust enables adoption.

The Platform Shift

One thing that stands out in recent discussions around agent infrastructure is that governance is increasingly moving into the platform itself. Developers shouldn’t need to become security experts every time they use an agent. Just as developers rely on platforms to handle identity, networking, deployment, and observability concerns, governance increasingly becomes part of the environment where agents operate. When governance is embedded into the platform, developers spend less time worrying about boundaries and more time focusing on outcomes. That’s a developer experience improvement as much as a security improvement.

Governance as an Enabler

The organizations that adopt agents most successfully may not be the organizations with the fewest controls. They may be the organizations with the clearest controls. Clear boundaries create confidence. Confidence enables delegation. Delegation unlocks productivity. Viewed through that lens, governance is not the thing slowing agent adoption. It is one of the things that makes large-scale adoption possible.

Looking Ahead

The conversation around AI agents often focuses on what models can do. Increasingly, I think the more interesting question is what organizations are willing to trust them to do. That trust won’t come from capability alone. It will come from visibility, accountability, and well-defined boundaries because the future of agentic software is unlikely to be determined solely by the most capable agents. It will also be shaped by the environments that make those agents trustworthy enough to use at scale.

Learn more

Read the whole story
alvinashcraft
43 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

"If the code works, why does clean code matter?"

1 Share

Hello and Welcome, I’m your Code Monkey!

"If the code works, why does clean code matter?"

This is probably the most common argument I hear against clean code. And honestly, if you’re making a tiny one-day prototypes, maybe yeah it doesn't matter much since the project is too small to cause problems. But the longer the project goes, the more important it becomes!

The real question is not "does the code work today?"

The real question is "will the code still work after I add 20 new features?"

Messy code is fine when the project is tiny because you can keep everything in your head. Even if you are a beginner it’s not hard to keep 10 classes and 100 lines of code in your head. But as soon as the game grows, that stops being possible, you cannot keep a mental model of 1000 classes and 10,000 lines of code. On that scale, every new feature becomes slower to build. Every bug becomes harder to track. Every change creates fear because you don’t know what you might break.

That's when the project starts falling apart and that's when people usually quit and the project ends up as yet another one in the folder titled "AbandonedProjects"

Clean code is not about making your code pretty! Instead it’s about making sure you can keep working on the game tomorrow, next week, and 6 months from now and actually release it! Disposable prototypes can get away with anything but finished games need maintainable code.

So if you want to actually finish and release your games, I highly recommend you focus on writing the best quality code you can write.

Check out my talk on YouTube titled: "How to Write High Quality Code that doesn't fall apart" In this talk I cover a very detailed overview of what clean code is (and what isn’t) as well as some very practical guides for how you can write better code that enables you to build better games and actually finish them.

Thanks for reading! Best of luck in your game dev journey!



Get Rewards by Sending the Game Dev Report to a friend!

(please don’t try to cheat the system with temp emails, it won’t work, just makes it annoying for me to validate)

Thanks for reading!

Code Monkey

Read the whole story
alvinashcraft
43 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Creating a Production-Ready CRUD Application in ASP.NET Core

1 Share

Are you a beginner looking to create a portfolio to impress recruiters? Or perhaps an experienced developer looking to build better basic apps ready to evolve? Learn how to create a CRUD application in ASP.NET Core that goes beyond the basics and has everything you need to stand out in the real world.

In this post, you’ll learn how to structure an ASP.NET Core project following CRUD best practices, even when the premise is simple. The idea here is to build something you can actually use as a portfolio.

In addition, we’ll focus on decisions that experienced developers make daily: how to avoid unnecessary coupling, how to better model the domain, and how to prepare your application to grow without becoming chaotic.

By the end of the post, you’ll have a solid foundation on how to create a CRUD application ready to evolve into something bigger, maybe with authentication, messaging, caching, or even an event-driven architecture.

The Problem with Generic CRUDs

You’ve probably faced a requirement like this at some point: Create a simple, straightforward CRUD that only performs the basic operations of Create, Read, Update and Delete, with entities representing tables, and everything working in a few minutes. And that’s not wrong. The problem starts when this same model, designed for prototyping, is taken to production without being prepared for future evolution.

One of the clearest signs of a basic CRUD is the use of anemic entities, classes that only have properties, without any behavior:

public class User
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Here, the entity does not protect its own state. Therefore, it can be created with an empty name, it can have an invalid email, it can be changed from anywhere. In other words, there is no business rule, just a simple data structure.

Another classic symptom of a prematurely scaled CRUD is having rules and validations scattered throughout the code. In basic CRUDs, when rules appear, they are scattered a bit in the Controller class, a bit in the Service class, and in the worst cases they don’t even exist.

Consider the example below:

   if (string.IsNullOrWhiteSpace(user.Name))
        return Results.BadRequest();

This solves the immediate problem, but creates a bigger one: the rule doesn’t belong to the domain, it’s loose in the system. Therefore, as the system grows, validations can be duplicated, lose consistency and become harder to find everywhere because they are scattered.

The biggest problem here is that you’ve only modeled the data, not the system. Basic CRUDs focus on basic operations: creating, reading, updating and deleting data. But real systems are about behavior: A user can register, they can deactivate their account, they can change their email based on internal validations, they can have a preferred name, they can change their address …

When you only model data, the system loses meaning and becomes just a data handling tool, which can easily be replaced by anything else, like a simple Excel spreadsheet for example.

The opposite of this are applications that, despite using only basic operations, are prepared for new features that will likely be needed. The image below illustrates the main points of both approaches:

Creating a CRUD Ready for Production

Now that we’ve reviewed some examples to avoid, let’s see how to create a complete CRUD application, organizing each part of the code according to best practices and preparing the project for evolution. You can access the complete project code in this GitHub repository: Campus Hub source code.

Architecture and Basic Structure

The application will be a CRUD to manage university courses, which can be registered, updated and deactivated. We will use the principles of Clean Architecture combined with tactical Domain-Driven Design (DDD) to create the basic structure of the project. Thus, the application will have the following project organization:

src/

  • Presentation -> CampusHub.Api
  • Application -> CampusHub.Application
  • Domain -> CampusHub.Domain
  • Infrastructure -> CampusHub.Infrastructure

Let’s run the .NET commands to create the projects. In a terminal, execute the command below:

dotnet new sln -n CampusHub

This will create a new solution (CampusHub.sln). Then, to create the projects within the src directory, run the following command:

dotnet new webapi -n CampusHub.Api -o src/CampusHub.Api
dotnet new classlib -n CampusHub.Application -o src/CampusHub.Application
dotnet new classlib -n CampusHub.Domain -o src/CampusHub.Domain
dotnet new classlib -n CampusHub.Infrastructure -o src/CampusHub.Infrastructure

Now, to add the projects to the solution, run the following command:

dotnet sln add src/CampusHub.Api/CampusHub.Api.csproj
dotnet sln add src/CampusHub.Application/CampusHub.Application.csproj
dotnet sln add src/CampusHub.Domain/CampusHub.Domain.csproj
dotnet sln add src/CampusHub.Infrastructure/CampusHub.Infrastructure.csproj

Finally, run the following commands to add the dependencies between the projects:

dotnet add src/CampusHub.Application reference src/CampusHub.Domain
dotnet add src/CampusHub.Infrastructure reference src/CampusHub.Domain
dotnet add src/CampusHub.Infrastructure reference src/CampusHub.Application
dotnet add src/CampusHub.Api reference src/CampusHub.Application
dotnet add src/CampusHub.Api reference src/CampusHub.Infrastructure

Then, in the CampusHub.Infrastructure.cs add the following NuGet packages:


  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
  </ItemGroup>

Domain Layer

In the domain layer, we will place the project’s entities and enums. Therefore, we will have a class to represent the Course entity and an enum to represent the course statuses. So, within the CampusHub.Domain project, create a folder called Entities and add the following class inside it:

using CampusHub.Domain.Enums;

namespace CampusHub.Domain.Entities;

public class Course
{
    private const int MinNameLength = 3;
    private const int MaxNameLength = 200;
    private const int MinWorkload = 1;

    public Guid Id { get; private set; }
    public string Code { get; private set; } = string.Empty;
    public string Name { get; private set; } = string.Empty;
    public int WorkloadHours { get; private set; }
    public int MaxStudents { get; private set; }
    public CourseStatus Status { get; private set; }

    // EF Core requirement
    private Course() { }

    private Course(string code, string name, int workloadHours, int maxStudents)
    {
        Id = Guid.NewGuid();
        SetCode(code);
        SetName(name);
        SetWorkload(workloadHours);
        SetMaxStudents(maxStudents);

        Status = CourseStatus.Draft;
    }

    public static Course Create(string code, string name, int workloadHours, int maxStudents)
    {
        return new Course(code, name, workloadHours, maxStudents);
    }

    // Behavior methods
    public void UpdateDetails(string name, int workloadHours, int maxStudents)
    {
        EnsureNotArchived();

        SetName(name);
        SetWorkload(workloadHours);
        SetMaxStudents(maxStudents);
    }

    public void ChangeCapacity(int maxStudents)
    {
        EnsureNotArchived();

        SetMaxStudents(maxStudents);
    }

    public void Activate()
    {
        if (Status == CourseStatus.Active)
            throw new InvalidOperationException("Course is already active.");

        Status = CourseStatus.Active;
    }

    public void Archive()
    {
        if (Status == CourseStatus.Archived)
            throw new InvalidOperationException("Course is already archived.");

        Status = CourseStatus.Archived;
    }


    // Private validation logic
    private void SetCode(string code)
    {
        if (string.IsNullOrWhiteSpace(code))
            throw new ArgumentException("Course code cannot be empty.");

        Code = code.Trim().ToUpper();
    }

    private void SetName(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Course name cannot be empty.");

        if (name.Length < MinNameLength || name.Length > MaxNameLength)
            throw new ArgumentException($"Course name must be between {MinNameLength} and {MaxNameLength} characters.");

        Name = name.Trim();
    }

    private void SetWorkload(int workloadHours)
    {
        if (workloadHours < MinWorkload)
            throw new ArgumentException("Workload must be greater than zero.");

        WorkloadHours = workloadHours;
    }

    private void SetMaxStudents(int maxStudents)
    {
        if (maxStudents <= 0)
            throw new ArgumentException("Max students must be greater than zero.");

        MaxStudents = maxStudents;
    }

    private void EnsureNotArchived()
    {
        if (Status == CourseStatus.Archived)
            throw new InvalidOperationException("Archived courses cannot be modified.");
    }
}

Note that this entity has an excellent structure. It is not anemic, meaning it has behaviors such as the Activate(), Archive() and UpdateDetails() methods.

Furthermore, you can never create an invalid Course because the verification methods protect against creating an invalid state. For example, an archived course cannot be modified, and an active course cannot be reactivated.

Now, create a new folder called Enums and add the following enum to it:

namespace CampusHub.Domain.Enums;

public enum CourseStatus
{
    Draft = 0,
    Active = 1,
    Archived = 2
}

Application Layer

The application layer is where we will configure the service classes with CRUD methods and Data Transfer Objects (DTOs). So, within the CampusHub.Application project, create a new folder called DTOs and create the following records inside it:

namespace CampusHub.Application.DTOs;

public record CourseResponseDto(
    Guid Id,
    string Code,
    string Name,
    int WorkloadHours,
    int MaxStudents,
    string Status
);
namespace CampusHub.Application.DTOs;

public record CreateCourseDto(
    string Code,
    string Name,
    int WorkloadHours,
    int MaxStudents
);
namespace CampusHub.Application.DTOs;

public record UpdateCourseDto(
    string Name,
    int WorkloadHours,
    int MaxStudents
);

Then, create a new folder called Interfaces and add the following interfaces to it:

using CampusHub.Domain.Entities;

namespace CampusHub.Application.Interfaces;

public interface ICourseRepository
{
    Task AddAsync(Course course);
    Task<Course?> GetByIdAsync(Guid id);
    Task<List<Course>> GetAllAsync();
    Task Update(Course course);
    Task<bool> ExistsByCodeAsync(string code);
}
using CampusHub.Application.DTOs;

namespace CampusHub.Application.Interfaces;

public interface ICourseService
{
    Task<Guid> CreateAsync(CreateCourseDto dto);
    Task UpdateAsync(Guid id, UpdateCourseDto dto);
    Task ActivateAsync(Guid id);
    Task ArchiveAsync(Guid id);
    Task<CourseResponseDto?> GetByIdAsync(Guid id);
    Task<List<CourseResponseDto>> GetAllAsync();
}

Finally, create a new folder called Services and add the class below to it:

using CampusHub.Application.Interfaces;
using CampusHub.Domain.Entities;
using CampusHub.Application.DTOs;

namespace CampusHub.Application.Services;

public class CourseService : ICourseService
{
    private readonly ICourseRepository _repository;

    public CourseService(ICourseRepository repository)
    {
        _repository = repository;
    }

    public async Task<Guid> CreateAsync(CreateCourseDto dto)
    {
        var exists = await _repository.ExistsByCodeAsync(dto.Code);
        if (exists)
            throw new InvalidOperationException("Course code already exists.");

        var course = Course.Create(
            dto.Code,
            dto.Name,
            dto.WorkloadHours,
            dto.MaxStudents
        );

        await _repository.AddAsync(course);

        return course.Id;
    }

    public async Task UpdateAsync(Guid id, UpdateCourseDto dto)
    {
        var course = await _repository.GetByIdAsync(id)
            ?? throw new InvalidOperationException("Course not found.");

        course.UpdateDetails(dto.Name, dto.WorkloadHours, dto.MaxStudents);

        await _repository.Update(course);
    }

    public async Task ActivateAsync(Guid id)
    {
        var course = await _repository.GetByIdAsync(id)
            ?? throw new InvalidOperationException("Course not found.");

        course.Activate();

        await _repository.Update(course);
    }

    public async Task ArchiveAsync(Guid id)
    {
        var course = await _repository.GetByIdAsync(id)
            ?? throw new InvalidOperationException("Course not found.");

        course.Archive();

        await _repository.Update(course);
    }

    public async Task<CourseResponseDto?> GetByIdAsync(Guid id)
    {
        var course = await _repository.GetByIdAsync(id);

        if (course is null)
            return null;

        return MapToResponse(course);
    }

    public async Task<List<CourseResponseDto>> GetAllAsync()
    {
        var courses = await _repository.GetAllAsync();

        return courses.Select(MapToResponse).ToList();
    }

    private static CourseResponseDto MapToResponse(Course course)
    {
        return new CourseResponseDto(
            course.Id,
            course.Code,
            course.Name,
            course.WorkloadHours,
            course.MaxStudents,
            course.Status.ToString()
        );
    }
}

Here we have all the methods we need to execute the CRUD functions. Note that to modify the state of the objects we use the methods created in the domain class.

Infrastructure Layer

Now let’s create the infrastructure layer, used for communication with external parts of the application such as external APIs and databases. Within the CampusHub.Infrastructure project, create a new folder called Data and add the following classes to it:

using CampusHub.Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace CampusHub.src.CampusHub.Infrastructure.Data;

public class AppDbContext : DbContext
{
    public DbSet<Course> Courses => Set<Course>();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);

        base.OnModelCreating(modelBuilder);
    }
}
using CampusHub.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace CampusHub.src.CampusHub.Infrastructure.Data;

public class CourseConfiguration : IEntityTypeConfiguration<Course>
{
    public void Configure(EntityTypeBuilder<Course> builder)
    {
        builder.ToTable("Courses");

        builder.HasKey(c => c.Id);

        builder.Property(c => c.Code)
            .IsRequired()
            .HasMaxLength(20);

        builder.HasIndex(c => c.Code)
            .IsUnique();

        builder.Property(c => c.Name)
            .IsRequired()
            .HasMaxLength(200);

        builder.Property(c => c.WorkloadHours)
            .IsRequired();

        builder.Property(c => c.MaxStudents)
            .IsRequired();

        builder.Property(c => c.Status)
            .IsRequired()
            .HasConversion<int>(); // Enum -> int

        builder.Property(c => c.Id)
            .ValueGeneratedNever();
    }
}

Here we made the necessary configurations for the EF Core implementation, defining database details such as the table name, maximum number of characters for the Name property and others.

Next, create a new folder called Repositories and add the following class to it:

using CampusHub.Application.Interfaces;
using CampusHub.Domain.Entities;
using CampusHub.src.CampusHub.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace CampusHub.Infrastructure.Repositories;

public class CourseRepository : ICourseRepository
{
    private readonly AppDbContext _context;

    public CourseRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task AddAsync(Course course)
    {
        await _context.Courses.AddAsync(course);
        await _context.SaveChangesAsync();
    }

    public async Task<Course?> GetByIdAsync(Guid id)
    {
        return await _context.Courses.FirstOrDefaultAsync(c => c.Id == id);
    }

    public async Task<List<Course>> GetAllAsync()
    {
        return await _context.Courses.ToListAsync();
    }

    public async Task<bool> ExistsByCodeAsync(string code)
    {
        var normalizedCode = code.Trim().ToUpper();

        return await _context.Courses
            .AnyAsync(c => c.Code == normalizedCode);
    }

    public async Task Update(Course course)
    {
        _context.Courses.Update(course);
        await _context.SaveChangesAsync();
    }
}

In the repository class, we create the implementation of the CRUD methods that execute the operations on the database.

The last class in the infrastructure layer will be used to configure the dependency injection and define the connection string. So, inside the project add the class below:

using CampusHub.Application.Interfaces;
using CampusHub.Infrastructure.Repositories;
using CampusHub.src.CampusHub.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace CampusHub.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddDbContext<AppDbContext>(options =>
        options.UseMySql(
            configuration.GetConnectionString("DefaultConnection"),
            ServerVersion.AutoDetect(configuration.GetConnectionString("DefaultConnection"))
        ));

        services.AddScoped<ICourseRepository, CourseRepository>();

        return services;
    }
}

API Layer

The API is the final layer of the application, here we will define the controller classes and other settings such as the database connection string. Inside the Controllers folder, add the following controller:

using CampusHub.Application.DTOs;
using CampusHub.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace CampusHub.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
public class CoursesController : ControllerBase
{
    private readonly ICourseService _service;

    public CoursesController(ICourseService service)
    {
        _service = service;
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateCourseDto dto)
    {
        var id = await _service.CreateAsync(dto);

        return CreatedAtAction(nameof(GetById), new { id }, null);
    }

    [HttpGet("{id:guid}")]
    public async Task<IActionResult> GetById(Guid id)
    {
        var course = await _service.GetByIdAsync(id);

        if (course is null)
            return NotFound();

        return Ok(course);
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var courses = await _service.GetAllAsync();

        return Ok(courses);
    }

    [HttpPut("{id:guid}")]
    public async Task<IActionResult> Update(Guid id, UpdateCourseDto dto)
    {
        await _service.UpdateAsync(id, dto);

        return NoContent();
    }

    [HttpPost("{id:guid}/activate")]
    public async Task<IActionResult> Activate(Guid id)
    {
        await _service.ActivateAsync(id);

        return NoContent();
    }

    [HttpPost("{id:guid}/archive")]
    public async Task<IActionResult> Archive(Guid id)
    {
        await _service.ArchiveAsync(id);

        return NoContent();
    }
}

Then, in the Program class, replace the existing code with the code below:

using CampusHub.Application.Services;
using CampusHub.Application.Interfaces;
using CampusHub.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// Services

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();

// Application

builder.Services.AddScoped<ICourseService, CourseService>();

// Infrastructure

builder.Services.AddInfrastructure(builder.Configuration);

// Build

var app = builder.Build();

// Middleware

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Finally, in the file appsettings.json, add the following configuration. (Don’t forget to add your credentials!):

 "ConnectionStrings": {
    "DefaultConnection": "server=localhost;port=3306;database=campushub;user=YOUR_USER;password=YOUR_PASSWORD;"
  }
,

Running EF Core Commands

To apply the migration commands, in the project root, open a new terminal and run the commands below.

Creating the Migration Files

dotnet ef migrations add InitialCreate --project src/CampusHub.Infrastructure --startup-project src/CampusHub.Api --output-dir Data/Migrations

Applying the Migration Commands

dotnet ef database update --project src/CampusHub.Infrastructure --startup-project src/CampusHub.Api

What Does the Final Structure Look Like?

After implementing all the steps above, the project will have the following structure:

When Does a Complete CRUD Not Pay Off?

In this post, we created a complete CRUD, with all the necessary elements for its evolution. However, this complexity can exponentially increase development time and the level of knowledge required to maintain the project.

With this in mind, in some scenarios, simplicity is preferable. For example, when the system basically stores and retrieves data without relevant business rules. In scenarios such as basic registrations, auxiliary tables or internal administrative panels, adding layers such as services, rich entities and DDD patterns tends to generate more complexity than value, making the code more difficult to maintain without a justified need.

It is also not worthwhile to invest in a sophisticated architecture in the initial phases of a product, such as MVPs or systems still in validation. In these cases, the priority is speed and adaptation to frequent changes, and a simple structure allows for faster evolution without the burden of unnecessary abstractions.

On the other hand, a more elaborate CRUD makes sense when the domain has important rules, states, complex validations or when errors can have a significant impact. In these scenarios, a more structured architecture, as demonstrated in the post, helps protect the domain, maintain consistency and facilitate the system’s evolution over time (all parts are in their proper places).

The final decision of whether or not to use a complete and evolve-ready CRUD should therefore always take into account the level of complexity of the problem.

Conclusion and Next Steps

Knowing the best approach when creating new web applications is always a challenge, but we must keep in mind that if the project requires complex business rules, validations and errors that can have a significant impact, this is a strong indication that the application should not be a simple and generic CRUD.

In this post, we learned how to create a complete CRUD, with each layer representing a part of Clean Architecture, and using DDD development principles to implement a well-structured domain with private properties and behaviors.

But far beyond the basic structure presented in this post, a complete CRUD also involves unit tests, complex validations (FluentValidation), authentication and authorization with modern methods (JW Tokens for example), and many other elements. I hope this post serves as a starting point and helps you create CRUD applications that are not only ready to function, but also to evolve with quality over time.

Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Build an Excel-Like Spreadsheet in .NET MAUI Using Blazor Hybrid

1 Share

TL;DR: Learn how to build an Excel‑like spreadsheet UI inside a .NET MAUI app using Blazor Hybrid to deliver consistent cross‑platform data editing, reusable web components, and a maintainable app architecture with native performance and better UX.

Delivering a consistent and modern user experience across mobile and desktop platforms has become essential for today’s business applications. With .NET MAUI Blazor Hybrid, you can build apps for web, desktop, Android, iOS, and macOS using a single shared codebase. This reduces duplication, simplifies maintenance, and significantly accelerates development.

In this blog, we’ll explore how to integrate the Syncfusion® Blazor Spreadsheet component into a .NET MAUI Blazor Hybrid application to deliver an Excel-like experience across all platforms.

Why use a Spreadsheet UI in a .NET MAUI App?

A spreadsheet interface makes sense when users need flexible, cell-based editing rather than row-by-row data entry. Compared to a standard DataGrid, a spreadsheet UI is better suited for:

  • Financial or budgeting apps where formulas are required
  • Data entry tools with copy/paste operations
  • Reporting or planning screens where users expect Excel-like behavior
  • Scenarios where users work with files rather than records

Using Blazor inside .NET MAUI also brings architectural benefits. The UI logic lives in reusable Razor components, while MAUI handles native shell features like navigation, lifecycle, and platform APIs. This approach reduces duplication and keeps the app easier to maintain as it grows.

Prerequisites

Before starting, make sure you have the following set up:

  • Visual Studio 2026 with .NET MAUI and ASP.NET workloads installed
  • Latest .NET SDK
  • Basic familiarity with .NET MAUI and Blazor

Once your environment is ready, the next step is to create a .NET MAUI Blazor Hybrid app and configure it to host a spreadsheet UI.

Create a .NET MAUI Blazor Hybrid App

Let’s begin by creating a new .NET MAUI Blazor Hybrid application. In Visual Studio, create a new project and select the .NET MAUI Blazor Hybrid App template.

Choose the .NET MAUI Blazor Hybrid App template
Choose the .NET MAUI Blazor Hybrid App template

Enter your project name, choose a location, configure the solution details, and then click Next.

Configure the project name and details
Configure the project name and details

On the next screen, select the target .NET Framework version you want to use and finish creating the project.

Selecting the target .NET framework version
Selecting the target .NET framework version

Once the project is created, you’ll see the standard MAUI Blazor Hybrid project structure. This includes:

  • A standard .NET MAUI project layout
  • A wwwroot folder for web assets
  • Razor components rendered through BlazorWebView
Default MAUI Blazor Hybrid project template
Default MAUI Blazor Hybrid project template

This project serves as the foundation for hosting Blazor-based UI components inside a native .NET MAUI application.

Install the required NuGet packages

With the project ready, the next step is to add the packages required for the Spreadsheet component. Install the following NuGet packages:

These packages provide the Spreadsheet control itself along with the theme files required for proper styling.

You can install them through the NuGet Package Manager or run the following commands in the Package Manager Console:

Install-Package Syncfusion.Blazor.Spreadsheet 
Install-Package Syncfusion.Blazor.Themes

After the installation is complete, you’re ready to register the services required by the Spreadsheet component.

Register Syncfusion services

Before using the Spreadsheet component, you need to register the Syncfusion Blazor services in your MAUI application. Open the MauiProgram.cs file and add the AddSyncfusionBlazor() service registration inside the CreateMauiApp method:

using Syncfusion.Blazor;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.Services.AddSyncfusionBlazor();     
        ...
    }
}

This registration ensures that the required Syncfusion services are available when your application starts.

Add the required script and styles

Now that the services are registered, let’s add the resources required to render the Spreadsheet component correctly.

Open the wwwroot/index.html file and add the following CSS and JavaScript references inside the <head> section:

<head>
  ...
  <link href="_content/Syncfusion.Blazor.Themes/fluent2.css" rel="stylesheet" />
  <script src="_content/Syncfusion.Blazor.Spreadsheet/scripts/syncfusion-blazor-spreadsheet.min.js"></script>
</head>

The stylesheet provides the visual appearance of the component, while the JavaScript file enables the Spreadsheet’s interactive functionality.

Create the Spreadsheet page

With the application configured, it’s time to add the Spreadsheet component to your UI.
Open
Home.razor and add the following code:

@page "/"
@using Syncfusion.Blazor.Spreadsheet

<SfSpreadsheet @ref="Spreadsheet" DataSource="@DataSourceBytes">
    <SpreadsheetRibbon></SpreadsheetRibbon>
</SfSpreadsheet>

@code {
    private SfSpreadsheet? Spreadsheet;
    public byte[]?DataSourceBytes{ get; set; }

    protected override void OnInitialized()
    {
        var filePath = “wwwroot/Sample.xlsx”;
        DataSourceBytes= File.ReadAllBytes(filePath);
    }
}

In this example, the Spreadsheet component loads an Excel file and displays it directly inside the MAUI Blazor application.

Once the application runs, users can interact with the spreadsheet just as they would in a traditional spreadsheet application. Features include:

  • Cell editing
  • Formula calculations
  • Sorting and filtering
  • Import and export support
  • Ribbon-based commands

All of these capabilities run inside a native .NET MAUI app while allowing you to reuse the same Blazor-based UI across multiple platforms.

Excel-like Spreadsheet UI in a .NET MAUI Blazor Hybrid App
Excel-like Spreadsheet UI in a .NET MAUI Blazor Hybrid App

Common issues to watch for

If the Spreadsheet doesn’t appear as expected, here are a few common issues to check:

  • Blank page or missing styles: Verify that the required CSS and JavaScript references have been added correctly to index.html.
  • File not found errors: Make sure the Excel file is located in the wwwroot folder and configured with the appropriate build action.
  • Licensing issues: Ensure that your Syncfusion license key or trial configuration has been set up before running the application.

Checking these items early can help you avoid some of the most common setup issues and make the integration process much smoother.

GitHub reference

Want to see everything in action? Check out our GitHub sample for creating a Syncfusion Blazor Spreadsheet in .NET MAUI.

Frequently Asked Questions

When should I use a spreadsheet UI instead of a DataGrid in .NET MAUI?

Use a spreadsheet UI when users need Excel‑like interactions such as free‑form cell editing, formulas, copy/paste workflows, or file‑based data entry rather than row‑based records.

How does Blazor Hybrid help in building spreadsheet UIs for .NET MAUI?

Blazor Hybrid allows web-based UI components to run inside a native .NET MAUI app using a shared codebase, combining Blazor UI reuse with native performance and platform access.

Can this approach be used across mobile and desktop platforms?

Yes. The same Blazor components run consistently across Android, iOS, Windows, and macOS, while MAUI handles native app hosting and lifecycle management.

What are common issues when hosting a spreadsheet UI in a MAUI Blazor app?

The most common issues are missing CSS or JavaScript references, incorrect Excel file paths, and licensing or initialization issues during app startup.

Syncfusion Blazor components can be transformed into stunning and efficient web apps.

Conclusion

Embedding a spreadsheet UI into a .NET MAUI app using Blazor Hybrid is a practical way to deliver Excel-like data editing across platforms from a single codebase. By combining MAUI’s native capabilities with Blazor’s reusable web components, you can build applications that are easier to maintain, faster to develop, and more familiar to end users.

This approach works especially well for data-heavy business apps where flexibility, consistency, and productivity matter more than simple tabular display.

Additionally, Syncfusion’s Spreadsheet Editor SDK is available across multiple platforms, including  AngularASP.NET CoreJavaScriptASP.NET MVCVueWPFWinForms, and UWP, as well as Document SDK libraries.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us through our support forumsupport portal, or feedback portal for queries. We are always happy to assist you!

Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Azure DevOps Remote MCP Server is generally available

1 Share

Today, we’re excited to announce the general availability of the Azure DevOps MCP Server 🎉

The Azure DevOps MCP Server gives AI assistants secure, contextual access to your Azure DevOps projects so they can help you plan, build, and ship software more effectively. With the Azure DevOps remote MCP Server, you can get started without installing or hosting anything yourself. Simply connect your AI assistant directly to the Azure DevOps hosted endpoint by using streamable HTTP transport and start working with your projects in minutes.

Getting Started

Getting started in simple. Depending on the tools that you are using, you only need to add the following server information to your mcp.json.

{
  "servers": {
    "ado-remote-mcp": {
      "url": "https://mcp.dev.azure.com/{organization}",
      "type": "http"
    }
  },
  "inputs": []
}

There are additional configuration options available, and you can read more in our official documentation.

Supported Clients and Services

The Remote MCP Server is hosted by the Azure DevOps service and uses Microsoft Entra for authentication. Because it relies on Microsoft Entra, it follows the platform’s authentication requirements and constraints. As a result, your Azure DevOps organization must be backed by a Microsoft Entra tenant. Standalone organizations that use Microsoft accounts (MSAs) are not supported.

Support for the Remote MCP Server depends on a client’s ability to authenticate with Microsoft Entra. Today, clients such as Claude Desktop, Claude Code, ChatGPT, Cursor, and similar tools require support for dynamic OAuth client registration or client ID Metadata documents in Microsoft Entra before they can connect to the Remote MCP Server. We are working closely with the Microsoft Entra team to enable this capability. Until then, customers using these clients can continue using the local Azure DevOps MCP Server.

The following clients are supported today with no additional onboarding required:

Visual Studio Code with GitHub Copilot

Using the Azure DevOps MCP Server with GitHub Copilot in Visual Studio Code gives Copilot secure access to your Azure DevOps projects, enabling it to understand work items, pull requests, repositories, and pipelines. With this added context, Copilot can provide more relevant responses, automate common tasks, and help you stay focused without leaving your editor.

mcp vs code image

Microsoft Foundry

Azure AI Foundry is Microsoft’s end-to-end platform for building, evaluating, and managing AI applications and agents using foundation models, enterprise data, and integrated developer tools. In Foundry you can connect to all of the tools in Azure DevOps from the tools catalog.

azure foundry screenshot image

Microsoft Copilot Studio 🆕

Microsoft Copilot Studio is Microsoft’s low code platform for building, customizing, and deploying AI agents and copilots. It enables organizations to create conversational experiences that connect to enterprise data, automate business processes, and integrate with Microsoft 365 and external services. You can now connect your AI agents to Azure DevOps using the Azure DevOps MCP Server.

mcp copilot studio image

Other

The Azure DevOps Remote MCP Server is also supported by additional clients, including Visual Studio, GitHub Copilot CLI, and the GitHub Copilot app.

Local MCP Server

Although we recommend using the Remote MCP Server whenever possible, customers using clients that are not yet supported can continue to use the local Azure DevOps MCP Server.

We remain committed to maintaining feature parity between the local and remote servers. In fact, we recently completed a consolidation of the local MCP Server toolset so that it now aligns with the Remote MCP Server. We will continue to support and maintain the local MCP Server while we work with the Microsoft Entra team to enable the client registration capabilities required for broader Remote MCP Server support.

The post Azure DevOps Remote MCP Server is generally available appeared first on Azure DevOps Blog.

Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Two Insert Exec Problems

1 Share

Two Insert Exec Problems


Chapters

  • 00:00:00 – Introduction to the Learn T-SQL with Eric series
  • 00:01:34 – Understanding Insert Exec and Its Implications
  • 00:07:18 – Blocking Issues with Insert Exec
  • 00:12:59 – Performance Issues with Insert Exec
  • 00:16:07 – Conclusion and Next Steps

Full Transcript

Erik Darling here, Darling Data, I’ve got a rather exciting one for you today I think, probably anyway, we’re going to talk about two problems with insert exec, one of them I’ve actually shown on this very channel before, but since I have a new one I also want to include the last one because who knows how many of you have shown up to love, adore, and cherish our time together. Since I recorded the last one, I don’t know, I suppose there’s always a chance that some of you found that first video and that’s where you just decided this is the place for me, I’m here for life, but I don’t know, I don’t have those kind of metrics, no one tells me anything, so you’re getting the twofer, alright, good for you. Down in the video description, you’ll find all sorts of useful, helpful links in order for you to give me money in exchange for goods and services.

Services like SQL Server consulting, perhaps you would like me to address performance issues on your SQL Server, wouldn’t that be nice for you, right, you wouldn’t even have to talk to a robot for that to happen, I mean, aside from me. You can also purchase my training, down in the video description, there’s even a coupon code for the Learn T-SQL with Erik course where I talk about things just like this for hours and hours. It’s a Tantric experience, a Tantric T-SQL experience, perhaps the T in T-SQL is for Tantric, I don’t know, it’s Transact, alright, whatever. You can also become a subscribing member of the channel where you give me as few as four American dollars a month in exchange for all of this wonderful content.

You can continue to ask me office hours questions, I’m going to have to work on your taste in music and cloud providers. In the future for those, given recent dilemmas, but that’s, you know, something we can address later. And of course, if you enjoy this content, please do like, subscribe, tell a friend, yada, yada, yada, yada, yada.

If you would like free, as in gratis, gratis, gratissimo, SQL Server performance monitoring, boy, have I got a deal for you. More free, totally free, totally open source. You don’t need to give me an email address or, you know, worry about me, like, looking at your data secretly.

I don’t want it, unless you pay me. It is a bunch of T-SQL collectors running, getting all the important information about performance on your SQL servers and laying them out in nice charts and graphs for you to peruse, browse, and otherwise stare at in a flummoxed state of bafflement for as long as you can bear them. But there’s also…

There’s also a really nice thing in there. There is a built-in MCP server that is optional. You have to enable it yourself. I don’t turn it on by default so that you can have your robot companion friends read just your performance data. Look at just the nice collected aggregated performance data and perhaps give you a better chance of analyzing things a little bit more quickly.

It’s really helpful for folks who are not maybe as well-informed. in SQL Server performance issues as they would like to be or perhaps as well-versed as they should be. But a lot of folks do seem to like that part. But anyway, let’s you and I talk about Insert Exec because you got all sorts of stuff to talk about in here.

All right. So, the first thing I’m going to show you is the blocking problems that Insert Exec can incur. And the reason…

why this happens is because when you use insert exec, the exec portion of the insert has a transaction opened around it. So if your exec is doing more, is like say executing a store procedure that does a bunch of stuff which might include taking locks on things, might include executing other store procedures that perhaps take locks on things, those locks will be held until the insert completes. That can be a very very shocking experience for a lot of people.

Very very shocking. So I’ve got a store procedure here. This is insert exec 2. We’re gonna have to nest things a little bit so I’ve got a 2 and then a 1. Insert exec 2 declares a trancount and holds the current trancount, deletes from a table called lockme, inserts into a table called lockme, and the insert of course does this. Now just to sort of exacerbate a locking issue, I have a wait for delay of 5 seconds inside of insert exec 2. So that’s gonna hold the locks from the delete and the insert above for 5 seconds.

Insert exec 1 just looks at the current trancount, creates a temp table, and then inserts the trancount into the table. Because remember insert what insert exec 2 to or insert exec 2 does is inserts the transaction count from in here right so really what this does is it just shows you the transaction count incrementing to prove to you that in the context of insert exec there is a transaction right so that’s the whole point of this one so if i just if i run insert exec 2 first this will run for five seconds because there is a five second wait for and it will return a tram count of zero right because there is no current insert exec for this but if i run execute insert exec 1 where there is an insert exec where because insert exec 2 up here right we have this block right this is the part where we run into trouble even just inserting into a temp table but the problem really is that we have a delete and an insert in here right and this delete and insert is going to hold locks while the other stuff happens so what i’ve got here is if i run insert exec 1 here and i run this over here and i run sp who is active over here uh we we lost it but that’s okay uh we can do that again real quick and we’ll see just you know immediately the tram count before insert exec was zero and then we smuggled back a transaction here so let’s do that again and let’s run that and let’s just get the lock information from this one so in here you’ll see that i’ve run this over here and i’ve run sp who is active over here and i run and i’ve run this over here and i run this over here and i run this over here and i run this over here we can see um this mouse wheel is weird we can see uh the the wait for delay right uh this is the five seconds that insert exec 2 puts into things to exacerbate locking and we can see our select query here trying to select from lock me uh getting blocked right there uh the blocking session id or rather the the blocking information from who is active points directly to uh session 78 blocking session 84 that’s our select here and if we look over in the locks portion the the locks portion for the query that’s taking the locks is perhaps not terribly terribly interesting um you know we can see the obvious stuff uh we took locks we deleted we updated blah blah blah um i don’t know that one’s not that cool then we we also see the open tran count of one over here right so there’s multiple ways to validate that the insert exec uh does take the lock and then the query takes the lock and then the query takes the lock and then the take uh open a transaction around the entire insert exec thing that does that does not let up until the insert is completed right so all the stuff inside the exec is like all the locks in there are held until the insert completes that can be a very shocking thing for a lot of people but what i want to show you next is something even well something even crazier right so uh what i’m going to do is show you um uh does it does this database matter no not really we’re using temp stuff anyway uh the insert exec can not only block stuff but uh with even just a moderately sized result set it can really really slow things down and it’s really hard to figure out where the time is going and being spent right so i have a temporary store procedure here and this temporary store procedure basically just takes a number of rows that we want to return that really should be a big end but uh we’re not we’re only using i think two million or something in this so it doesn’t matter too much but it should be noted that the input uh to top to a top and uh even offset fetch is is all big end based so don’t don’t be too harsh on me so we’re gonna make this store this temporary store procedure and uh we’re also going to make this one now this one has sort of two paths in it right um we’re gonna say if this temp table exists we’re going to insert uh this query directly into the temp table if not we’re just going to execute the query down here but and this is to show you sort of a fix for the um the insert exec problem right so let’s make sure that query plans are enabled uh and then here this is where we’re going to do two different things uh the first one that we’re going to do is we’re going to create a temp table and then we’re going to insert uh exec like this and uh and then second one uh what we’re going to do is just show um if we use the shared temp table and we insert into that shared temp table locally uh the the time is no longer weird with things but i i’m going to run this all at once because we declare some stuff up here and then we reuse it in both both branches and i think that’s probably not worth retyping for this demo because you know what they say typing in demos just gets you into nothing but trouble right so uh we’ve got some statistics time output which is sort of valuable here just to get sort of an initial look at things and we can see that the first batch in here uh takes about seven and a half seconds to complete we’ve got this weird sort of five and a half seconds thing here and then uh so that was batch a completing right that was this one so about seven and a half seconds for two million rows and then down below we have batch b completing uh which takes about 1.2 seconds for those same 2 million rows this is just inserting directly into the temp table now where things get interesting right is we have um this initial thing here right and this takes 819 milliseconds all right if we look at the properties of this and if you’re looking at query plans please always be looking at properties uh this query looks like it finishes in 819 milliseconds and if you are looking at this query plan and you said this finishes in 819 milliseconds i would not call you totally wrong but we have if we look over here we have an additional five and a half seconds right or we have five and a half seconds so let’s just pretend let’s round a little bit let’s just say we have five seconds of of time that we cannot account for right like maybe i don’t know something weird happened we also have this fun thing all right non-parallelizable intrinsic function uh darn it uh well that’s okay it maxed off one makes more sense for this anyway right and if we look down here that we have this sort of oddball second query right uh and if we look we have this insert exec dest right uh this takes 1.6 seconds uh and comes off 2.4 seconds here but this parameter table scan so what sql server does is it for when you do insert exec uh there’s sort of like a hidden work table type thing where sql inserts all of the rows into that work table right and this is again there’s a transaction here and then from that print from that work table which is the parameter table scan then inserts all your rows where you want them to go so you’re doing like a double right copy thing here with that right so that’s not a very good time if we look at the query time stats on this one right this will actually line up pretty well with what we did right they have cpu and elapsed time at 1.7 seconds there that’s close i mean we’re we still like kind of lost a second or i don’t know like 100 milliseconds or so but i’m willing to forgive 100 millisecond loss but it’s it’s just quite interesting the way that pans out there’s just a count query down here to sort of separate things make visually things make a little bit more sense and then of course we have down here the sort of plain uh insert uh into the temptation table in the store procedure and the query time stats here make total sense right this took one second of time here which lines up pretty closely to what we did here so the insert exec portion double copies the rows and we end up with this weird big sort of time suck of stuff that happens i did a lot of work with um like uh windows performance recorder and um the purview i think to like break down the call stacks and stuff there are some technically interesting points in there that just show like what functions internally the time is spent in but it’s not very interesting on video the big thing you you have to understand the big thing you should start doing is if you have insert exec code that is uh just slow for reasons that you cannot easily determine well stop doing this right because this is this is the insert exact pattern that we’ve shown is bad from a blocking perspective and from a performance perspective and instead inside of your store procedures where you have to um where you would uh let’s just say sometimes you uh and let’s let’s say sometimes you use insert exec and you dump the results into a temp table other times you just execute the store procedure and return the results out one way that you can get around that and you might have to do a little bit more work here with like dynamic sql or something but one way that you can get around that is just look to see and this is very similar to uh the pattern that i showed you about getting triggers to selectively fire if this temp table exists then insert the rows into the temp table right because the outer store procedure will create the temp table um so it’ll be visible to the store procedure on the inner block right so we can see this temp table i can’t it’s showing squiggles here but that’s okay because in the original store procedure up here we create the temp table and that’s that’s where that’s where it will go up and down so um and also That’s where, sorry, in the code itself, we create the temp table so the store procedure can see it, right?

That’s this part down here. We create a table called shared and then we execute this and sort of conditionally inside, the presence of this table means a store procedure takes a different path, does the insert.

If it doesn’t see that temp table, then it just returns the select. And you don’t have to worry so much about like weird if branching stuff. If your code is all parameterized in this way and you’re running the same query either way, then you’ll get the compiled plan for both branches for the set of parameters that you pass in.

It’s a pretty good situation. Anyway, that’s enough of that. We’ve talked for too long. Thank you for watching. I hope you enjoyed yourselves. I hope you learned something and I will see you in tomorrow’s video where we will talk about, I forget, probably go back to talking about date and time stuff.

Go back to the Learn T-SQL with Eric experience. All right. Thank you for watching.

Going Further


If this is the kind of SQL Server stuff you love learning about, you’ll love my training. Blog readers get 25% off the Everything Bundle — over 100 hours of performance tuning content. Need hands-on help? I offer consulting engagements from targeted investigations to ongoing retainers. Want a quick sanity check before committing to a full engagement? Schedule a call — no commitment required.

The post Two Insert Exec Problems appeared first on Darling Data.

Read the whole story
alvinashcraft
44 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories