Доступ к данным в .NET MAUI. SQLite и EF Core в .NET MAUI

В предыдущей части мы рассмотрели работу с SQLite в .NET MAUI с использованием библиотеки SQLite.NET. В этой части мы рассмотрим использование SQLite с технологией Microsoft Entity Framework Core (EF Core). Использование SQLite и EF Core в .NET MAUI имеет свои особенности и, чтобы их продемонстрировать, мы повторим приложение из предыдущей части, но уже с использованием EF Core.

Создание проекта и установка необходимых пакетов

Создадим новый проект .NET MAUI, добавим в проект папку Models, в которой разместим класс Project:

public class Project
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Author { get; set; }
}

Теперь установим в проект следующие NuGet-пакета:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Sqlite

Теперь настроим наш проект .NET MAUI и EF Core для работы с моделью, которая представлена в проекте классом Project.

Работа с SQLite и EF Core в .NET MAUI

Для того, чтобы наш проект мог взаимодействовать в БД SQLite с использованием EF Core мы должны создать контекст базы данных и зарегистрировать его в качестве сервиса в нашем приложении. Добавим в проект папку Services и разместим в ней класс ApplicationContext:

using MauiEfCore.Models;

using Microsoft.EntityFrameworkCore;

namespace MauiEfCore.Services
{
    public class ApplicationContext : DbContext
    {
        public DbSet<Project> Projects { get; set; }

        public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
        {
            Database.EnsureDeleted();
            Database.EnsureCreated();
        }
    }
}

В конструкторе ApplicationContext мы удаляем и создаем заново базу данных SQLite. Для отладки приложения — это вполне рабочий вариант, но в рабочем проекте лучше использовать миграции о которых мы обязательно поговорим в контексте .NET MAUI.

Теперь зарегистрируем наш контекст базы данных SQLite в качестве сервиса. Для этого перейдем в файл MauiProgram.cs и изменим его следующим образом:

using Microsoft.Extensions.Logging;
using MauiEfCore.Services;
using MauiEfCore.ViewModels;
using Microsoft.EntityFrameworkCore;
namespace MauiEfCore;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        builder.Services.AddDbContext<ApplicationContext>(options => { options.UseSqlite("Data Source=databse.dat"); });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

Здесь мы регистрируем контекст базы данных как сервис, используя метод расширения IServiceCollection AddDbContext, указав в качестве параметра метода настройки подключения к базе:

builder.Services.AddDbContext<ApplicationContext>(options => 
{ 
    options.UseSqlite("Data Source=databse.dat"); 
});

На этом настройку EF Core в нашем приложении .NET MAUI можно считать законченной. Остается создать модель представления и подключить ей к странице приложения.

Организация доступа к данным SQLite с EF Core в .NET MAUI

Создадим в проекте новую папку ViewModels и разместим в ней класс MainPageViewModel:

Код класса будет следующим:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

using MauiEfCore.Services;
using MauiEfCore.Models;
using Microsoft.EntityFrameworkCore;

namespace MauiEfCore.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {

        private readonly ApplicationContext _database;

        public ObservableCollection<Project> Projects { get; set; } = [];

        private string _name = string.Empty;

        public string Name
        {
            get => _name;
            set
            {
                if (_name == value) return;
                _name = value;
                OnPropertyChanged();
            }
        }

        private string _description = string.Empty;
        public string Description
        {
            get => _description;
            set
            {
                if (_description == value) return;
                _description = value;
                OnPropertyChanged();
            }
        }

        private string _author = string.Empty;
        public string Author
        {
            get => _author;
            set
            {
                if (_author == value) return;
                _author = value;
                OnPropertyChanged();
            }
        }

        private DateTime _deadline = DateTime.Now;
        public DateTime Deadline
        {
            get => _deadline;
            set
            {
                if (_deadline == value) return;
                _deadline = value;
                OnPropertyChanged();
            }
        }

        public ICommand AddCommand { get; set; }
        public ICommand UpdateCommand { get; set; }
        public ICommand DeleteCommand { get; set; }
        public ICommand ReadCommand { get; set; }

        public ICommand SelectedItemCommand { get; set; }

        public MainPageViewModel(ApplicationContext database)
        {
            _database = database;

            AddCommand = new Command(async () =>
            {
                Project project = new()
                {
                    Name = this.Name,
                    Description = this.Description,
                    Author = this.Author
                };

                await _database.Projects.AddAsync(project);
                int result = await _database.SaveChangesAsync();

                if (result == 0)
                    throw new Exception("Ошибка добавления новой записи в базу данных");

                Projects.Add(project);

                Name = string.Empty;
                Description = string.Empty;
                Author = string.Empty;
            });

            UpdateCommand = new Command<Project>(async (Project) =>
            {
                Project.Name = this.Name;
                Project.Description = this.Description;
                Project.Author = this.Author;

                _database.Projects.Update(Project);
                int result = await _database.SaveChangesAsync();
                
                if (result == 0)
                    throw new Exception("Ошибка обновления записи в базе данных");

                Projects.Remove(Project);
                ReadCommand?.Execute(null);
            },
            (Param) => { return Param != null; });

            DeleteCommand = new Command<Project>(async (Project) =>
            {
                _database.Projects.Remove(Project);
                int result = await _database.SaveChangesAsync();
                Projects.Remove(Project);
                if (result == 0)
                    throw new Exception("Ошибка удаления записи из базы данных");
            },
            (Param) => { return Param != null; });

            ReadCommand = new Command(async () =>
            {
                var list = await _database.Projects.ToArrayAsync();
                Projects.Clear();
                foreach (var item in list)
                    Projects.Add(item);
                OnPropertyChanged(nameof(Projects));
            });

            SelectedItemCommand = new Command<Project>((Project) =>
            {
                Name = Project.Name;
                Description = Project.Description;
                Author = Project.Author;
            },
            (Project) => { return Project != null; });
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string prop = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        }
    }
}

Наша модель представления содержит необходимые свойства создания нового или изменения существующего объекта Project:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

using MauiEfCore.Services;
using MauiEfCore.Models;
using Microsoft.EntityFrameworkCore;

namespace MauiEfCore.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Project> Projects { get; set; } = [];

        private string _name = string.Empty;
        public string Name
        {
            get => _name;
            set
            {
                if (_name == value) return;
                _name = value;
                OnPropertyChanged();
            }
        }

        private string _description = string.Empty;
        public string Description
        {
            get => _description;
            set
            {
                if (_description == value) return;
                _description = value;
                OnPropertyChanged();
            }
        }

        private string _author = string.Empty;
        public string Author
        {
            get => _author;
            set
            {
                if (_author == value) return;
                _author = value;
                OnPropertyChanged();
            }
        }

        private DateTime _deadline = DateTime.Now;
        public DateTime Deadline
        {
            get => _deadline;
            set
            {
                if (_deadline == value) return;
                _deadline = value;
                OnPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler? PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string prop = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        }
    }
}

Для уведомления об изменениях свойств модель представления реализует интерфейс INotifyPropertyChanged. В конструкторе мы запрашиваем наш контекст базы данных:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

using MauiEfCore.Services;
using MauiEfCore.Models;
using Microsoft.EntityFrameworkCore;

namespace MauiEfCore.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged
    {

        private readonly ApplicationContext _database;

        ...

        public MainPageViewModel(ApplicationContext database)
        {
            _database = database;

            ...
        }
    }
}

для работы с базой данных мы также определили следующие команды:

SelectedItemCommand — обновляет свойства, при выборе объекта в списке

SelectedItemCommand = new Command<Project>((Project) =>
{
    Name = Project.Name;
    Description = Project.Description;
    Author = Project.Author;
},
(Project) => { return Project != null; });

ReadCommand — обновляет список проектов в модели представления

ReadCommand = new Command(async () =>
{
    var list = await _database.Projects.ToArrayAsync();
    Projects.Clear();
    foreach (var item in list)
        Projects.Add(item);
});

DeleteCommand — удаляет проект из базы данных и списка Projects

DeleteCommand = new Command<Project>(async (Project) =>
{
    _database.Projects.Remove(Project);
    int result = await _database.SaveChangesAsync();
    Projects.Remove(Project);
    if (result == 0)
        throw new Exception("Ошибка удаления записи из базы данных");
},
(Param) => { return Param != null; });

UpdateCommand — обновляет проект, используя свойства модели представления

UpdateCommand = new Command<Project>(async (Project) =>
{
    Project.Name = this.Name;
    Project.Description = this.Description;
    Project.Author = this.Author;

    _database.Projects.Update(Project);
    int result = await _database.SaveChangesAsync();
    
    if (result == 0)
        throw new Exception("Ошибка обновления записи в базе данных");

    Projects.Remove(Project);
    ReadCommand?.Execute(null);
},
(Param) => { return Param != null; });

AddCommand — создает новый проект, используя свойства модели представления

AddCommand = new Command(async () =>
{
    Project project = new()
    {
        Name = this.Name,
        Description = this.Description,
        Author = this.Author
    };

    await _database.Projects.AddAsync(project);
    int result = await _database.SaveChangesAsync();

    if (result == 0)
        throw new Exception("Ошибка добавления новой записи в базу данных");

    Projects.Add(project);

    Name = string.Empty;
    Description = string.Empty;
    Author = string.Empty;
});

Обратите внимание на то, что при работе с SQLite и EF Core в .NET MAUI, везде где требуется изменение данных мы используем метод SaveChangesAsync(). Например, при создании новой записи в БД:

await _database.Projects.AddAsync(project); //добавляем новый элемент
int result = await _database.SaveChangesAsync();//сохраняем изменения

метод SaveChangesAsync() сохраняет изменения в базе данных и возвращает количество строк, измененных в результате выполнения запроса.

Чтобы наша модель представления могла получать в качестве сервиса контекст базы данных, необходимо её также зарегистрировать как сервис. Добавим в MauiProgram.cs следующую строку:

builder.Services.AddSingleton<MainPageViewModel>();

Теперь нам необходимо использовать полученную модель представления на странице MainPage.

Привязка свойств и команд модели представления при использовании SQLite и EF Core в .NET MAUI

Изменим код страницы MainPage следующим образом:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiEfCore.MainPage"
             xmlns:local="clr-namespace:MauiEfCore.ViewModels"
             xmlns:models="clr-namespace:MauiEfCore.Models"
             x:DataType="local:MainPageViewModel">

    <ScrollView>
        <VerticalStackLayout
            Padding="30,0"
            Spacing="25">

            <VerticalStackLayout Padding="15" Margin="15">
                <Label Text="Название проекта" />
                <Entry Text="{Binding Name}" />
                <Label Text="Описание" />
                <Entry Text="{Binding Description}" />
                <Label Text="Автор" />
                <Entry Text="{Binding Author}" />

                <CollectionView x:Name="ProjectsView" 
                                SelectionMode="Single" 
                                ItemsSource="{Binding Projects, Mode=TwoWay}" 
                                Margin="15" 
                                SelectionChangedCommand="{Binding SelectedItemCommand}"
                                SelectionChangedCommandParameter="{Binding  Source={RelativeSource Mode=Self},Path=SelectedItem}">
                    <CollectionView.ItemTemplate>
                        <DataTemplate x:DataType="{x:Type models:Project}">
                            <Border StrokeShape="Rectangle" Background="LightGreen">
                                <VerticalStackLayout>
                                    <Label Text="{Binding Name}" FontSize="16" FontAttributes="Bold" TextColor="Blue"/>
                                    <Label Text="{Binding Id}"/>
                                    <Label Text="{Binding Description}"/>
                                    <Label Text="{Binding Author}"/>
                                </VerticalStackLayout>
                            </Border>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>

            </VerticalStackLayout>

            <HorizontalStackLayout HorizontalOptions="Center">
                <Button 
                    Margin="10"
                    Text="Обновить список" 
                    Command="{Binding ReadCommand}" />
                <Button 
                    Margin="10"
                    Text="Добавить"
                    Command="{Binding AddCommand}"
                    CommandParameter="{Binding Source={x:Reference ProjectsView}, Path=SelectedItem}"/>
                <Button 
                    Margin="10"
                    Text="Удалить" 
                    Command="{Binding DeleteCommand}" 
                    CommandParameter="{Binding Source={x:Reference ProjectsView}, Path=SelectedItem}"/>
                <Button 
                    Margin="10"
                    Text="Изменить" 
                    Command="{Binding UpdateCommand}" 
                    CommandParameter="{Binding Source={x:Reference ProjectsView}, Path=SelectedItem}"/>

            </HorizontalStackLayout>


        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Здесь мы установили необходимые привязки к командам и свойствам модели представления, как мы это делали ранее. Остается только передать нашу модель представления на страницу. Для этого изменим код MainPage.xaml.cs следующим образом:

using MauiEfCore.ViewModels;

namespace MauiEfCore
{
    public partial class MainPage : ContentPage
    {
        public MainPage(MainPageViewModel model)
        {
            InitializeComponent();
            BindingContext = model;
        }
    }
}

Проверим работу приложения

SQLite и EF Core в .NET MAUI

Итого

Для использования SQLite и EF Core в .NET MAUI нам необходимо установить в проект необходимые NuGet-пакеты, создать контекст базы данных и подключить его в проект в качестве сервиса.  качестве примера мы разработали небольшое приложение, использующее базу данных SQLite для хранения данных о проектах. Наше приложение использует шаблон MVVM и содержит модель, модель представления и представление.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии