目录
- [.NET桌面应用架构Demo与实战|WPF+MVVM+EFCore+IOC+DI+Code First+AutoPapper](#.NET桌面应用架构Demo与实战|WPF+MVVM+EFCore+IOC+DI+Code First+AutoPapper)
- 技术栈简述
- 项目地址:
- 功能展示
- 项目结构
- 项目引用
- [1. 新建模型](#1. 新建模型)
- [2. Data层,依赖EF Core,实现数据库增删改查](#2. Data层,依赖EF Core,实现数据库增删改查)
- [3. Bussiness层,实现具体的业务逻辑](#3. Bussiness层,实现具体的业务逻辑)
- [4. Service层,业务逻辑封装](#4. Service层,业务逻辑封装)
- [5. UI层,用户界面](#5. UI层,用户界面)
- 参考
.NET桌面应用架构Demo与实战|WPF+MVVM+EFCore+IOC+DI+Code First+AutoPapper
该应用程序旨在高效地管理日常工作中所需的AI Prompt,支持新增、编辑、删除、快速复制以及查询等功能。然而,这些功能本身并不是本文讨论的重点;更关注的是背后的技术实现与架构设计思路。功能灵感来自 ChatHub - GPT-4o, Claude 3.5, Gemini 1.5 side by side
技术栈简述
- MVVM设计模式 将应用程序分为三个核心组件:模型(Model)、视图(View)和视图模型(ViewModel)。这种分离关注点的方法提高了应用程序的可维护性和可测试性。
- EFCore :一个轻量级的、可扩展的ORM(对象关系映射)框架,用于.NET平台。它允许使用C#代码来处理数据库操作,而不是直接编写SQL语句。
- Code First模式:EFCore支持Code First开发模式,开发者可以直接通过C#类定义来创建数据库模型,然后EFCore会自动生成数据库架构。
- IOC :一种设计原则,它倒置了传统程序设计中的控制流,使得高层模块不依赖于低层模块,两者都依赖于抽象。
- DI :实现IOC的一种方式,它通过外部注入依赖来实现模块间的解耦。
- AutoMapper:是一个对象到对象的映射库,它可以帮助开发者自动转换不同类型的对象,例如将数据库实体(Model)映射到视图模型(ViewModel)。
总体来讲,EFCore简化了数据库操作,而IOC和DI则提高了应用程序的可维护性和可测试性,AutoMapper的加入进一步减少了对象映射的复杂性。使用这些技术栈的组合,使得AI Prompt管理应用的开发更加高效、灵活和可靠。
项目地址:
https://github.com/Nita121388/NitasDemo/tree/main/08WPFArctitectureDemo
功能展示
软件启动后,会展示一个列表,其中包含了所有的AI Prompt(AI Prompt)。用户可以通过以下操作来管理这些Prompt:
- 查看所有Prompt:软件启动时,用户将看到一个包含所有AIPrompt的列表,方便快速浏览。
- 新增Prompt:用户可以通过点击"新增"按钮来添加新的AI Prompt。点击后,会弹出一个编辑界面,用户可以在这里输入标题和内容,完成新Prompt的创建。
- 编辑Prompt:在Prompt列表中,用户可以通过点击"编辑"按钮来修改现有的AIPrompt。编辑界面会加载当前Prompt的内容,用户可以对其进行修改。
- 删除Prompt:用户可以通过点击"删除"按钮来移除不再需要的AIPrompt。
- 使用Prompt:用户可以通过点击"使用"按钮来将选中的AI Prompt复制到系统剪贴板,用户就可以将Prompt粘贴到任何需要的AI对话系统中,以便快速输入或引用。
- 导出Prompt:用户可以通过点击"导出"按钮来保存当前所有的AI Prompt到一个Json文件中。这个操作允许用户将Prompt列表导出为JSON文件,方便备份或在其他设备上导入。
- 导入Prompt :用户可以通过点击"导入"按钮来从文件中加载AI Prompt。点击后,会弹出一个文件选择对话框,用户可以选择包含Prompt信息的文件进行导入。导入成功后,软件会将文件中的Prompt添加到现有的Prompt列表中。
项目结构
应用分层架构通过将应用程序划分为UI层、服务层、业务逻辑层、数据访问层和领域模型层,实现了关注点分离。每个层级都有明确的职责,UI层负责用户界面,服务层提供业务操作接口,业务逻辑层处理具体的业务需求,数据访问层处理数据库交互,而领域模型层则定义了业务实体。
c#
WFArchitectureDemo
│
├───────►UI
│ │
│ ├─────►Common
│ │ Helper
│ │ HostBuilders //主机构建器(HostBuilder) 配置应用程序的各种设置
│ ├─────►Converters
│ │ PercentageConverter.cs
│ ├─────►ViewModels
│ ├─────►Views
│ ├─────►App.xaml
│ │ App.xaml.cs //依赖注入与配置管理
│ ├─────►appsettings.json //数据库设置
│ └─────►MainWindow.xaml //启动界面
│
├────────►Service
│ │
│ ├─────►IServices //服务接口
│ │ IPromptService.cs
│ ├─────►Services //服务实现
│ │ PromptService.cs
│ └─────►Result.cs
│
├────────►Business
│ │
│ ├────►IManager //业务逻辑接口
│ │ IPromptManager.cs
│ │
│ ├────►Manager //具体的业务逻辑
│ │ PromptManager.cs
│ │
│ └────►DTO //数据传输对象
│ PromptDTO.cs
│ PromptUsageDTO.cs
│
├────────►Data //负责与数据库的直接交互,包括CRUD操作
│ │
│ ├────►RepositoryFactory
│ │ RepositoryFactory.cs
│ │
│ ├────►IRepository //仓储模式、CURD接口
│ │ IRepository.cs
│ │ IPromptRepository.cs
│ │ IPromptUsageRepository.cs
│ │
│ ├────►Repository //仓储模式、CURD实现
│ │ PromptRepository.cs
│ │ PromptUsageRepository.cs
│ │
│ ├────►DatabaseContexts //EF Core的数据库上下文
│ │ DbContextFactory.cs
│ │ PromptDbContext.cs
│ │
│ └────►Migrations //数据迁移文件,管理数据库变化
│
└────────►Domainn //领域模型
│
└────►Models
Prompt.cs
PromptUsage.cs
项目引用
CommunityToolkit.Mvvm
Microsoft.Xaml.Behaviors.Wpf
AutoMapper
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.Design
以下为该项目一步一步一层层的实现大致过程和概述。
1. 新建模型
c#
public class Prompt
{
public long ID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsDelete { get; set; }
public DateTime CreateDateTime { get; set; }
public DateTime UpdateDateTime { get; set; }
public ICollection<PromptUsage> PromptUsages { get; set; } = new List<PromptUsage>();
}
public class PromptUsage
{
public long ID { get; set; }
public long PromptID { get; set; }
public DateTime CreateDateTime { get; set; }
public Prompt Prompt { get; set; }
}
2. Data层,依赖EF Core,实现数据库增删改查
该层引用:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.Design
-
定义仓储接口IRepository
c#public interface IRepository<T> { T Add(T entity); void Delete(long id); void Update(T entity); List<T> Get(); List<T> Select(string filterSql,string orderBySql); T Get(long id); List<T> GetByForeignKey(long id); }
-
IPromptRepository和IPromptUsageRepository都继承自IRepository 接口,在由PromptRepository和PromptUsageRepository
-
PromptRepository和PromptUsageRepository依赖DbContextFactory提供的数据库上下文,具体实现增删改查
-
PromptDbContext是构建数据访问层的核心,继承DbContext,使用 EF Core 来定义数据库上下文、配置实体和它们之间的关系。
c#public class PromptDbContext : DbContext { public DbSet<Prompt> Prompts { get; set; } public DbSet<PromptUsage> PromptUsages { get; set; } public PromptDbContext(DbContextOptions options) : base(options) { } //设计时不能包含此行内容 protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置 Prompt 实体 modelBuilder.Entity<Prompt>(entity => { entity.HasKey(e => e.ID); entity.HasMany(e => e.PromptUsages) .WithOne(u => u.Prompt) .HasForeignKey(u => u.PromptID); }); // 配置 PromptUsage 实体 modelBuilder.Entity<PromptUsage>(entity => { entity.HasKey(e => e.ID); }); } }
-
Code First模式,生成迁移文件,创建数据库 💡
- Microsoft.EntityFrameworkCore.Tools是一个包含了用于创建、更新和管理数据库迁移的命令行工具的程序包。这些工具允许开发者在不直接修改数据库架构的情况下,通过代码来跟踪和更新数据库的变化。这些迁移可以应用于不同的数据库环境,从而帮助实现数据库的持续集成和持续部署。
- Microsoft.EntityFrameworkCore.Design包则主要用于在设计时为EF Core提供支持和功能,包括生成代码和模型等。虽然它本身不直接提供迁移功能,但它是迁移功能所依赖的一个重要组件。
在Vistual Studio →工具→NuGet包管理器→程序包管理器控制台,执行以下命令:
markdownPM> Add-Migration Init Build started... Build succeeded. To undo this action, use Remove-Migration. PM> update-database Build started... Build succeeded. Applying migration '20241108031752_Init'. Done. PM>
然后可以看到项目中新增了文件夹Migrations,其包含20241114045010_init.cs和PromptDbContextModelSnapshot
20241114045010_init.cs
:包含了一个迁移类,它定义了数据库架构的变化。这个类包含了Up
和Down
方法,分别用于应用迁移和撤销迁移。PromptDbContextModelSnapshot
:它包含了当前数据库上下文的模型快照。这个快照用于在添加新的迁移时比较模型的变化,确保迁移的准确性。
3. Bussiness层,实现具体的业务逻辑
Bussiness层调用Data层实现具体的业务逻辑,要依旧这层依赖倒置原则(Dependency Inversion Principle, DIP)------高层模块不应该依赖于低层模块,二者都应该依赖于其抽象;抽象不应该依赖于细节,细节应该依赖于抽象。IManager中是对外提供的接口,Manager中是具体的实现。
4. Service层,业务逻辑封装
Service层调用Bussiness层 可以进一步封装和抽象业务逻辑,使其更加模块化和可重用。
5. UI层,用户界面
初始化应用程序的服务和组件
从App.xaml.cs
开始,这是WPF应用程序的入口点。创建了一个继承自Application
的App
类,并在其中初始化应用程序的服务和组件,处理启动和退出事件,并确保数据库的迁移和主窗口的显示。
主要组件
- IServiceProvider:提供对应用程序服务的访问。
- IHost:.NET Core的主机构建器,用于配置和启动应用程序。
IHost主机构建器
定义一个CreateHostBuilder
方法,它使用Host.CreateDefaultBuilder
来设置主机构建器,并通过一系列静态类来扩展IHostBuilder
,来简化主机构建器的配置,以便在应用程序启动时注册所需的服务。
c#
public static IHostBuilder CreateHostBuilder(string[] args = null)
{
return Host.CreateDefaultBuilder(args)
.AddConfiguration()
.AddAutoMapper()
.AddDbContext()
.AddServices()
.AddViewModels()
.AddViews();
}
通过模块化配置,将不同的功能(如数据库访问、业务逻辑、视图模型等)分离到不同的类中,从而提高代码的可读性和可维护性。添加了以下组件:
配置:通过AddConfiguration
方法加载appsettings.json
和环境变量。
c#
public static IHostBuilder AddConfiguration(this IHostBuilder host)
{
host.ConfigureAppConfiguration(c =>
{
c.AddJsonFile("appsettings.json");
c.AddEnvironmentVariables();
});
return host;
}
注册对象映射
在代码中涉及将DTO对象转为ViewModel,此处采用AutoMapper来实现便捷的映射
c#
public static IHostBuilder AddAutoMapper(this IHostBuilder host)
{
host.ConfigureServices((context, services) =>
{
services.AddAutoMapper(typeof(App));
});
return host;
}
数据库配置
通过AddDbContext
方法配置Entity Framework Core,数据库上下文
c#
public static IHostBuilder AddDbContext(this IHostBuilder host)
{
host.ConfigureServices((context, services) =>
{
string connectionString = context.Configuration.GetConnectionString("sqlite");
Action<DbContextOptionsBuilder> configureDbContext = o => o.UseSqlite(connectionString);
// 注册 DbContext
services.AddDbContext<PromptDbContext>(configureDbContext);
services.AddSingleton<DbContextFactory>(sp => new DbContextFactory(configureDbContext));
services.AddSingleton<RepositoryFactory>(sp => new RepositoryFactory(configureDbContext));
});
return host;
}
注册应用程序服务
c#
public static IHostBuilder AddServices(this IHostBuilder host)
{
host.ConfigureServices(services =>
{
// 注册 IPromptManager 和 IPromptService
services.AddSingleton<IPromptManager, PromptManager>(sp =>
{
var repositoryFactory = sp.GetRequiredService<RepositoryFactory>();
return new PromptManager(repositoryFactory);
});
services.AddSingleton<IPromptService, PromptService>(sp =>
{
var promptManager = sp.GetRequiredService<IPromptManager>();
return new PromptService(promptManager);
});
});
return host;
}
使用
c#
private IPromptService _promptService;
private IMapper _mapper;
public PromptsViewModel()
{
_promptService = App.Current.Services.GetRequiredService<IPromptService>();
_mapper = App.Current.Services.GetRequiredService<IMapper>();
}
注册视图模型
c#
public static IHostBuilder AddViewModels(this IHostBuilder host)
{
host.ConfigureServices(services =>
{
services.AddTransient<PromptsViewModel>();
});
return host;
}
使用:
c#
public MainWindow()
{
InitializeComponent();
ViewModel = App.Current.Services.GetRequiredService<PromptsViewModel>();
ViewModel.Refresh();
DataContext = ViewModel;
}
注册视图
c#
public static IHostBuilder AddViews(this IHostBuilder host)
{
host.ConfigureServices(services =>
{
services.AddSingleton<MainWindow>();
});
return host;
}
使用
c#
Window window = _host.Services.GetRequiredService<MainWindow>();
window.Show();
定义AutoMapper映射规则
AutoMapper用于简化对象之间的映射。创建了一个MappingProfile
类来定义映射规则。
c#
public class MappingProfile : Profile
{
public MappingProfile()
{
// PromptUsageViewModel => PromptUsageDTO
CreateMap<PromptUsageViewModel, PromptUsageDTO>();
//PromptUsageDTO => PromptUsageViewModel
CreateMap<PromptUsageDTO, PromptUsageViewModel>();
// PromptViewModel => PromptDTO
CreateMap<PromptViewModel, PromptDTO>()
.ForMember(
dest => dest.Usages,
opt => opt.MapFrom(src => src.Usages.ToList()));
CreateMap<PromptDTO, PromptViewModel>()
.ForMember(
dest => dest.Usages,
opt => opt.MapFrom(src => src.Usages.ToList()));
}
}
使用示例
c#
var promptViewModel = _mapper.Map<PromptViewModel>(promptDTO);
应用程序启动与关闭
在OnStartup
方法中,启动主机(这会触发所有服务的注册和初始化),然后执行数据库迁移,最后显示主窗口。在OnExit
方法中,停止并释放主机资源。
c#
/// <summary>
/// 在应用程序启动时执行的操作。
/// </summary>
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 启动主机,这将触发所有服务的注册和初始化。
_host?.Start();
// 获取DbContextFactory服务的实例,用于创建数据库上下文。
DbContextFactory contextFactory = _host.Services.GetRequiredService<DbContextFactory>();
// 使用DbContextFactory创建数据库上下文实例,并应用所有未应用的迁移。
// 这确保了数据库结构是最新的,以匹配应用程序的数据模型。
using (var context = contextFactory.CreateDbContext())
{
context.Database.Migrate();
}
// 获取MainWindow服务的实例
Window window = _host.Services.GetRequiredService<MainWindow>();
// 显示主窗口
window.Show();
}
c#
protected override async void OnExit(ExitEventArgs e)
{
// 等待_host对象的StopAsync方法完成,这个方法应该是停止一些后台服务的操作。
await _host.StopAsync();
// 释放_host对象所占用的资源。
_host.Dispose();
// 调用基类的OnExit方法,确保基类的退出逻辑也能被执行。
base.OnExit(e);
}
示例
c#
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public new static App Current => (App)Application.Current;
public IServiceProvider Services { get; }
private readonly IHost _host;
public App()
{
_host = CreateHostBuilder().Build();
Services = _host.Services;
}
public static IHostBuilder CreateHostBuilder(string[] args = null)
{
return Host.CreateDefaultBuilder(args)
.AddConfiguration()
.AddAutoMapper()
.AddDbContext()
.AddServices()
.AddViewModels()
.AddViews();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_host?.Start();
DbContextFactory contextFactory = _host.Services.GetRequiredService<DbContextFactory>();
using (var context = contextFactory.CreateDbContext())
{
context.Database.Migrate();
}
Window window = _host.Services.GetRequiredService<MainWindow>();
window.Show();
}
protected override async void OnExit(ExitEventArgs e)
{
await _host.StopAsync();
_host.Dispose();
base.OnExit(e);
}
}
UI+ViewModelM界面交互处理
在用户界面层(UI Layer)中,采用了CommunityToolkit.Mvvm库来实现MVVM(Model-View-ViewModel)设计模式。通过MVVM模式,实现了命令绑定和数据绑定,使得用户界面与业务逻辑之间的交互更加清晰和高效。因为此处不是本文重点所以不详细介绍啦。
参考
- 实体框架文档中心 | Microsoft Learn
- https://www.youtube.com/playlist?list=PLA8ZIAm2I03jSfo18F7Y65XusYzDusYu5
- Construction --- AutoMapper documentation
- SingletonSean/SimpleTrader:全栈 WPF MVVM 交易应用程序。 --- SingletonSean/SimpleTrader: A full stack WPF MVVM trading application.
- xtenzQ/WPF-MVVM-EFC-Example: 📲 MVVM (WPF) application built with EFCore, Abstract Factory pattern and dependency injection (Microsoft Unity)
平平无奇的代码小白,如有问题请多指正!