Skip to content

Commit

Permalink
refactor: entity framework core to ado.net (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnc1997 authored Jan 14, 2024
1 parent 48f8793 commit ee4a32b
Show file tree
Hide file tree
Showing 31 changed files with 1,029 additions and 545 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LinqKit.Microsoft.EntityFrameworkCore" Version="8.1.5"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0"/>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.1"/>
<PackageReference Include="System.Linq.Async" Version="6.0.1"/>
</ItemGroup>

Expand Down
26 changes: 0 additions & 26 deletions src/AzureAppConfigurationEmulator/Contexts/ApplicationDbContext.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
namespace AzureAppConfigurationEmulator.Entities;

public class ConfigurationSetting(
string eTag,
string etag,
string key,
string label,
string? label,
string? contentType,
string? value,
DateTimeOffset lastModified,
bool isReadOnly)
bool locked,
IDictionary<string, object?>? tags)
{
public string ETag { get; set; } = eTag;
public string Etag { get; set; } = etag;

public string Key { get; set; } = key;

public string Label { get; set; } = label;
public string? Label { get; set; } = label;

public string? ContentType { get; set; } = contentType;

public string? Value { get; set; } = value;

public DateTimeOffset LastModified { get; set; } = lastModified;

public bool IsReadOnly { get; set; } = isReadOnly;
public bool Locked { get; set; } = locked;

public IDictionary<string, object?>? Tags { get; set; } = tags;
}

This file was deleted.

103 changes: 82 additions & 21 deletions src/AzureAppConfigurationEmulator/Extensions/HostingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Security.Cryptography.X509Certificates;
using AzureAppConfigurationEmulator.Contexts;
using Microsoft.EntityFrameworkCore;
using AzureAppConfigurationEmulator.Factories;

namespace AzureAppConfigurationEmulator.Extensions;

Expand Down Expand Up @@ -28,17 +27,6 @@ public static class HostingExtensions
_ => throw new ArgumentOutOfRangeException()
};

public static string DatabasePath { get; } = Environment.OSVersion.Platform switch
{
PlatformID.Win32S => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Win32Windows => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Win32NT => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.WinCE => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Unix => "/var/lib/azureappconfigurationemulator/emulator.db",
PlatformID.MacOSX => "/var/lib/azureappconfigurationemulator/emulator.db",
_ => throw new ArgumentOutOfRangeException()
};

public static IWebHostBuilder ConfigureKestrel(this IWebHostBuilder builder)
{
return builder.ConfigureKestrel(options =>
Expand All @@ -55,19 +43,92 @@ public static IWebHostBuilder ConfigureKestrel(this IWebHostBuilder builder)

public static void InitializeDatabase(this IApplicationBuilder app)
{
if (!Directory.Exists(Path.GetDirectoryName(DatabasePath)!))
{
Directory.CreateDirectory(Path.GetDirectoryName(DatabasePath)!);
}
using var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
var commandFactory = scope.ServiceProvider.GetRequiredService<IDbCommandFactory>();
var connectionFactory = scope.ServiceProvider.GetRequiredService<IDbConnectionFactory>();

if (!File.Exists(DatabasePath))
using var connection = connectionFactory.Create();

if (!Directory.Exists(Path.GetDirectoryName(connection.DataSource)!))
{
File.Create(DatabasePath);
Directory.CreateDirectory(Path.GetDirectoryName(connection.DataSource)!);
}

using (var scope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
if (!File.Exists(connection.DataSource))
{
scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
File.Create(connection.DataSource);
}

connection.Open();

using var command = commandFactory.Create(connection);

command.CommandText = """
CREATE TABLE IF NOT EXISTS configuration_settings (
etag TEXT NOT NULL,
key TEXT NOT NULL,
label TEXT,
content_type TEXT,
value TEXT,
last_modified TEXT NOT NULL,
locked INTEGER NOT NULL,
tags TEXT,
PRIMARY KEY (key, label)
);
CREATE TABLE IF NOT EXISTS configuration_settings_history (
etag TEXT NOT NULL,
key TEXT NOT NULL,
label TEXT,
content_type TEXT,
value TEXT,
last_modified TEXT NOT NULL,
locked INTEGER NOT NULL,
tags TEXT,
valid_from TEXT NOT NULL,
valid_to TEXT NOT NULL
);
CREATE TRIGGER IF NOT EXISTS delete_configuration_setting
AFTER DELETE ON configuration_settings
FOR EACH ROW
BEGIN
UPDATE configuration_settings_history
SET valid_to = datetime()
WHERE valid_to = '9999-12-31 23:59:59'
AND key = old.key
AND CASE old.label
WHEN NOT NULL THEN label = old.label
ELSE label IS NULL
END;
END;
CREATE TRIGGER IF NOT EXISTS insert_configuration_setting
AFTER INSERT ON configuration_settings
FOR EACH ROW
BEGIN
INSERT INTO configuration_settings_history (etag, key, label, content_type, value, last_modified, locked, tags, valid_from, valid_to)
VALUES (new.etag, new.key, new.label, new.content_type, new.value, new.last_modified, new.locked, new.tags, new.last_modified, '9999-12-31 23:59:59');
END;
CREATE TRIGGER IF NOT EXISTS update_configuration_setting
AFTER UPDATE ON configuration_settings
FOR EACH ROW
BEGIN
UPDATE configuration_settings_history
SET valid_to = new.last_modified
WHERE valid_to = '9999-12-31 23:59:59'
AND key = old.key
AND CASE old.label
WHEN NOT NULL THEN label = old.label
ELSE label IS NULL
END;
INSERT INTO configuration_settings_history (etag, key, label, content_type, value, last_modified, locked, tags, valid_from, valid_to)
VALUES (new.etag, new.key, new.label, new.content_type, new.value, new.last_modified, new.locked, new.tags, new.last_modified, '9999-12-31 23:59:59');
END;
""";

command.ExecuteNonQuery();
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
using System.Text;
using AzureAppConfigurationEmulator.Constants;

namespace AzureAppConfigurationEmulator.Extensions;

public static class StringExtensions
{
public static string? NormalizeNull(this string s)
{
return s is LabelFilter.Null ? null : s;
}

public static string Unescape(this string s)
{
var builder = new StringBuilder();

for (int i = 0; i < s.Length; i++)
{
if (s[i] == '\\' && i < s.Length - 1)
if (s[i] is '\\' && i < s.Length - 1)
{
i++;
}
Expand Down
56 changes: 56 additions & 0 deletions src/AzureAppConfigurationEmulator/Factories/DbCommandFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Collections;
using System.Data.Common;

namespace AzureAppConfigurationEmulator.Factories;

public class DbCommandFactory(ILogger<DbCommandFactory>? logger = null) : IDbCommandFactory
{
private ILogger<DbCommandFactory>? Logger { get; } = logger;

public DbCommand Create(DbConnection connection, string? text = null, IEnumerable<DbParameter>? parameters = null)
{
Logger?.LogDebug("Creating the command.");
var command = connection.CreateCommand();

if (text is not null)
{
Logger?.LogDebug("Setting the command text.");
command.CommandText = text;
}

if (parameters is not null)
{
Logger?.LogDebug("Enumerating the parameters.");
foreach (var parameter in parameters)
{
using (Logger?.BeginScope(new DbParameterLogScope(parameter)))
{
Logger?.LogDebug("Adding the parameter.");
command.Parameters.Add(parameter);
}
}
}

return command;
}

private class DbParameterLogScope(DbParameter parameter) : IEnumerable<KeyValuePair<string, object?>>
{
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
{
yield return new KeyValuePair<string, object?>("ParameterName", parameter.ParameterName);

yield return new KeyValuePair<string, object?>("ParameterValue", parameter.Value);
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public override string ToString()
{
return $"ParameterName:{parameter.ParameterName} ParameterValue:{parameter.Value}";
}
}
}
25 changes: 25 additions & 0 deletions src/AzureAppConfigurationEmulator/Factories/DbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Data.Common;
using Microsoft.Data.Sqlite;

namespace AzureAppConfigurationEmulator.Factories;

public class DbConnectionFactory(IConfiguration? configuration = null) : IDbConnectionFactory
{
private string ConnectionString { get; } = configuration?.GetConnectionString("DefaultConnection") ?? $"Data Source={DatabasePath}";

private static string DatabasePath { get; } = Environment.OSVersion.Platform switch
{
PlatformID.Win32S => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Win32Windows => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Win32NT => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.WinCE => @"C:\ProgramData\Azure App Configuration Emulator\emulator.db",
PlatformID.Unix => "/var/lib/azureappconfigurationemulator/emulator.db",
PlatformID.MacOSX => "/var/lib/azureappconfigurationemulator/emulator.db",
_ => throw new ArgumentOutOfRangeException()
};

public DbConnection Create()
{
return new SqliteConnection(ConnectionString);
}
}
23 changes: 23 additions & 0 deletions src/AzureAppConfigurationEmulator/Factories/DbParameterFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Data.Common;
using System.Text.Json;
using Microsoft.Data.Sqlite;

namespace AzureAppConfigurationEmulator.Factories;

public class DbParameterFactory : IDbParameterFactory
{
public DbParameter Create<TValue>(string name, TValue? value)
{
return value switch
{
bool b => new SqliteParameter(name, SqliteType.Integer) { Value = b ? 1 : 0 },
DateTime d => new SqliteParameter(name, SqliteType.Text) { Value = d.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss") },
DateTimeOffset d => new SqliteParameter(name, SqliteType.Text) { Value = d.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss") },
IDictionary<string, object?> d => new SqliteParameter(name, SqliteType.Text) { Value = JsonSerializer.Serialize(d) },
int i => new SqliteParameter(name, SqliteType.Integer) { Value = i },
null => new SqliteParameter(name, SqliteType.Text) { Value = DBNull.Value },
string s => new SqliteParameter(name, SqliteType.Text) { Value = s },
_ => new SqliteParameter(name, SqliteType.Text) { Value = value.ToString() }
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Data.Common;

namespace AzureAppConfigurationEmulator.Factories;

public interface IDbCommandFactory
{
public DbCommand Create(DbConnection connection, string? text = null, IEnumerable<DbParameter>? parameters = null);
}
Loading

0 comments on commit ee4a32b

Please sign in to comment.