读写数据库

如何在Avalonia中建立数据库连接,如何在数据库中插入数据

  • Mvvm设计模式:Model、ViewModel、View 客户端开发的一种设计模式(MVC是服务端开发的设计模式)

从Model开始,添加新类作为模型类

1
2
3
4
5
public class Poetry {
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}

service层,添加新类作为服务类 接口IPoetryStorage,实现类PoetryStorage

1
2
3
public interface IPoetryStorage {
Task InsertAsync(Poetry poetry);
}

这是一个异步操作,函数名以Async结尾,返回值是Task,参数是Poetry类型的对象
异步操作:不会阻塞当前线程,而是在另一个线程上执行,执行完毕后会通知当前线程

什么时候用异步操作? 当操作比较耗时的时候,比如读写文件、读写数据库、网络请求

1
2
3
4
public class PoetryStorage : IPoetryStorage {
public async Task InsertAsync(Poetry poetry){
throw new NotImplementedException();
}
  • .NET代码优先,code first,怎么把数据库表建起来? 根据模型类建立数据库表
  • 运行在服务器,数据库在服务器上,但是我们的代码运行在用户的本机上,怎么让我们的代码和数据库建立连接?

嵌入式数据库,SQLite 行业标准 程序的一部分,不需要安装,不需要配置,不需要启动

  • 通过NuGet安装sqlite-net-pcl
  • 确定数据库怎么找,先确定数据库的文件名,在PoetryStorage类中添加一个常量
    1
    public const string DbName = "poetrydb.sqlite3";
  • 重点问题:放在哪?放在哪个目录下? 放用户总目录下,有一个位置专门存放应用程序

新建Helpers文件夹,添加一个新类PathHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static class PathHelper {
private static string _localFolder = string.Empty;

// 懒初始化,只有在需要的时候才初始化
private static string LocalFolder {
get
{
if (!string.IsNullOrEmpty(_localFolder)) {
return _localFolder;
}

// 专门存放应用程序的位置
_localFolder =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder
.LocalApplicationData), nameof(Mvvm)); // 获取物理路径

// nameof(Mvvm)获取Mvvm的字符串名称

if (!Directory.Exists(_localFolder)) {
Directory.CreateDirectory(_localFolder);
}

return _localFolder;
}
}

// 给定一个文件名,返回文件的完整路径
public static string GetLocalFilePath(string fileName) {
return Path.Combine(LocalFolder, fileName); // 拼接路径
}
}

回到PoetryStorage类,可以获取数据库文件的完整路径

1
2
public static readonly string PoetryDbPath =
PathHelper.GetLocalFilePath(DbName);
  • public const string 和 public static readonly string 的区别?
    • public const string 是常量,编译时就确定了值,不能修改
    • public static readonly string 是只读的,运行时确定值,可以修改

建立数据库连接

1
2
3
4
5
private SQLiteAsyncConnection _connection;

private SQLiteAsyncConnection Connection =>
_connection ??= new SQLiteAsyncConnection(PoetryDbPath);

  • ??= 是什么意思? 空合并运算符,左边的值为空时,才会计算右边的值

建立数据库表

  • 现在IPoetryStorage接口中添加一个新方法
    1
    Task InitializeAsync();
  • 在PoetryStorage类中实现这个方法,根据模型类建立数据库表,不用写SQL语句
    1
    2
    3
    public async Task InitializeAsync() {
    await Connection.CreateTableAsync<Poetry>();
    }

初探MVVM

通过像数据库中插入数据,来学习MVVM设计模式,Avolonia是如何把UI连起来的

实现插入诗词的功能,在PoetryStorage类中实现InsertAsync方法 (Service层)

1
2
3
public async Task InsertAsync(Poetry poetry) {
await Connection.InsertAsync(poetry);
}
  • 软件是分层的,每一层都有自己的职责,UI层(View)只负责显示,业务逻辑层(ViewModel)负责业务逻辑,Service层负责数据存取,Model层负责数据结构

向上一层,添加一个新类PoetryViewModel

ViewModel中的属性,是UI中的数据源,UI中的控件绑定到ViewModel中的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public partial class MainWindowViewModel : ViewModelBase {
private readonly IPoetryStorage _poetryStorage;

// 为什么一定用接口?

// 用构造方法的方式注入依赖
// new一个MainWindowViewModel对象时,必须传入一个IPoetryStorage类型的对象
public MainWindowViewModel(IPoetryStorage poetryStorage) {
_poetryStorage = poetryStorage;
}

private string _message;

public string Message {
get => _message;
set => SetProperty(ref _message, value);
// ref 引用传递,传递的是变量的地址
}

public void SayHello() {
Message = "Hello, Avalonia!";
}
// 谁来调用SayHello方法? 由UI层调用

}

基本CURD

单元测试

Mock

Mock2

Behaviors

无限滚动及其原理

MVVM

访问Json Web服务

MVVM + IService架构

导航的原理

Avalonia.Samples-main\src\Avalonia.Samples\Routing\BasicViewLocatorSample

从导航的按钮出发 MainWindow.axaml

1
<Button Command="{Binding NavigateNextCommand}" Content="Next" />
1
NavigateNextCommand = ReactiveCommand.Create(NavigateNext, canNavNext);
1
2
3
4
5
6
7
8
private void NavigateNext()
{
// get the current index and add 1
var index = Pages.IndexOf(CurrentPage) + 1;

// /!\ Be aware that we have no check if the index is valid. You may want to add it on your own. /!\
CurrentPage = Pages[index];
}

Pages和CurrentPage是什么?

1
2
3
4
5
public PageViewModelBase CurrentPage
{
get { return _CurrentPage; }
private set { this.RaiseAndSetIfChanged(ref _CurrentPage, value); }
}
  • CurrentPage是PageViewModelBase类型的对象,继承于ViewModelBase,也就是说CurrentPage是一个ViewModel
    1
    2
    3
    4
    5
    6
    7
    // A read.only array of possible pages
    private readonly PageViewModelBase[] Pages =
    {
    new FirstPageViewModel(),
    new SecondPageViewModel(),
    new ThirdPageViewModel()
    };
  • Pages是一个数组,数组中的元素是PageViewModelBase类型的对象,也就是说Pages是一个ViewModel数组
  • 怎么做到把ViewModel一改变,View就跟着改变?

在MainWindow.axaml中,有一个TransitioningContentControl控件,但只是用来呈现内容的,不是用来导航的

1
<TransitioningContentControl Content="{Binding CurrentPage}" />

有一个抽象的机制ViewLocater

这里有一个函数Match,每次对按钮赋值Content的时候,都会调用这个函数

1
2
3
4
public bool Match(object? data)
{
return data is ViewModelBase;
}
  • 打了三次断点:Back、Next、FirstPageViewModel
  • 这个函数用来判断当前的ViewModel是否匹配,如果匹配就返回true,否则返回false
  • 返回true之后跳到了上面的方法Build
  • 也就是说只有content的赋值为ViewModelBase子类的时候,才会调用Build方法
    1
    var name = data.GetType().FullName!.Replace("ViewModel", "View");
  • 通过ViewModel的类型,找到对应的View的类型
  • 通过反射,找到View的类型,然后实例化View
    1
    2
    3
    4
    5
    var type = Type.GetType(name);
    if (type != null)
    {
    return (Control)Activator.CreateInstance(type)!;
    }
  • 返回一个View的实例给了MainWindow.axaml中的TransitioningContentControl控件的Content