视频讲解 第5章〓书山有路勤为径〓学海无涯苦作舟——MAUI数据访问 5.1本地数据库 5.1.1环境搭建 第2章中讲述了Sqlite数据库,作为一款轻量级的关系数据库,其资源占用小,对资源有限的智能手机来说,Sqlite数据库特别适合嵌入式开发的场景。 搭建Sqlite数据库环境非常简单,安装Sqlite相关的软件包即可。NuGet作为Visual Studio的扩展,是一款开源的包管理开发工具,使用该工具可以非常方便地将第三方的组件库整合至项目中,类似于Maven和Npm等工具。 第一种安装程序包的方法是采用命令行的方式进行。在Visual Studio中选择“工具”→“NuGet 包管理器(N)”→“程序包管理控制台(O)”选项后,打开PowerShell控制台执行安装命令安装相关库。 【例51】使用PowerShell 安装软件包。 1Install-Package sqlite-net-pcl 2Install-Package SQLitePCLRaw.bundle_green 第二种安装程序包的方法是采用图形化界面的方式进行。在Visual Studio中选择“工具”→“NuGet包管理器(N)”→“管理解决方案的NuGet程序包(N)”选项,打开NuGet管理向导后,选择相应的项目,搜索需要安装的软件包安装即可。管理解决方案的NuGet程序包如图51所示。 图51管理解决方案的NuGet 程序包 第三种安装程序包的方法是采用修改工程配置文件的方式进行。双击项目打开MAUIDemo.csproj 配置文件,找到相应的配置节点并追加如下配置。建议安装最新稳定版本。 【例52】工程方式配置Sqlite。 MAUIDemo.csproj代码如下: 1 2 3 4 5.1.2功能封装 Sqlite数据库支持多种编程语言,本节采用C#进行相关功能的封装。软件工程的核心思想之一是封装,封装的目的是便于后期各种工程复用。对于任何包含主键的表,需要定义基本模型。 【例53】Sqlite数据库功能封装。 BaseModel.cs代码如下: 1using SQLite; 2 3namespace MAUISDK.Models 4{ 5public class BaseModel 6{ 7[PrimaryKey, AutoIncrement] 8public long Id 9{ 10get; 11set; 12} 13} 14} 上述基本模型仅有一个字段,Id代表数据库索引,所以添加注解PrimaryKey表示主键,注解AutoIncrement表示自动增长,这样无须用户手工维护主键索引。基本模型能够满足大部分建表需求。定义业务对象时,只需要继承基本模型即可。 MAUILearning.cs代码如下: 1namespace MAUISDK.Models 2{ 3public class MAUILearning : BaseModel 4{ 5public string ?Learning 6{ 7get; 8set; 9} 10} 11} MAUILearning类作为Sqlite数据库操作示例模型,继承BaseModel,为简化起见,仅定义一个Learning字段。 IMAUIRepository.cs代码如下: 1using MAUISDK.Models; 2 3namespace MAUISDK.Interfaces 4{ 5public interface IMAUIRepository where T : BaseModel 6{ 7bool Exist(long Id); 8T Find(long Id); 9void Insert(T Item); 10void Update(T Item); 11void Remove(long Id); 12} 13} 在接口文件夹中定义IMAUIRepository接口,仓储接口定义了增、删、查、改4个基本操作,同时定义了Exist()方法判断搜索对象是否存在。为保证封装的通用性,这里采用泛型机制,添加 where T : BaseModel 约束表明泛型对象需要继承BaseModel类。 MAUIRepository.cs代码如下: 1using MAUISDK.Interfaces; 2using MAUISDK.Models; 3 4namespace MAUISDK.Services 5{ 6public class MAUIRepository : IMAUIRepository where T : BaseModel 7{ 8public List ItemList; 9public MAUIRepository() 10{ 11ItemList = new(); 12} 13public void Remove(long Id) 14{ 15ItemList.Remove(Find(Id)); 16} 17public bool Exist(long Id) 18{ 19return ItemList.Any(item => item.Id == Id); 20} 21public T Find(long Id) 22{ 23return ItemList.FirstOrDefault(item => item.Id == Id)!; 24} 25public void Insert(T Item) 26{ 27ItemList.Add(Item); 28} 29public void Update(T Item) 30{ 31int index = ItemList.IndexOf(Find(Item.Id)); 32ItemList.RemoveAt(index); 33ItemList.Insert(index, Item); 34} 35} 36} 上述代码实现了IMAUIRepository接口,对Lambda表达式还不熟悉的读者可以参阅本书1.4节中的相关内容。仓储中的核心数据结构是ItemList 列表,用于维护一系列对象。 BaseViewModel.cs代码如下: 1using System.ComponentModel; 2using System.Runtime.CompilerServices; 3 4namespace MAUIDemo.ViewModels 5{ 6public class BaseViewModel : IQueryAttributable, INotifyPropertyChanged 7{ 8public event PropertyChangedEventHandler PropertyChanged; 9public virtual void ApplyQueryAttributes(IDictionary query) 10{ 11} 12protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 13{ 14PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 15} 16} 17} 与基本模型相对应,上述代码中定义了基本视图模型BaseViewModel。BaseViewModel继承了IQueryAttributable和INotifyPropertyChanged接口,目的是监听数据动态变化,及时通知视图层。实现了接口的OnPropertyChanged()方法,定义事件委托PropertyChangedEventHandler 调用Invoke()方法触发属性变更动作。 MAUILearningDAO.cs代码如下: 1using MAUISDK.Core; 2using MAUISDK.Models; 3using SQLite; 4 5namespace MAUIDemo.Core 6{ 7public class MAUILearningDAO 8{ 9private SQLiteAsyncConnection Database; 10public MAUILearningDAO() 11{ 12} 13public async Task LogAsync() 14{ 15await Database.EnableWriteAheadLoggingAsync(); 16} 17public async Task Init() 18{ 19if (Database is not null) 20return; 21Database = new(GlobalValues.LocalDatabasePath, GlobalValues.Flags); 22_ = await Database.CreateTableAsync(); 23} 24public async Task> GetItemsAsync() 25{ 26await Init(); 27return await Database.Table().ToListAsync(); 28} 29public async Task GetItemAsync(int Id) 30{ 31await Init(); 32return await Database.Table().Where(i => i.Id == Id).FirstOrDefaultAsync(); 33} 34public async Task SaveItemAsync(MAUILearning model) 35{ 36await Init(); 37return await Database.InsertAsync(model); 38} 39public async Task DeleteItemAsync(object primaryKey) 40{ 41await Init(); 42return await Database.DeleteAsync(primaryKey); 43} 44} 45} DAO(Data Access Objects,数据访问对象)属于面向对象中的一种设计模式,该设计模式将底层的数据访问逻辑与上层的业务逻辑进行分离。数据访问逻辑专注于数据访问相关的操作,业务逻辑专注于业务执行流程。MAUILearningDAO是针对MAUILearning类对应的数据表进行操作。内部引入SQLiteAsyncConnection Sqlite数据库异步连接对象,Init()方法完成初始化。GlobalValues.LocalDatabasePath是自定义MAUIDemo.db3文件的路径。MAUIDemo.db3文件是用于存储数据的Sqlite数据库文件,数据库相关的所有操作均会影响此文件内容。Gitee的.gitignore文件可添加 *.db3配置,屏蔽上传Sqlite数据文件。与本地文件相对应的是本地数据库缓存路径,本地数据库缓存路径是Sqlite数据库操作默认缓存文件的位置,针对不同的操作系统有一定的差异性。笔者的本地数据库缓存路径是 C:\Users\Administrator\AppData\Local\Packages\***\LocalState。 MAUILearningViewModel.cs代码如下: 1using MAUIDemo.Core; 2using MAUISDK.Models; 3using System.Collections.ObjectModel; 4using System.Windows.Input; 5 6namespace MAUIDemo.ViewModels 7{ 8public class MAUILearningViewModel : BaseViewModel 9{ 10private MAUILearningDAO dao; 11public ObservableCollection MAUILearnings 12{ 13get; 14set; 15} 16public ICommand OnSaveAsync 17{ 18set; 19get; 20} 21public ICommand OnDeleteAsync 22{ 23set; 24get; 25} 26public ICommand OnFlushAsync 27{ 28set; 29get; 30} 31public MAUILearningViewModel() 32{ 33MAUILearnings = new(); 34dao = new(); 35OnSaveAsync = new Command(async (text) => 36{ 37if (text != null && !String.IsNullOrWhiteSpace(text.ToString())) 38{ 39MAUILearning Item = new() 40{ 41Learning = text.ToString() 42}; 43await dao.SaveItemAsync(Item); 44} 45OnFlushAsync.Execute(null); 46}); 47OnDeleteAsync = new Command(async (text) => 48{ 49if (text != null) 50{ 51int del = -1; 52for (int i = 0; i < MAUILearnings.Count; i++) 53{ 54if (MAUILearnings[i].Id.Equals(long.Parse(text.ToString()))) 55{ 56del = i; 57break; 58} 59}//for 60if (del != -1) 61{ 62await dao.DeleteItemAsync(MAUILearnings[del]); 63} 64} 65OnFlushAsync.Execute(null); 66}); 67OnFlushAsync = new Command(async () => 68{ 69var items = await dao.GetItemsAsync(); 70MainThread.BeginInvokeOnMainThread(() => 71{ 72MAUILearnings.Clear(); 73foreach (var item in items) 74{ 75MAUILearnings.Add(item); 76} 77}); 78OnPropertyChanged("MAUILearnings"); 79}); 80} 81} 82} MAUILearningViewModel类继承BaseViewModel类。内部的核心成员MAUILearningDAO用于实际数据库操作。MAUILearnings列表是ObservableCollection可观察的列表,底层使用观察者设计模式。观察者设计模式也称为发布订阅模式,多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知并且会被自动更新。观察者设计模式是一种数据更新的触发机制,可以降低目标与观察者之间的耦合关系。MAUI视图模型中大量使用观察者设计模式,以保证数据的双向绑定。 MAUILearningViewModel类相关方法如下。 OnSaveAsync()。用于新增数据对象。当界面输入数据不为空时,构造MAUILearning对象,调用DAO层封装的SaveItemAsync()方法完成新增数据对象逻辑。最后调用OnFlushAsync()方法实现界面层数据刷新。 OnDeleteAsync()。用于删除数据对象。依次遍历观察者列表中的所有内容,如果满足删除条件,调用DAO层封装的DeleteItemAsync()方法完成删除数据对象逻辑。最后调用OnFlushAsync()方法实现界面层数据刷新。 OnFlushAsync()。用于刷新数据对象,同步界面层数据显示。首先调用GetItemsAsync()方法获取当前数据库中的全部对象,因为这里涉及多线程间互操作界面更新问题。多线程间互操作修改线程数据会导致线程不安全,MAUI提供了解决此问题的一种思路,采用主线程方式调用MainThread.BeginInvokeOnMainThread()方法。该方法的参数是一个委托,委托内部完成MAUILearnings 可观察数据对象的更新操作,最后调用父类的OnPropertyChanged()方法完成属性更新的通知操作。 MAUILearningConfig.cs代码如下: 1using MAUISDK.Models; 2using Microsoft.EntityFrameworkCore; 3using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 5namespace MAUISDK.Configs 6{ 7public class MAUILearningConfig : IEntityTypeConfiguration 8{ 9public void Configure(EntityTypeBuilder builder) 10{ 11builder.Property(e=> e.Learning).IsRequired(); 12} 13} 14} 上述MAUILearningConfig 配置类完成数据表的自动生成,Configure()方法完成数据表的自动创建过程。内部根据需求配置表中的各个字段,如IsRequired()表示该字段必须设置,不能为空。至此,完成了数据库相关的功能封装。 5.1.3应用调用 应用调用逻辑在SQLitePage页面演示。 【例54】Sqlite应用调用。 SQLitePage.xaml代码如下: 1 2 9 10 11 12 13 22 28 34 35 36 43 44 45 46 50 51