Jokaisen mobiilisovelluksen täytyy jossain vaiheessa tallentaa tietoa paikallisesti — käyttäjätietoja, asetuksia, välimuistia tai offline-dataa. Ja kun puhutaan paikallisesta tietokannasta .NET MAUI -maailmassa, SQLite on käytännössä se ainoa vakavasti otettava vaihtoehto. Se on kevyt, luotettava ja toimii jokaisella alustalla ilman erillisiä palvelimia.
Tässä oppaassa käyn läpi kaiken tarvittavan: NuGet-paketista asynkronisiin CRUD-operaatioihin, MVVM-integraatioon ja offline-first-suunnittelun perusteisiin. Koodiesimerkit ovat valmiita copy-paste-käyttöön.
Jos olet jo lukenut Shell-navigointi-oppaamme, tämä artikkeli täydentää sitä antamalla sovelluksellesi pysyvän tietovaraston. Yhdessä ne muodostavat vankan perustan .NET MAUI -sovellukselle.
Miksi SQLite on paras valinta paikalliseksi tietokannaksi?
SQLite on maailman käytetyin tietokantamoottori — ja sen suosio mobiilipuolella ei ole sattumaa. Se on sisäänrakennettu sekä Androidiin että iOS:ään, eikä vaadi erillistä palvelinta. Tietokanta on yksinkertaisesti tiedosto laitteen tallennustilassa.
- Kevyt ja nopea — Koko moottori on alle 600 kilotavua, ja paikallinen data on jopa 90 % nopeampaa kuin palvelinkutsut.
- Alustariippumaton — Sama tietokanta toimii Androidissa, iOS:ssä, Windowsissa ja macOS:ssä.
- Luotettava — ACID-yhteensopivat transaktiot takaavat tietojen eheyden.
- Ei erillistä asennusta — Ei tarvitse asentaa tai konfiguroida tietokantapalvelinta.
- Hyvä .NET-integraatio — Saatavilla useita laadukkaita NuGet-paketteja.
Rehellisesti sanottuna, olen kokeillut muitakin vaihtoehtoja (LiteDB, Realm), mutta SQLite on edelleen se, johon palaan aina takaisin. Ekosysteemi on yksinkertaisesti liian hyvä.
Projektin valmistelu: NuGet-paketin asennus
Ensimmäinen askel on asentaa sqlite-net-pcl NuGet-paketti. Nimestä huolimatta se toimii täysin .NET MAUI:n kanssa — kyseessä on Frank Kruegerin ylläpitämä, laajasti käytetty kirjasto.
dotnet add package sqlite-net-pcl
dotnet add package SQLitePCLRaw.bundle_green
SQLitePCLRaw.bundle_green varmistaa, että SQLiten natiivikirjastot latautuvat oikein kaikilla alustoilla. Ilman tätä pakettia törmäät lähes varmasti ajoaikaisiin virheisiin iOS:ssä ja Androidissa. Älä ohita tätä vaihetta.
Vakioiden ja tietokantapolun määrittely
Luo projektiisi Constants-luokka, joka keskittää tietokannan konfiguraation yhteen paikkaan:
public static class Constants
{
public const string DatabaseFilename = "app_data.db3";
public const SQLiteOpenFlags Flags =
SQLiteOpenFlags.ReadWrite |
SQLiteOpenFlags.Create |
SQLiteOpenFlags.SharedCache;
public static string DatabasePath =>
Path.Combine(
FileSystem.AppDataDirectory,
DatabaseFilename);
}
Käytetyt liput tarkoittavat seuraavaa:
- ReadWrite — Tietokantaa voidaan sekä lukea että kirjoittaa.
- Create — Tietokanta luodaan automaattisesti, jos sitä ei ole.
- SharedCache — Jaettu välimuisti parantaa suorituskykyä.
FileSystem.AppDataDirectory palauttaa sovelluksen yksityisen tallennushakemiston jokaisella alustalla. Tämä on turvallinen paikka tietokannalle, koska muut sovellukset eivät pääse siihen käsiksi.
Tietomallin luominen
SQLite.NET käyttää attribuutteja taulujen ja sarakkeiden määrittelyyn. Rakennetaan esimerkkinä yksinkertainen tehtäväsovelluksen tietomalli:
using SQLite;
public class TodoItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(200)]
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? CompletedAt { get; set; }
}
Tässä ei ole mitään yllättävää, jos olet käyttänyt ORM-kirjastoja aiemmin. [PrimaryKey, AutoIncrement] määrittää perusavaimen automaattisella kasvulla, ja [MaxLength(200)] rajoittaa merkkijonon pituutta. Jälkimmäinen on valinnainen, mutta suosittelen sitä — se auttaa pitämään tietokannan siistinä.
Tietokantapalvelun rakentaminen asynkronisesti
Tietokantaoperaatiot tulisi aina suorittaa asynkronisesti, jotta käyttöliittymä pysyy responsiivisena. Tässä on tietokantapalvelu, joka käyttää asynkronista lazy-alustusta:
using SQLite;
public class DatabaseService
{
private SQLiteAsyncConnection? _database;
private async Task InitAsync()
{
if (_database is not null)
return;
_database = new SQLiteAsyncConnection(
Constants.DatabasePath,
Constants.Flags);
// Ota WAL-tila käyttöön parempaa suorituskykyä varten
await _database.EnableWriteAheadLoggingAsync();
// Luo taulu, jos sitä ei ole
await _database.CreateTableAsync<TodoItem>();
}
// CREATE - Lisää uusi tehtävä
public async Task<int> AddItemAsync(TodoItem item)
{
await InitAsync();
return await _database!.InsertAsync(item);
}
// READ - Hae kaikki tehtävät
public async Task<List<TodoItem>> GetAllItemsAsync()
{
await InitAsync();
return await _database!.Table<TodoItem>()
.OrderByDescending(i => i.CreatedAt)
.ToListAsync();
}
// READ - Hae yksittäinen tehtävä
public async Task<TodoItem?> GetItemByIdAsync(int id)
{
await InitAsync();
return await _database!.Table<TodoItem>()
.Where(i => i.Id == id)
.FirstOrDefaultAsync();
}
// READ - Hae vain keskeneräiset tehtävät
public async Task<List<TodoItem>> GetPendingItemsAsync()
{
await InitAsync();
return await _database!.Table<TodoItem>()
.Where(i => !i.IsCompleted)
.OrderBy(i => i.CreatedAt)
.ToListAsync();
}
// UPDATE - Päivitä olemassa oleva tehtävä
public async Task<int> UpdateItemAsync(TodoItem item)
{
await InitAsync();
return await _database!.UpdateAsync(item);
}
// DELETE - Poista tehtävä
public async Task<int> DeleteItemAsync(TodoItem item)
{
await InitAsync();
return await _database!.DeleteAsync(item);
}
// DELETE - Poista kaikki valmiit tehtävät
public async Task<int> DeleteCompletedItemsAsync()
{
await InitAsync();
return await _database!.ExecuteAsync(
"DELETE FROM TodoItem WHERE IsCompleted = 1");
}
}
Miksi asynkroninen lazy-alustus?
Huomaa, miten jokainen metodi kutsuu InitAsync()-metodia ensimmäisenä. Tämä malli takaa, että tietokantayhteys on aina valmiina, mutta sitä ei luoda turhaan sovelluksen käynnistyessä. Ensimmäinen kutsu luo yhteyden, ja kaikki seuraavat ohittavat alustuksen null-tarkistuksen ansiosta.
Käytännössä tämä tarkoittaa sitä, ettei sovelluksen käynnistyminen hidastu tietokannan alustamisen takia. Iso juttu varsinkin mobiilissa.
Write-Ahead Logging (WAL)
WAL-tila on SQLiten edistyneempi kirjoitusmekanismi. Perinteisessä tilassa muutokset kirjoitetaan suoraan tietokantatiedostoon, mutta WAL-tilassa ne tallennetaan ensin erilliseen lokitiedostoon. Tämä mahdollistaa samanaikaiset luku- ja kirjoitusoperaatiot ilman lukituksia — mikä on aika merkittävä etu mobiilisovelluksessa.
Riippuvuuksien rekisteröinti (Dependency Injection)
.NET MAUI:ssa riippuvuuksien injektointi on sisäänrakennettu ominaisuus. Rekisteröi tietokantapalvelu MauiProgram.cs-tiedostossa:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Rekisteröi tietokantapalvelu singletonina
builder.Services.AddSingleton<DatabaseService>();
// Rekisteröi ViewModelit
builder.Services.AddTransient<TodoListViewModel>();
builder.Services.AddTransient<TodoDetailViewModel>();
// Rekisteröi sivut
builder.Services.AddTransient<TodoListPage>();
builder.Services.AddTransient<TodoDetailPage>();
return builder.Build();
}
}
Elinaikoihin liittyvä tärkeä huomio:
- AddSingleton —
DatabaseServicerekisteröidään singletonina, koska haluamme vain yhden tietokantayhteyden koko sovelluksen elinajan. Tämä on tärkeää — useampi yhteys samaan SQLite-tiedostoon aiheuttaa ongelmia. - AddTransient — ViewModelit ja sivut rekisteröidään transienttina, koska jokainen navigointi luo uuden instanssin.
MVVM-arkkitehtuurin integrointi CommunityToolkit.Mvvm:llä
MVVM-malli erottaa käyttöliittymälogiikan liiketoimintalogiikasta. CommunityToolkit.Mvvm-kirjasto vähentää toistuvaa koodia merkittävästi source generaattoreiden avulla. Jos et ole vielä kokeillut sitä, nyt on hyvä hetki — se on oikeasti iso parannus perinteiseen tapaan verrattuna.
Asenna ensin paketti:
dotnet add package CommunityToolkit.Mvvm
TodoListViewModel
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
public partial class TodoListViewModel : ObservableObject
{
private readonly DatabaseService _databaseService;
public TodoListViewModel(DatabaseService databaseService)
{
_databaseService = databaseService;
}
[ObservableProperty]
private ObservableCollection<TodoItem> _items = new();
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _newItemTitle = string.Empty;
[RelayCommand]
private async Task LoadItemsAsync()
{
IsLoading = true;
try
{
var items = await _databaseService.GetAllItemsAsync();
Items = new ObservableCollection<TodoItem>(items);
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private async Task AddItemAsync()
{
if (string.IsNullOrWhiteSpace(NewItemTitle))
return;
var newItem = new TodoItem
{
Title = NewItemTitle.Trim(),
CreatedAt = DateTime.UtcNow
};
await _databaseService.AddItemAsync(newItem);
Items.Insert(0, newItem);
NewItemTitle = string.Empty;
}
[RelayCommand]
private async Task ToggleCompletedAsync(TodoItem item)
{
item.IsCompleted = !item.IsCompleted;
item.CompletedAt = item.IsCompleted
? DateTime.UtcNow
: null;
await _databaseService.UpdateItemAsync(item);
}
[RelayCommand]
private async Task DeleteItemAsync(TodoItem item)
{
await _databaseService.DeleteItemAsync(item);
Items.Remove(item);
}
}
Huomaa, miten [ObservableProperty] ja [RelayCommand] -attribuutit korvaavat manuaalisen INotifyPropertyChanged-toteutuksen ja komentoluokkien luomisen. Boilerplate-koodin määrä tippuu dramaattisesti. Omissa projekteissani tämä on säästänyt tuntikausia manuaalista työtä.
XAML-näkymä
<?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"
xmlns:vm="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Views.TodoListPage"
x:DataType="vm:TodoListViewModel"
Title="Tehtävät">
<Grid RowDefinitions="Auto,*" Padding="16">
<!-- Uuden tehtävän lisäys -->
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
<Entry Placeholder="Uusi tehtävä..."
Text="{Binding NewItemTitle}"
ReturnCommand="{Binding AddItemCommand}" />
<Button Text="Lisää"
Command="{Binding AddItemCommand}"
Grid.Column="1" />
</Grid>
<!-- Tehtävälista -->
<CollectionView ItemsSource="{Binding Items}"
Grid.Row="1"
Margin="0,16,0,0">
<CollectionView.EmptyView>
<Label Text="Ei tehtäviä. Lisää ensimmäinen!"
HorizontalOptions="Center"
VerticalOptions="Center"
TextColor="Gray" />
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:TodoItem">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Poista"
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:TodoListViewModel}}, Path=DeleteItemCommand}"
CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.RightItems>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="12"
Padding="8">
<CheckBox IsChecked="{Binding IsCompleted}"
VerticalOptions="Center" />
<VerticalStackLayout Grid.Column="1"
VerticalOptions="Center">
<Label Text="{Binding Title}"
FontSize="16" />
<Label Text="{Binding CreatedAt, StringFormat='{0:dd.MM.yyyy HH:mm}'}"
FontSize="12"
TextColor="Gray" />
</VerticalStackLayout>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
Yksi tärkeä yksityiskohta: x:DataType-attribuutti mahdollistaa käännetyt sidokset (compiled bindings), jotka ovat selvästi nopeampia kuin perinteiset reflection-pohjaiset sidokset. Käytä niitä aina kun mahdollista — ero on mitattavissa erityisesti listoissa, joissa on paljon dataa.
Geneerinen Repository-malli useille tietomalleille
Jos sovelluksessasi on useita tietomalleja (ja useimmissa on), geneerinen repository-rajapinta tekee koodista uudelleenkäytettävää. Tämä on yksi niistä malleista, jotka kannattaa ottaa käyttöön heti projektin alussa.
public interface IRepository<T> where T : class, new()
{
Task<List<T>> GetAllAsync();
Task<T?> GetByIdAsync(int id);
Task<int> SaveAsync(T entity);
Task<int> DeleteAsync(T entity);
}
public class SqliteRepository<T> : IRepository<T>
where T : class, new()
{
private readonly SQLiteAsyncConnection _db;
public SqliteRepository(SQLiteAsyncConnection db)
{
_db = db;
_db.CreateTableAsync<T>().Wait();
}
public async Task<List<T>> GetAllAsync()
=> await _db.Table<T>().ToListAsync();
public async Task<T?> GetByIdAsync(int id)
=> await _db.FindAsync<T>(id);
public async Task<int> SaveAsync(T entity)
{
var idProp = typeof(T).GetProperty("Id");
if (idProp is not null)
{
var idValue = (int)(idProp.GetValue(entity) ?? 0);
if (idValue != 0)
return await _db.UpdateAsync(entity);
}
return await _db.InsertAsync(entity);
}
public async Task<int> DeleteAsync(T entity)
=> await _db.DeleteAsync(entity);
}
Rekisteröi geneerinen repository DI-säiliöön:
// MauiProgram.cs
builder.Services.AddSingleton(new SQLiteAsyncConnection(
Constants.DatabasePath, Constants.Flags));
builder.Services.AddSingleton<IRepository<TodoItem>, SqliteRepository<TodoItem>>();
builder.Services.AddSingleton<IRepository<Category>, SqliteRepository<Category>>();
Näppärää, eikö? Nyt voit lisätä uusia tietomalleja lisäämällä vain yhden rivin DI-rekisteröintiin.
Entity Framework Core vaihtoehtona
Jos tulet ASP.NET Core -maailmasta tai tarvitset monimutkaisempia kyselyitä ja migraatioita, Entity Framework Core on erinomainen vaihtoehto suoralle SQLite.NET-kirjastolle. Se tuo mukanaan tutun ohjelmointimallin.
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Luo DbContext:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<TodoItem> TodoItems => Set<TodoItem>();
public DbSet<Category> Categories => Set<Category>();
public AppDbContext()
{
SQLitePCL.Batteries_V2.Init();
Database.EnsureCreated();
}
protected override void OnConfiguring(
DbContextOptionsBuilder options)
{
options.UseSqlite(
$"Data Source={Constants.DatabasePath}");
}
protected override void OnModelCreating(
ModelBuilder modelBuilder)
{
modelBuilder.Entity<TodoItem>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Title)
.HasMaxLength(200)
.IsRequired();
});
}
}
Kumpi sitten kannattaa valita?
- sqlite-net-pcl — Yksinkertaisemmat sovellukset, nopeampi aloitus, pienempi muistinjälki. Oma suosikkini pieniin ja keskikokoisiin projekteihin.
- EF Core — Monimutkaisemmat tietomallit, relaatiot, migraatiot ja LINQ-kyselyt. Hyvä valinta, jos tietokannan rakenne muuttuu usein.
Offline-first-suunnittelu ja tietojen synkronointi
Tässä kohtaa mennään jo vähän pidemmälle. Moderni mobiilisovellus ei voi olettaa, että verkkoyhteys on aina käytettävissä. Offline-first-arkkitehtuurissa paikallinen tietokanta on ensisijainen tietolähde, ja verkko toimii taustalla synkronointia varten.
Perusperiaatteet
- Tallenna ensin paikallisesti — Kaikki kirjoitusoperaatiot menevät ensin SQLite-tietokantaan.
- Synkronoi taustalla — Kun verkkoyhteys on käytettävissä, lähetä muutokset palvelimelle.
- Hallitse konflikteja — Päätä, kumman muutos voittaa: paikallinen vai palvelimen.
Kuulostaa yksinkertaiselta paperilla, mutta käytännössä konfliktienhallinta voi olla yllättävän monimutkaista. Aloitetaan yksinkertaisella synkronointijonolla.
Yksinkertainen synkronointijono
public class SyncItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string EntityType { get; set; } = string.Empty;
public int EntityId { get; set; }
public string Operation { get; set; } = string.Empty; // Insert, Update, Delete
public string SerializedData { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsSynced { get; set; }
}
public class SyncService
{
private readonly DatabaseService _db;
private readonly IConnectivity _connectivity;
private readonly HttpClient _httpClient;
public SyncService(
DatabaseService db,
IConnectivity connectivity,
HttpClient httpClient)
{
_db = db;
_connectivity = connectivity;
_httpClient = httpClient;
}
public async Task SyncPendingChangesAsync()
{
if (_connectivity.NetworkAccess != NetworkAccess.Internet)
return;
var pendingItems = await _db.GetPendingSyncItemsAsync();
foreach (var item in pendingItems)
{
try
{
// Lähetä muutos palvelimelle
await SendToServerAsync(item);
// Merkitse synkronoiduksi
item.IsSynced = true;
await _db.UpdateSyncItemAsync(item);
}
catch (HttpRequestException)
{
// Yhteysongelma — yritetään myöhemmin uudelleen
break;
}
}
}
private async Task SendToServerAsync(SyncItem item)
{
var content = new StringContent(
item.SerializedData,
System.Text.Encoding.UTF8,
"application/json");
var response = item.Operation switch
{
"Insert" => await _httpClient.PostAsync(
$"/api/{item.EntityType}", content),
"Update" => await _httpClient.PutAsync(
$"/api/{item.EntityType}/{item.EntityId}", content),
"Delete" => await _httpClient.DeleteAsync(
$"/api/{item.EntityType}/{item.EntityId}"),
_ => throw new InvalidOperationException(
$"Tuntematon operaatio: {item.Operation}")
};
response.EnsureSuccessStatusCode();
}
}
Azure Mobile Apps -vaihtoehto
Jos et halua rakentaa synkronointilogiikkaa itse (ymmärrettävää!), Microsoftin DataSync-kirjasto tarjoaa valmiin ratkaisun. Asenna Microsoft.Datasync.Client.SQLiteStore NuGet-paketti ja käytä IOfflineTable<T>-rajapintaa. Kirjasto hoitaa automaattisesti konfliktien tunnistamisen ja versioinnin.
Suorituskyvyn optimointi
SQLite on jo itsessään nopea, mutta muutamalla tempulla saat siitä vielä nopeamman. Nämä ovat ne optimoinnit, joilla on oikeasti merkitystä käytännössä.
1. Käytä transaktioita massaoperaatioissa
public async Task AddManyItemsAsync(List<TodoItem> items)
{
await InitAsync();
await _database!.RunInTransactionAsync(db =>
{
foreach (var item in items)
{
db.Insert(item);
}
});
}
Ilman transaktiota jokainen Insert-operaatio luo oman transaktion. Tämä hidastaa toimintaa merkittävästi. Transaktioiden avulla sata riviä lisätään lähes yhtä nopeasti kuin yksi — ero voi olla kirjaimellisesti satakertainen.
2. Käytä indeksejä
public class TodoItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[Indexed]
public bool IsCompleted { get; set; }
[Indexed]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// ... muut kentät
}
[Indexed]-attribuutti nopeuttaa hakuja kentissä, joita käytetään WHERE- tai ORDER BY -lauseissa. Lisää indeksi jokaiseen kenttään, jonka perusteella haet tai järjestät dataa — mutta älä indeksoi kaikkea, sillä se hidastaa kirjoitusoperaatioita.
3. Vältä N+1-kyselyongelmaa
Tämä on klassikko. Hae kaikki tarvittava data yhdellä kyselyllä sen sijaan, että tekisit erillisen kyselyn jokaiselle riville. Jos huomaat tekeväsi silmukkaa, jonka sisällä on tietokantakysely, pysähdy ja mieti uudelleen.
4. Ota WAL-tila käyttöön
Kuten aiemmin mainittiin, WAL-tila parantaa suorituskykyä erityisesti sovelluksissa, jotka tekevät paljon samanaikaisia luku- ja kirjoitusoperaatioita. Se on helppo ottaa käyttöön ja tuo ilmaista suorituskykyä.
Tietokannan varmuuskopiointi ja hallinta
SQLite-tietokanta on yksinkertaisesti tiedosto, joten sen varmuuskopiointi on suoraviivaista. Tässä on käytännön toteutus:
public async Task<string> BackupDatabaseAsync()
{
var backupFileName = $"backup_{DateTime.UtcNow:yyyyMMdd_HHmmss}.db3";
var backupPath = Path.Combine(
FileSystem.AppDataDirectory,
backupFileName);
// Varmista, ettei tietokantaan kirjoiteta varmuuskopioinnin aikana
var source = Constants.DatabasePath;
if (File.Exists(source))
{
File.Copy(source, backupPath, overwrite: true);
// Kopioi myös WAL- ja SHM-tiedostot, jos ne ovat olemassa
var walFile = source + "-wal";
var shmFile = source + "-shm";
if (File.Exists(walFile))
File.Copy(walFile, backupPath + "-wal", true);
if (File.Exists(shmFile))
File.Copy(shmFile, backupPath + "-shm", true);
}
return backupPath;
}
Tärkeä muistutus: Jos käytät WAL-tilaa, muista aina kopioida myös .wal- ja .shm-tiedostot tietokantatiedoston mukana. Ilman niitä varmuuskopio saattaa olla puutteellinen.
Yleisiä virheitä ja niiden välttäminen
Tässä ovat ne sudenkuopat, joihin olen itse törmännyt (ja joista olen oppinut kantapään kautta) .NET MAUI:n SQLite-kehityksessä:
- Puuttuva SQLitePCLRaw.bundle_green — Ilman tätä pakettia saat virheen: "You need to call SQLitePCL.raw.SetProvider()". Ratkaisu on yksinkertainen: asenna paketti.
- Usean yhteyden ongelma — Älä luo useita
SQLiteAsyncConnection-instansseja samaan tietokantaan. Käytä aina singletonia. Tämä on yllättävän yleinen virhe. - Synkroninen kutsu UI-säikeessä — Käytä aina
async/await-mallia. Synkroniset kutsut jäädyttävät käyttöliittymän, ja käyttäjät huomaavat sen heti. - Unohdettu CreateTableAsync — Taulun täytyy olla luotu ennen kuin sitä voi käyttää. Lazy-alustus hoitaa tämän automaattisesti, joten käytä sitä.
- Migraatio-ongelmat — SQLite.NET ei tue automaattisia migraatioita. Jos lisäät uusia sarakkeita, käytä
ALTER TABLE-komentoja tai siirry EF Coreen.
Usein kysytyt kysymykset
Missä SQLite-tietokanta tallennetaan .NET MAUI -sovelluksessa?
Tietokanta tallennetaan sovelluksen yksityiseen tallennushakemistoon, johon pääset käsiksi FileSystem.AppDataDirectory-ominaisuuden kautta. Sijainti on alustakohtainen: Androidissa sovelluksen sisäisessä tallennustilassa, iOS:ssä Library-hakemistossa ja Windowsissa LocalApplicationData-kansiossa. Muut sovellukset eivät pääse tähän dataan käsiksi.
Pitäisikö käyttää sqlite-net-pcl vai Entity Framework Corea?
Riippuu sovelluksen monimutkaisuudesta. sqlite-net-pcl on kevyempi ja sopii yksinkertaisiin tietomalleihin. EF Core tarjoaa migraatiot, monimutkaiset relaatiot ja tehokkaat LINQ-kyselyt. Jos käytät jo EF Corea ASP.NET Core -projekteissa, se on luonnollinen valinta myös MAUI:ssa.
Miten synkronoin paikallisen tietokannan palvelimen kanssa?
Yksinkertaisin tapa on luoda synkronointijono, johon tallennetaan kaikki paikalliset muutokset. Kun verkkoyhteys palaa, jono käsitellään ja muutokset lähetetään palvelimelle. Microsoftin DataSync-kirjasto tarjoaa myös valmiin ratkaisun konfliktien hallintoineen.
Miten voin parantaa SQLite-kyselyiden suorituskykyä?
Tärkeimmät optimoinnit: WAL-tilan käyttöönotto, indeksien lisääminen usein käytettyihin kenttiin, transaktioiden käyttö massaoperaatioissa ja N+1-kyselyongelman välttäminen. Käännetyt sidokset XAML:ssä auttavat myös näkymän renderöinnissä.
Tukeeko .NET MAUI:n SQLite salattujen tietokantojen käyttöä?
Kyllä. Asenna sqlite-net-sqlcipher NuGet-paketti sqlite-net-pcl:n sijaan ja anna salausavain yhteyden avaamisen yhteydessä. Tämä on ehdottomasti suositeltavaa, jos tietokanta sisältää arkaluonteista tietoa (ja useimmissa tuotantosovelluksissa sisältää).