feat(worker,ui): wire EF Core into DI and update all consumers to IDbContextFactory
Worker and App Program.cs: replace SqliteConnectionFactory+SchemaInitializer with AddDbContextFactory<ClaudeDoDbContext> + Database.Migrate(). Repos changed from AddSingleton to AddScoped. All singleton services (QueueService, StaleTaskRecovery, WorktreeManager, TaskRunner) and singleton ViewModels (MainWindowViewModel, TaskDetailViewModel, TaskListViewModel, TaskEditorViewModel) now take IDbContextFactory<ClaudeDoDbContext> and create short-lived contexts per operation. Test infrastructure: DbFixture now uses EF migrations instead of SchemaInitializer; all test classes create contexts via DbFixture.CreateContext(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,21 +2,21 @@ using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels;
|
||||
|
||||
public partial class TaskListViewModel : ViewModelBase
|
||||
{
|
||||
private readonly TaskRepository _taskRepo;
|
||||
private readonly TagRepository _tagRepo;
|
||||
private readonly ListRepository _listRepo;
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly WorkerClient _worker;
|
||||
private readonly Func<TaskEditorViewModel> _editorFactory;
|
||||
private readonly Action<string> _showMessage;
|
||||
@@ -33,13 +33,10 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
partial void OnSelectedTaskChanged(TaskItemViewModel? value) =>
|
||||
SelectedTaskChanged?.Invoke(value);
|
||||
|
||||
public TaskListViewModel(TaskRepository taskRepo, TagRepository tagRepo,
|
||||
ListRepository listRepo, WorkerClient worker,
|
||||
public TaskListViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, WorkerClient worker,
|
||||
Func<TaskEditorViewModel> editorFactory, Action<string> showMessage)
|
||||
{
|
||||
_taskRepo = taskRepo;
|
||||
_tagRepo = tagRepo;
|
||||
_listRepo = listRepo;
|
||||
_dbFactory = dbFactory;
|
||||
_worker = worker;
|
||||
_editorFactory = editorFactory;
|
||||
_showMessage = showMessage;
|
||||
@@ -77,7 +74,9 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
if (listId is not null)
|
||||
{
|
||||
var list = await _listRepo.GetByIdAsync(listId);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var listRepo = new ListRepository(context);
|
||||
var list = await listRepo.GetByIdAsync(listId);
|
||||
ListName = list?.Name ?? "Tasks";
|
||||
}
|
||||
else
|
||||
@@ -89,10 +88,12 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
var entities = await _taskRepo.GetByListAsync(listId);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
var entities = await taskRepo.GetByListIdAsync(listId);
|
||||
foreach (var e in entities)
|
||||
{
|
||||
var tags = await _taskRepo.GetEffectiveTagsAsync(e.Id);
|
||||
var tags = await taskRepo.GetEffectiveTagsAsync(e.Id);
|
||||
Tasks.Add(new TaskItemViewModel(e, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync));
|
||||
}
|
||||
}
|
||||
@@ -110,8 +111,13 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
var title = InlineAddTitle.Trim();
|
||||
if (string.IsNullOrEmpty(title) || CurrentListId is null) return;
|
||||
|
||||
var list = await _listRepo.GetByIdAsync(CurrentListId);
|
||||
var defaultCommitType = list?.DefaultCommitType ?? "chore";
|
||||
string defaultCommitType;
|
||||
using (var context = _dbFactory.CreateDbContext())
|
||||
{
|
||||
var listRepo = new ListRepository(context);
|
||||
var list = await listRepo.GetByIdAsync(CurrentListId);
|
||||
defaultCommitType = list?.DefaultCommitType ?? "chore";
|
||||
}
|
||||
|
||||
var entity = new TaskEntity
|
||||
{
|
||||
@@ -125,8 +131,10 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
await _taskRepo.AddAsync(entity);
|
||||
var tags = await _taskRepo.GetEffectiveTagsAsync(entity.Id);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
await taskRepo.AddAsync(entity);
|
||||
var tags = await taskRepo.GetEffectiveTagsAsync(entity.Id);
|
||||
var vm = new TaskItemViewModel(entity, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync);
|
||||
Tasks.Add(vm);
|
||||
SelectedTask = vm;
|
||||
@@ -141,9 +149,13 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
[RelayCommand(CanExecute = nameof(CanAddTask))]
|
||||
private async Task AddTask()
|
||||
{
|
||||
// Get list default commit type
|
||||
var list = await _listRepo.GetByIdAsync(CurrentListId);
|
||||
var defaultCommitType = list?.DefaultCommitType ?? "chore";
|
||||
string defaultCommitType;
|
||||
using (var context = _dbFactory.CreateDbContext())
|
||||
{
|
||||
var listRepo = new ListRepository(context);
|
||||
var list = await listRepo.GetByIdAsync(CurrentListId);
|
||||
defaultCommitType = list?.DefaultCommitType ?? "chore";
|
||||
}
|
||||
|
||||
var editor = _editorFactory();
|
||||
await editor.LoadAgentsAsync(_worker);
|
||||
@@ -159,15 +171,18 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
await _taskRepo.AddAsync(saved);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
var tagRepo = new TagRepository(context);
|
||||
await taskRepo.AddAsync(saved);
|
||||
|
||||
foreach (var tagName in editor.SelectedTagNames)
|
||||
{
|
||||
var tagId = await _tagRepo.GetOrCreateAsync(tagName);
|
||||
await _taskRepo.AddTagAsync(saved.Id, tagId);
|
||||
var tagId = await tagRepo.GetOrCreateAsync(tagName);
|
||||
await taskRepo.AddTagAsync(saved.Id, tagId);
|
||||
}
|
||||
|
||||
var tags = await _taskRepo.GetEffectiveTagsAsync(saved.Id);
|
||||
var tags = await taskRepo.GetEffectiveTagsAsync(saved.Id);
|
||||
Tasks.Add(new TaskItemViewModel(saved, tags, RunNowAsync, () => _worker.IsConnected, ToggleDoneAsync));
|
||||
|
||||
// Auto wake-queue if agent+queued
|
||||
@@ -188,10 +203,17 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
private async Task EditTask()
|
||||
{
|
||||
if (SelectedTask is null || CurrentListId is null) return;
|
||||
var entity = await _taskRepo.GetByIdAsync(SelectedTask.Id);
|
||||
if (entity is null) return;
|
||||
|
||||
var taskTags = await _taskRepo.GetTagsAsync(entity.Id);
|
||||
TaskEntity? entity;
|
||||
List<TagEntity> taskTags;
|
||||
using (var context = _dbFactory.CreateDbContext())
|
||||
{
|
||||
var taskRepo = new TaskRepository(context);
|
||||
entity = await taskRepo.GetByIdAsync(SelectedTask.Id);
|
||||
if (entity is null) return;
|
||||
taskTags = await taskRepo.GetTagsAsync(entity.Id);
|
||||
}
|
||||
|
||||
var editor = _editorFactory();
|
||||
await editor.LoadAgentsAsync(_worker);
|
||||
await editor.InitForEditAsync(entity, taskTags);
|
||||
@@ -206,18 +228,21 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
try
|
||||
{
|
||||
await _taskRepo.UpdateAsync(saved);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
var tagRepo = new TagRepository(context);
|
||||
await taskRepo.UpdateAsync(saved);
|
||||
|
||||
var existingTags = await _taskRepo.GetTagsAsync(saved.Id);
|
||||
var existingTags = await taskRepo.GetTagsAsync(saved.Id);
|
||||
foreach (var old in existingTags)
|
||||
await _taskRepo.RemoveTagAsync(saved.Id, old.Id);
|
||||
await taskRepo.RemoveTagAsync(saved.Id, old.Id);
|
||||
foreach (var tagName in editor.SelectedTagNames)
|
||||
{
|
||||
var tagId = await _tagRepo.GetOrCreateAsync(tagName);
|
||||
await _taskRepo.AddTagAsync(saved.Id, tagId);
|
||||
var tagId = await tagRepo.GetOrCreateAsync(tagName);
|
||||
await taskRepo.AddTagAsync(saved.Id, tagId);
|
||||
}
|
||||
|
||||
var newTags = await _taskRepo.GetEffectiveTagsAsync(saved.Id);
|
||||
var newTags = await taskRepo.GetEffectiveTagsAsync(saved.Id);
|
||||
SelectedTask.Refresh(saved, newTags);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -232,7 +257,9 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
if (SelectedTask is null) return;
|
||||
try
|
||||
{
|
||||
await _taskRepo.DeleteAsync(SelectedTask.Id);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
await taskRepo.DeleteAsync(SelectedTask.Id);
|
||||
Tasks.Remove(SelectedTask);
|
||||
SelectedTask = null;
|
||||
}
|
||||
@@ -244,14 +271,16 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
public async Task RefreshSingleAsync(string taskId)
|
||||
{
|
||||
var entity = await _taskRepo.GetByIdAsync(taskId);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
var entity = await taskRepo.GetByIdAsync(taskId);
|
||||
var existing = Tasks.FirstOrDefault(t => t.Id == taskId);
|
||||
if (entity is null)
|
||||
{
|
||||
if (existing is not null) Tasks.Remove(existing);
|
||||
return;
|
||||
}
|
||||
var tags = await _taskRepo.GetEffectiveTagsAsync(taskId);
|
||||
var tags = await taskRepo.GetEffectiveTagsAsync(taskId);
|
||||
if (existing is not null)
|
||||
existing.Refresh(entity, tags);
|
||||
}
|
||||
@@ -270,14 +299,16 @@ public partial class TaskListViewModel : ViewModelBase
|
||||
|
||||
private async Task ToggleDoneAsync(string taskId)
|
||||
{
|
||||
var entity = await _taskRepo.GetByIdAsync(taskId);
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var taskRepo = new TaskRepository(context);
|
||||
var entity = await taskRepo.GetByIdAsync(taskId);
|
||||
if (entity is null) return;
|
||||
|
||||
entity.Status = entity.Status == TaskStatus.Done ? TaskStatus.Manual : TaskStatus.Done;
|
||||
if (entity.Status == TaskStatus.Done)
|
||||
entity.FinishedAt = DateTime.UtcNow;
|
||||
|
||||
await _taskRepo.UpdateAsync(entity);
|
||||
await taskRepo.UpdateAsync(entity);
|
||||
await RefreshSingleAsync(taskId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user