Кроме обычных файлов для хранения данных мы можем использовать локальные базы данных. Наиболее популярной системой баз данных для локальных приложений является SQLite, поэтому рассмотрим общие принципы работы с этой СУБД.
Для работы с SQLite можно использовать разные подходы и библиотеки. Рекомендуемой библиотекой является SQLite.NET, которая представляет простое ORM-решение (Object Relational Mapping) для Xamarin. Она позволяет работать с базой данных как с хранилищем объектов и манипулировать данными как объектами стандартных классов C# без использования выражений на языке SQL. И вначале добавим в главный проект в узел References с помощью менеджера NuGet эту библиотеку sqlite-net-pcl:
Следует отметить, что в Nuget можно найти много похожих библиотек с подобным названием. У нужной библиотеки будут следующие данные:
Автор: SQLite-net
Id: sqlite-net-pcl
Вначале определим класс, объекты которого будут храниться в базе данных. Добавим в главный проект следующий класс Friend:
using SQLite; namespace HelloApp { [Table("Friends")] public class Friend { [PrimaryKey, AutoIncrement, Column("_id")] public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Phone { get; set; } } }
Класс Friend выступает в качестве модели приложения. Этот класс использует атрибуты, которые позволяют настроить его отображение на таблицу в бд. Для настройки мы можем использовать следующие атрибуты:
[PrimaryKey]: применяется к свойству типа int
и
указывает, что столбец в таблице, который соответствует этому свойству, будет выполнять роль первичного ключа. Составные ключи не поддерживаются
[AutoIncrement]: применяется к свойству типа int
и
указывает, что столбец в таблице, который соответствует этому свойству, будет инкрементировать значение на единицу при добавлении нового элемента
[Column(name)]: задает сопоставление свойства со столбцом в таблице, который имеет имя name
[Table(name)]: устанавливает название таблицы, которая будет соотвествовать данному классу
[MaxLength(value)]: устанавливает максимальную длину для строковых свойств
[Ignore]: указывает, что свойство будет игнорироваться. Это может быть полезно, если значение данного свойства не надо хранить в базе данных, и данное свойство не должно сопоставляться со столбцами из таблицы в бд
[Unique]: гарантирует, что столбец, который соответствует свойству с этим атрибутом, будет иметь уникальные неповторяющиеся значения
При запросах к базе данных будет происходить автоматическое сопоставление типов данных из SQLite с типами данных из C#. Сопоставление типов можно описать следующей таблицей:
C# | SQLite |
int, long | integer, bigint |
bool | integer (1 = true) |
enum | integer |
float | real |
double | real |
decimal | real |
string | varchar, text |
DateTime | numeric, text |
byte[] | blob |
Также создадим класс репозитория, через который будут идти все операции с данными:
using System.Collections.Generic; using SQLite; namespace HelloApp { public class FriendRepository { SQLiteConnection database; public FriendRepository(string databasePath) { database = new SQLiteConnection(databasePath); database.CreateTable<Friend>(); } public IEnumerable<Friend> GetItems() { return database.Table<Friend>().ToList(); } public Friend GetItem(int id) { return database.Get<Friend>(id); } public int DeleteItem(int id) { return database.Delete<Friend>(id); } public int SaveItem(Friend item) { if (item.Id != 0) { database.Update(item); return item.Id; } else { return database.Insert(item); } } } }
В конструкторе класса происходит создание подключения и базы данных (если она отсутствует). В качестве параметра извне будет передаваться путь к базе данных. При желании путь к бд можно определить и в самом конструкторе.
Для всех операций с данными используются методы, определенные в классе SQLiteConnection:
Insert: добавляет объект в таблицу
Get<T>: позволяет получить элемент типа T по id
Table<T>: возвращает все объекты из таблицы
Delete<T>: удаляет объект по id
Update<T>: обновляет объект
Query<T>: выполняет SQL-выражение и возвращает строки из таблицы в виде объектов типа T (относится к выражениям SELECT)
Execute: выполняет SQL-выражение, но ничего не возвращает (относится к операциям. где не надо возвращать результат - UPDATE, INSERT, DELETE)
Если предполагается, что к базе данных может обращаться сразу несколько потоков, то для блокирования одновременных операций с бд в классе репозитории можно использовать блок lock с заглушкой, например:
SQLiteConnection database; static object locker = new object(); //...................... public int DeleteItem(int id) { lock(locker) { return database.Delete<Friend>(id); } }
Создаваемое подключение будет общим для всего приложения, поэтому изменим файл App.xaml.cs следующим образом:
using System; using System.IO; using Xamarin.Forms; namespace HelloApp { public partial class App : Application { public const string DATABASE_NAME = "friends.db"; public static FriendRepository database; public static FriendRepository Database { get { if (database == null) { database = new FriendRepository( Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), DATABASE_NAME)); } return database; } } public App() { InitializeComponent(); MainPage = new NavigationPage(new MainPage()); } protected override void OnStart() { } protected override void OnSleep() { } protected override void OnResume() { } } }
Статический объект репозитория, создаваемый при создании главной страницы приложения, будет доступен из любого места приложения.
Поскольку в нашем приложении мы будем переходить по страницам - к странице добавления или просмотра, то в качестве главной страницы устанавливается
объект NavigationPage
.
Теперь на главной странице MainPage.xaml следующий код:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage" Title="Список друзей"> <StackLayout> <ListView x:Name="friendsList" ItemsSource="{Binding}" ItemSelected="OnItemSelected"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.View> <StackLayout Orientation="Horizontal"> <Label Text="{Binding Name}" FontSize="Medium" /> </StackLayout> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button Text="Добавить" Clicked="CreateFriend" /> </StackLayout> </ContentPage>
Элемент ListView будет выводить список объектов, а при нажатии на элемент списка, будет срабатывать обработчик OnItemSelected
.
И также для добавления нового объекта определена кнопка.
И изменим файл кода MainPage.xaml.cs:
using System; using Xamarin.Forms; namespace HelloApp { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } protected override void OnAppearing() { friendsList.ItemsSource = App.Database.GetItems(); base.OnAppearing(); } // обработка нажатия элемента в списке private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) { Friend selectedFriend = (Friend)e.SelectedItem; FriendPage friendPage = new FriendPage(); friendPage.BindingContext = selectedFriend; await Navigation.PushAsync(friendPage); } // обработка нажатия кнопки добавления private async void CreateFriend(object sender, EventArgs e) { Friend friend = new Friend(); FriendPage friendPage = new FriendPage(); friendPage.BindingContext = friend; await Navigation.PushAsync(friendPage); } } }
При переходе на любую страницу у нее вызывается метод OnAppearing()
, поэтому тут мы можем установить привязку и настроить другие начальные данные.
Остальные оба обработчика - OnItemSelected и CreateFriend предусматривают переход на страницу FriendPage, которая будет отвечать за работу с одним объектом из бд.
Через свойство BindingContext
мы можем установить полученный объект в качестве контекста страницы, а на самой странице использовать выражения привязки
для изменения значений свойств объекта.
Теперь добавим эту страницу FriendPage. В ее коде xaml пропишем следующий интерфейс:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.FriendPage" Title="Информация о друге"> <StackLayout> <Label Text="Имя" /> <Entry Text="{Binding Name}" /> <Label Text="Email" /> <Entry Text="{Binding Email}" /> <Label Text="Телефон" /> <Entry Text="{Binding Phone}" /> <StackLayout Orientation="Horizontal"> <Button Text="Сохранить" Clicked="SaveFriend" /> <Button Text="Удалить" Clicked="DeleteFriend" /> <Button Text="Отмена" Clicked="Cancel" /> </StackLayout> </StackLayout> </ContentPage>
А в файле связанного кода FriendPage.xaml.cs добавим обработчики нажатия кнопок:
using System; using Xamarin.Forms; namespace HelloApp { public partial class FriendPage : ContentPage { public FriendPage() { InitializeComponent(); } private void SaveFriend(object sender, EventArgs e) { var friend = (Friend)BindingContext; if (!String.IsNullOrEmpty(friend.Name)) { App.Database.SaveItem(friend); } this.Navigation.PopAsync(); } private void DeleteFriend(object sender, EventArgs e) { var friend = (Friend)BindingContext; App.Database.DeleteItem(friend.Id); this.Navigation.PopAsync(); } private void Cancel(object sender, EventArgs e) { this.Navigation.PopAsync(); } } }
Обработчики используют методы репозитория для сохранения и удаления объекта и после этого осуществляют переход назад на главную страницу.
В итоге главный проект будет выглядеть следующим образом:
Теперь с главной страницы мы можем попасть на страницу добавления и создать там новые объекты:
После добавления все объекты будут отображаться в списке на главной странице: