Rework database loading/migration

This commit is contained in:
2023-04-06 17:20:38 -04:00
parent b5f570688d
commit 6514f23329
21 changed files with 561 additions and 569 deletions

View File

@@ -1,218 +1,25 @@
using FeedCenter.Properties;
using Serilog;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.IO;
namespace FeedCenter.Data
{
public static class Database
{
#region Static database settings
public static string DatabaseFile { get; set; }
public static string DatabasePath { get; set; }
public static string DatabaseFile;
public static string DatabasePath;
public static FeedCenterEntities Entities { get; set; }
#endregion
public static bool Exists => File.Exists(DatabaseFile);
#region File version
public static FeedCenterEntities Entities;
private enum SqlServerCeFileVersion
{
Unknown,
Version20,
Version30,
Version35,
Version40,
}
public static bool Loaded { get; set; }
public static void Load()
{
if (Loaded) return;
Entities = new FeedCenterEntities();
}
private static SqlServerCeFileVersion GetFileVersion(string databasePath)
{
// Create a mapping of version numbers to the version enumeration
var versionMapping = new Dictionary<int, SqlServerCeFileVersion>
{
{ 0x73616261, SqlServerCeFileVersion.Version20 },
{ 0x002dd714, SqlServerCeFileVersion.Version30 },
{ 0x00357b9d, SqlServerCeFileVersion.Version35 },
{ 0x003d0900, SqlServerCeFileVersion.Version40 }
};
int signature;
try
{
// Open the database file
using var stream = new FileStream(databasePath, FileMode.Open, FileAccess.Read);
// Read the file using the binary reader
var reader = new BinaryReader(stream);
// Seek to the version signature
stream.Seek(16, SeekOrigin.Begin);
// Read the version signature
signature = reader.ReadInt32();
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception");
throw;
}
// If we know about the version number then return the right enumeration - otherwise unknown
return versionMapping.ContainsKey(signature) ? versionMapping[signature] : SqlServerCeFileVersion.Unknown;
}
#endregion
public static bool DatabaseExists => File.Exists(DatabaseFile);
public static void CreateDatabase()
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Creating database");
// Create the database itself
engine.CreateDatabase();
Log.Logger.Information("Running database script");
// Run the creation script
ExecuteScript(Resources.CreateDatabase);
}
private static int GetVersion(SqlCeConnection connection)
{
string versionString;
try
{
// Check the database version table
using var command = new SqlCeCommand("SELECT Value FROM DatabaseVersion", connection);
versionString = command.ExecuteScalar().ToString();
}
catch (SqlCeException)
{
// Check the setting table for the version
using var command = new SqlCeCommand("SELECT Value FROM Setting WHERE Name = 'DatabaseVersion'", connection);
versionString = command.ExecuteScalar().ToString();
}
if (string.IsNullOrEmpty(versionString))
versionString = "0";
Log.Logger.Information("Database version: {0}", versionString);
return int.Parse(versionString);
}
public static void UpdateDatabase()
{
Log.Logger.Information("Getting database file version");
// Get the database file version
var fileVersion = GetFileVersion(DatabaseFile);
Log.Logger.Information("Database file version: {0}", fileVersion);
// See if we need to upgrade the database file version
if (fileVersion != SqlServerCeFileVersion.Version40)
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Upgrading database");
// Upgrade the database (if needed)
engine.Upgrade();
}
Log.Logger.Information("Getting database version");
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Get the database version
var databaseVersion = GetVersion(connection);
// Create a dictionary of database upgrade scripts and their version numbers
var scriptList = new Dictionary<int, string>();
// Loop over the properties of the resource object looking for update scripts
foreach (var property in typeof(Resources).GetProperties().Where(property => property.Name.StartsWith("DatabaseUpdate")))
{
// Get the name of the property
var propertyName = property.Name;
// Extract the version from the name
var version = int.Parse(propertyName.Substring(propertyName.IndexOf("_", StringComparison.Ordinal) + 1));
// Add to the script list
scriptList[version] = propertyName;
}
// Loop over the scripts ordered by version
foreach (var pair in scriptList.OrderBy(pair => pair.Key))
{
// If the database version is less than or equal to the script version the script needs to run
if (databaseVersion <= pair.Key)
{
// Get the script text
var scriptText = Resources.ResourceManager.GetString(pair.Value);
// Run the script
ExecuteScript(scriptText);
}
}
}
public static void MaintainDatabase()
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Shrinking database");
// Compact the database
engine.Shrink();
}
private static void ExecuteScript(string scriptText)
{
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Setup the delimiters
var delimiters = new[] { "\r\nGO\r\n" };
// Split the script at the delimiters
var statements = scriptText.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
// Loop over each statement in the script
foreach (var statement in statements)
{
// Execute the statement
using var command = new SqlCeCommand(statement, connection);
command.ExecuteNonQuery();
}
Loaded = true;
}
}
}
}

View File

@@ -1,13 +0,0 @@
using System.Data.SqlTypes;
namespace FeedCenter.Data
{
public static class Extensions
{
#region SqlDateTime
public static SqlDateTime SqlDateTimeZero = new SqlDateTime(0, 0);
#endregion
}
}

View File

@@ -0,0 +1,234 @@
using Dapper;
using FeedCenter.Options;
using FeedCenter.Properties;
using Realms;
using Serilog;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
namespace FeedCenter.Data;
public static class LegacyDatabase
{
public static string DatabaseFile { get; set; }
public static string DatabasePath { get; set; }
private enum SqlServerCeFileVersion
{
Unknown,
Version20,
Version30,
Version35,
Version40,
}
private static SqlServerCeFileVersion GetFileVersion(string databasePath)
{
// Create a mapping of version numbers to the version enumeration
var versionMapping = new Dictionary<int, SqlServerCeFileVersion>
{
{ 0x73616261, SqlServerCeFileVersion.Version20 },
{ 0x002dd714, SqlServerCeFileVersion.Version30 },
{ 0x00357b9d, SqlServerCeFileVersion.Version35 },
{ 0x003d0900, SqlServerCeFileVersion.Version40 }
};
int signature;
try
{
// Open the database file
using var stream = new FileStream(databasePath, FileMode.Open, FileAccess.Read);
// Read the file using the binary reader
var reader = new BinaryReader(stream);
// Seek to the version signature
stream.Seek(16, SeekOrigin.Begin);
// Read the version signature
signature = reader.ReadInt32();
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception");
throw;
}
// If we know about the version number then return the right enumeration - otherwise unknown
return versionMapping.TryGetValue(signature, out var value) ? value : SqlServerCeFileVersion.Unknown;
}
public static bool Exists => File.Exists(DatabaseFile);
private static int GetVersion(SqlCeConnection connection)
{
string versionString;
try
{
// Check the database version table
using var command = new SqlCeCommand("SELECT Value FROM DatabaseVersion", connection);
versionString = command.ExecuteScalar().ToString();
}
catch (SqlCeException)
{
// Check the setting table for the version
using var command = new SqlCeCommand("SELECT Value FROM Setting WHERE Name = 'DatabaseVersion'", connection);
versionString = command.ExecuteScalar().ToString();
}
if (string.IsNullOrEmpty(versionString))
versionString = "0";
Log.Logger.Information("Database version: {0}", versionString);
return int.Parse(versionString);
}
public static void UpdateDatabase()
{
Log.Logger.Information("Getting database file version");
// Get the database file version
var fileVersion = GetFileVersion(DatabaseFile);
Log.Logger.Information("Database file version: {0}", fileVersion);
// See if we need to upgrade the database file version
if (fileVersion != SqlServerCeFileVersion.Version40)
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Upgrading database");
// Upgrade the database (if needed)
engine.Upgrade();
}
Log.Logger.Information("Getting database version");
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Get the database version
var databaseVersion = GetVersion(connection);
// Create a dictionary of database upgrade scripts and their version numbers
var scriptList = new Dictionary<int, string>();
// Loop over the properties of the resource object looking for update scripts
foreach (var property in typeof(Resources).GetProperties().Where(property => property.Name.StartsWith("DatabaseUpdate")))
{
// Get the name of the property
var propertyName = property.Name;
// Extract the version from the name
var version = int.Parse(propertyName[(propertyName.IndexOf("_", StringComparison.Ordinal) + 1)..]);
// Add to the script list
scriptList[version] = propertyName;
}
// Loop over the scripts ordered by version
foreach (var pair in scriptList.OrderBy(pair => pair.Key))
{
// If the database version is beyond this script then we can skip it
if (databaseVersion > pair.Key) continue;
// Get the script text
var scriptText = Resources.ResourceManager.GetString(pair.Value);
// Run the script
ExecuteScript(scriptText);
}
}
public static void MaintainDatabase()
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Shrinking database");
// Compact the database
engine.Shrink();
}
public static void MigrateDatabase()
{
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}");
var realm = Realm.GetInstance(realmConfiguration);
if (!File.Exists(DatabaseFile))
return;
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
connection.Open();
var settings = connection.Query<Setting>("SELECT * FROM Setting").OrderBy(s => s.Version).ToList();
var categories = connection.Query<Category>("SELECT * FROM Category").ToList();
var feeds = connection.Query<Feed>("SELECT * FROM Feed").ToList();
var feedItems = connection.Query<FeedItem>("SELECT * FROM FeedItem").ToList();
realm.Write(() =>
{
foreach (var category in categories)
{
category.Feeds = feeds.Where(f => f.CategoryId == category.Id).ToList();
}
foreach (var feed in feeds)
{
feed.Category = categories.FirstOrDefault(c => c.Id == feed.CategoryId);
}
foreach (var feedItem in feedItems)
{
var feed = feeds.First(f => f.Id == feedItem.FeedId);
feed.Items.Add(feedItem);
}
realm.Add(feeds);
realm.Add(categories);
realm.Add(settings, true);
});
connection.Close();
File.Move(DatabaseFile, DatabaseFile + "_bak");
}
private static void ExecuteScript(string scriptText)
{
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Setup the delimiters
var delimiters = new[] { "\r\nGO\r\n" };
// Split the script at the delimiters
var statements = scriptText.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
// Loop over each statement in the script
foreach (var statement in statements)
{
// Execute the statement
using var command = new SqlCeCommand(statement, connection);
command.ExecuteNonQuery();
}
}
}