You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF中Simple Injector结合EF的仓储注入及命令模式替代方案问询

Fixing DbContext Disposal Issues & Alternatives to Command Pattern in WPF with Simple Injector

Hey Danil, let's break down your problems and work through practical, straightforward solutions:

First: Resolve the DbContext Already Disposed Exception

The root cause of your error is that the ThreadScopedLifestyle scope you created in OnStartup gets disposed as soon as the wrapping using block finishes. But your MainWindow and its ViewModel have a much longer lifecycle—so when you try to use the repository later, the DbContext tied to that initial scope is already gone.

Correct Scope Management for WPF

WPF doesn't have built-in "request boundaries" like web apps, so we need to align the scope lifecycle with your database operations. Here are two solid approaches:

Option 1: Create a Scope Per Database Operation (Simple & Direct)

You don't need to pass the container to services—instead, wrap each database call in a scope where you invoke the service:

// In your ViewModel or UI event handler
private void RunTestDatabaseOperation()
{
    using (ThreadScopedLifestyle.BeginScope(App.appContainer))
    {
        var service = App.appContainer.GetInstance<IMainAppSerivce>();
        service.TestWorkWithDb(new int[] { 1, 2, 3 });
    }
}

This ensures every database interaction runs within a valid scope, so the DbContext won't be disposed prematurely.

Option 2: Switch to AsyncScopedLifestyle (Recommended for Modern WPF)

ThreadScopedLifestyle can break if you use background threads (like Task.Run), since it's tied strictly to the current thread. AsyncScopedLifestyle works better with async/await patterns, which are critical for keeping your WPF UI responsive:

private void ConfigureContainer()
{
    appContainer = new Container();
    // Replace ThreadScoped with AsyncScoped
    appContainer.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
    
    appContainer.RegisterSingleton<INLogger, LoggerNlog>();
    
    appContainer.Register<DbContext>(() => {
        string connectionString = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"].ToString();
        return new ExcelSlicerContext(connectionString);
    }, Lifestyle.Scoped);
    
    appContainer.Register(typeof(IRepository<>), typeof(BaseRepository<>), Lifestyle.Scoped);
    appContainer.Register<IMainAppSerivce, SlicerMainService>();
    appContainer.Register<MainViewModel>();
    appContainer.Verify();
}

Then use it in async operations:

private async Task LoadDataAsync()
{
    using (AsyncScopedLifestyle.BeginScope(App.appContainer))
    {
        var service = App.appContainer.GetInstance<IMainAppSerivce>();
        await Task.Run(() => service.TestWorkWithDb(new int[] { 1, 2, 3 }));
    }
}

Second: Alternative to Command Pattern (Avoid Class Explosion)

It makes total sense that creating a command and handler for every tiny operation feels like overkill. A more straightforward approach for your scenario is to create entity-specific business services—these wrap all CRUD and business logic for a single entity in one cohesive class.

Step 1: Create an Entity-Specific Service Interface

For FactModel, define an IFactService that exposes all the operations you need:

public interface IFactService
{
    IEnumerable<FactModel> GetFactsWithRowGreaterThan(int minRow);
    void AddSingleFact(FactModel fact);
    void AddBatchOfFacts(IEnumerable<FactModel> facts);
    // Add any other business-specific methods here
}

Step 2: Implement the Service

Inject your repository and logger into the service, then encapsulate the logic:

public class FactService : IFactService
{
    private readonly IRepository<FactModel> _factRepository;
    private readonly INLogger _logger;

    public FactService(IRepository<FactModel> factRepository, INLogger logger)
    {
        _factRepository = factRepository;
        _logger = logger;
    }

    public IEnumerable<FactModel> GetFactsWithRowGreaterThan(int minRow)
    {
        try
        {
            return _factRepository.Get(x => x.Row > minRow).ToList();
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Failed to retrieve fact data");
            throw; // Re-throw so the caller can handle it gracefully
        }
    }

    public void AddSingleFact(FactModel fact)
    {
        _factRepository.Create(fact);
    }

    public void AddBatchOfFacts(IEnumerable<FactModel> facts)
    {
        _factRepository.CreateFromRange(facts);
    }
}

Step 3: Register the Service in Simple Injector

Add this line to your ConfigureContainer method:

appContainer.Register<IFactService, FactService>(Lifestyle.Scoped);

Step 4: Use the Service in Your ViewModel/Other Services

Inject the service directly into your ViewModel (no container references needed):

public class MainViewModel
{
    private readonly IFactService _factService;

    public MainViewModel(IFactService factService)
    {
        _factService = factService;
    }

    private void LoadFactData()
    {
        using (AsyncScopedLifestyle.BeginScope(App.appContainer))
        {
            var facts = _factService.GetFactsWithRowGreaterThan(6);
            // Bind facts to your UI controls here
        }
    }
}

Why This Works Better for Your Case

  • No more class explosion: All logic for FactModel lives in one place instead of dozens of command/handler pairs
  • Clear separation of concerns: Data access stays in the repository, business logic stays in the service
  • Maintains DI benefits: You still get all the dependency injection perks from Simple Injector, with proper scope management

Bonus Optimizations

  1. Stop calling SaveChanges in the repository: Your BaseRepository calls SaveChanges in methods like Create—this forces a transaction per operation. Instead, let the service layer control when to save, or implement a Unit of Work pattern to batch changes.
  2. Go async with EF: EF Core (and EF6) supports async operations. Update your repository and service methods to use GetAsync, AddAsync, etc., to keep your WPF UI responsive.
  3. Avoid container calls in ViewModels: Use a ViewModel Locator pattern to inject services into ViewModels automatically, so you don't have to call GetInstance directly.

内容的提问来源于stack exchange,提问作者DanilGholtsman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:39:30