Xamarin.Forms MVVM ViewModel 如何与数据库交互?

Xamarin.Forms MVVM How does the ViewModel Interact with the Database?

我对 MVVM 模式完全陌生,我已经到处寻找有关使用 MVVM 和 SQLite 的信息,但我仍然完全不知所措。我有一个项目有多个模型,这些模型在我的数据库中填充 tables。我有一个数据库服务,可以为我的 table 添加、编辑、设置和获取对象。 ViewModel 有一个 ObservableCollection 对象,它们出现在视图中,但它是如何在我的数据库中更新的?所以基本上,我不想在 ViewModel 中填充我的 ObservableCollection。我想从数据库中相应的 table 填充它。我只是不明白,如果 ViewModel 的 ObservableCollection 正在从 View 更新,那么什么会更新我的数据库中的信息?要添加,get 方法在我的数据库服务 returns table ToList 中。我考虑尝试在我的 ViewModel 中做一些事情,比如将 ObservableCollection 的 属性 设置为我的 get 方法,但这不起作用,因为一个是列表,一个是 ObservableCollection。对此的任何想法或建议都会非常有帮助。谢谢大家

如果我理解这个问题,您正在寻找数据库和您的视图绑定到的 ObservableCollection 之间的 two-way 粘合剂。我记得在那个“完全迷失”的地方,想要用简单的术语从头开始解释,并决定 post 稍微 longer-than-usual 回答,因为这对我有帮助。

如果您阅读了,我将解释如何插入、更新和删除记录以及查询 SQLite 本地数据库,在 ListView 中显示返回的记录集,如下所示:

一开始您的视图绑定可能是这样的:

<StackLayout>
    <ListView ItemsSource="{Binding Recordset}" />
    <Grid>
        <Button Grid.Column="0"
                Text="Query" 
                Command="{Binding QueryCommand}"/>
        <Button Grid.Column="1"
                Text="Clear"
                Command="{Binding ClearCommand}" />
    </Grid>
</StackLayout>

BindingContext 设置如下:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        BindingContext = new MainPageBinding();
        InitializeComponent();
    }
}
class MainPageBinding : INotifyPropertyChanged
{
    public ObservableCollection<Record> Recordset { get; } = 
        new ObservableCollection<Record>();
    .
    .
    .
}

这将显示一个 Record/Model,看起来类似于:

[Table("items")]
class Record
{
    [PrimaryKey]
    public string guid { get; set; } = Guid.NewGuid().ToString().Trim().TrimStart('{').TrimEnd('}');
    public string Description { get; set; } = string.Empty;
    public override string ToString() => Description;
}

此时,我们放入 Recordset 中的每个新记录都会显示在视图中。具体来说,您想通过执行 SQLite 查询来创建这些记录。

public ICommand QueryCommand { get; private set; }
private void OnQuery(object o)
{
    Recordset.Clear();
    List<Record> queryResult;
    using (var cnx = new SQLiteConnection(mockConnectionString))
    {
        queryResult = cnx.Query<Record>("SELECT * FROM items");
        foreach (var record in queryResult)
        {
            Recordset.Add(record);
        }
    }
}

现在这只是一种方法,对吧?但我希望它能给你一些想法。如果您想试验一下,我已将我的工作示例上传到 GitHub

现在换个方向。

(这将回答问题的一部分 如果 ViewModel 的 ObservableCollection 正在从 View 更新,那么什么会更新我的数据库中的信息?)

假设弹出一个描述编辑器,您可以编辑 SelectedItem。提交后,它会设置 SQLite 记录的 Description 属性。

我们可以像这样模拟这种交互:

    public ICommand EditCommand { get; private set; }
    private void OnEdit(object o)
    {
        if(SelectedItem != null)
        {
            // Some kind of UI interaction that changes the bound record
            SelectedItem.Description = $"{SelectedItem.Description} (Edited)";

            // You'll need to decide what kind of UI action merits a SQL
            // update, but when you're ready to do that here's the command:
            using (var cnx = new SQLiteConnection(mockConnectionString))
            {
                cnx.Update(SelectedItem);
            }
        }
    }

与此同时,我们修改了 Record 以实现 INotifyPropertyChanged...

[Table("items")]
class Record : INotifyPropertyChanged
{
    [PrimaryKey]
    public string guid { get; set; } = Guid.NewGuid().ToString().Trim().TrimStart('{').TrimEnd('}');

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    string _Description = string.Empty;
    public string Description
    {
        get => _Description;
        set
        {
            if(_Description != value)
            {
                _Description = value;
                OnPropertyChanged();
            }
        }
    }
    public override string ToString() => Description;
} 

...并且由于我们希望视图更新 不仅对集合的更改而且对单个属性的编程更改 我们需要一个数据模板 xaml 现在可以正确显示绑定说明 属性:

<StackLayout>
    <ListView ItemsSource="{Binding Recordset}"
              SelectedItem="{Binding SelectedItem}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <Grid>
                        <Label Text="{Binding Description}" />
                    </Grid>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <Grid>
        <Button Grid.Column="0"
                Text="Query" 
                Command="{Binding QueryCommand}"/>
        <Button Grid.Column="1"
                Text="Clear"
                Command="{Binding ClearCommand}" />
        <Button Grid.Column="2"
                Text="Edit"
                Command="{Binding EditCommand}" />
    </Grid>
</StackLayout>

好的!所以 Select:

单击“编辑”按钮:

单击“清除”按钮:

现在单击“查询”按钮以确认数据库已更新。

INSERT 和 DELETE 操作

添加新记录:

    public ICommand AddCommand { get; private set; }
    private async void OnAdd(object o)
    {
        var result = await App.Current.MainPage.DisplayPromptAsync("New Item", "Describe the item");
        if(!string.IsNullOrWhiteSpace(result))
        {
            var newRecord = new Record { Description = result };
            Recordset.Add(newRecord);

            using (var cnx = new SQLiteConnection(mockConnectionString))
            {
                cnx.Insert(newRecord);
            }
        }
    }

要删除:

    public ICommand DeleteSelectedCommand { get; private set; }
    private void OnDeleteSelected(object o)
    {
        if (SelectedItem != null)
        {
            var removed = SelectedItem;
            Recordset.Remove(SelectedItem);
            using (var cnx = new SQLiteConnection(mockConnectionString))
            {
               cnx.Delete(removed);
            }
        }
    }

搜索栏

要全面回答 ViewModel 如何与数据库交互? 还需要一件事:主页上的搜索栏,以便我们可以在新数据库:这是代码 post 在 GitHub 上编辑的最终版本。

搜索:

public ICommand SearchCommand { get; }
private void OnSearch(string expr)
{
    Recordset.Clear();
    List<Record> queryResult;
    using (var cnx = new SQLiteConnection(mockConnectionString))
    {
        queryResult = cnx.Query<Record>($"SELECT * FROM items WHERE Description LIKE'%{expr}%'");
        foreach (var record in queryResult)
        {
            Recordset.Add(record);
        }
    }
}