aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Plugins.Extensions.Data
diff options
context:
space:
mode:
authorLibravatar vman <public@vaughnnugent.com>2022-12-09 13:54:16 -0500
committerLibravatar vman <public@vaughnnugent.com>2022-12-09 13:54:16 -0500
commit8b5f3eebb9f8d9bd55e922a809ffa3bd52e33401 (patch)
tree024fcdd2445b2fe37fc96d2870879d0f6aa5626f /VNLib.Plugins.Extensions.Data
parentc9d9e6d23ad7b6fdf25f30de9b4a84be23885e16 (diff)
Sql essentials classes moved, secret loading updates
Diffstat (limited to 'VNLib.Plugins.Extensions.Data')
-rw-r--r--VNLib.Plugins.Extensions.Data/SQL/DbExtensions.cs41
-rw-r--r--VNLib.Plugins.Extensions.Data/SQL/SqlColumnNameAttribute.cs (renamed from VNLib.Plugins.Extensions.Data/SQL/SqlColumnName.cs)21
-rw-r--r--VNLib.Plugins.Extensions.Data/SQL/SqlTableNameAttribute.cs40
-rw-r--r--VNLib.Plugins.Extensions.Data/SQL/SqlVariable.cs16
-rw-r--r--VNLib.Plugins.Extensions.Data/SQL/TableManager.cs5
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs48
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs72
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs44
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs390
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs8
-rw-r--r--VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs7
-rw-r--r--VNLib.Plugins.Extensions.Data/TransactionalDbContext.cs34
-rw-r--r--VNLib.Plugins.Extensions.Data/VNLib.Plugins.Extensions.Data.csproj1
13 files changed, 428 insertions, 299 deletions
diff --git a/VNLib.Plugins.Extensions.Data/SQL/DbExtensions.cs b/VNLib.Plugins.Extensions.Data/SQL/DbExtensions.cs
index e6ee6b1..1f9164e 100644
--- a/VNLib.Plugins.Extensions.Data/SQL/DbExtensions.cs
+++ b/VNLib.Plugins.Extensions.Data/SQL/DbExtensions.cs
@@ -44,13 +44,8 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/*
* Object rental for propery dictionaries used for custom result objects
*/
- private static readonly ObjectRental<Dictionary<string, PropertyInfo>> DictStore;
-
- static DbExtensions()
- {
- //Setup dict store
- DictStore = ObjectRental.Create<Dictionary<string, PropertyInfo>>(null, static dict => dict.Clear(), 20);
- }
+ private static ObjectRental<Dictionary<string, PropertyInfo>> DictStore { get; } = ObjectRental.Create<Dictionary<string, PropertyInfo>>(null, static dict => dict.Clear(), 20);
+
/// <summary>
/// Creates a new <see cref="DbParameter"/> configured for <see cref="ParameterDirection.Input"/> with the specified value
@@ -225,7 +220,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objectType.GetProperties())
{
//try to get the column name attribute of the propery
- SqlColumnName colAtt = prop.GetCustomAttribute<SqlColumnName>(true);
+ SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
//Attribute is valid and coumn name is not empty
if (!string.IsNullOrWhiteSpace(colAtt?.ColumnName))
{
@@ -245,7 +240,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (DbColumn col in columns)
{
//Get the propery if its specified by its column-name attribute
- if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo prop))
+ if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop))
{
//make sure the column has a value
if (col.ColumnOrdinal.HasValue)
@@ -288,7 +283,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objectType.GetProperties())
{
//try to get the column name attribute of the propery
- SqlColumnName colAtt = prop.GetCustomAttribute<SqlColumnName>(true);
+ SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
//Attribute is valid and coumn name is not empty
if (!string.IsNullOrWhiteSpace(colAtt?.ColumnName))
{
@@ -308,7 +303,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (DbColumn col in columns)
{
//Get the propery if its specified by its column-name attribute
- if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo prop))
+ if (avialbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop))
{
//make sure the column has a value
if (col.ColumnOrdinal.HasValue)
@@ -335,7 +330,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// <typeparam name="T"></typeparam>
/// <param name="reader"></param>
/// <returns>The created object, or default if no rows are available</returns>
- public static T GetFirstObject<T>(this DbDataReader reader) where T : new()
+ public static T? GetFirstObject<T>(this DbDataReader reader) where T : new()
{
//make sure its worth collecting object meta
if (!reader.HasRows)
@@ -355,7 +350,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objectType.GetProperties())
{
//try to get the column name attribute of the propery
- SqlColumnName colAtt = prop.GetCustomAttribute<SqlColumnName>(true);
+ SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
//Attribute is valid and coumn name is not empty
if (colAtt != null && !string.IsNullOrWhiteSpace(colAtt.ColumnName))
{
@@ -369,7 +364,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (DbColumn col in columns)
{
//Get the propery if its specified by its column-name attribute
- if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo prop) && col.ColumnOrdinal.HasValue)
+ if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop) && col.ColumnOrdinal.HasValue)
{
//Get the object
object val = reader.GetValue(col.ColumnOrdinal.Value);
@@ -390,7 +385,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// <typeparam name="T"></typeparam>
/// <param name="reader"></param>
/// <returns>The created object, or default if no rows are available</returns>
- public static async Task<T> GetFirstObjectAsync<T>(this DbDataReader reader) where T : new()
+ public static async Task<T?> GetFirstObjectAsync<T>(this DbDataReader reader) where T : new()
{
//Read
if (await reader.ReadAsync())
@@ -405,7 +400,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objectType.GetProperties())
{
//try to get the column name attribute of the propery
- SqlColumnName colAtt = prop.GetCustomAttribute<SqlColumnName>(true);
+ SqlColumnNameAttribute? colAtt = prop.GetCustomAttribute<SqlColumnNameAttribute>(true);
//Attribute is valid and coumn name is not empty
if (colAtt != null && !string.IsNullOrWhiteSpace(colAtt.ColumnName))
{
@@ -419,7 +414,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (DbColumn col in columns)
{
//Get the propery if its specified by its column-name attribute
- if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo prop) && col.ColumnOrdinal.HasValue)
+ if (availbleProps.TryGetValue(col.ColumnName, out PropertyInfo? prop) && col.ColumnOrdinal.HasValue)
{
//Get the object
object val = reader.GetValue(col.ColumnOrdinal.Value);
@@ -436,11 +431,11 @@ namespace VNLib.Plugins.Extensions.Data.SQL
}
/// <summary>
/// Executes a nonquery operation with the specified command using the object properties set with the
- /// <see cref="SqlVariable"/> attributes
+ /// <see cref="SqlVariableAttribute"/> attributes
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cmd"></param>
- /// <param name="obj">The object containing the <see cref="SqlVariable"/> properties to write to command variables</param>
+ /// <param name="obj">The object containing the <see cref="SqlVariableAttribute"/> properties to write to command variables</param>
/// <returns>The number of rows affected</returns>
/// <exception cref="TypeLoadException"></exception>
/// <exception cref="ArgumentException"></exception>
@@ -460,7 +455,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objtype.GetProperties())
{
//try to get the variable attribute of the propery
- SqlVariable varprops = prop.GetCustomAttribute<SqlVariable>(true);
+ SqlVariableAttribute varprops = prop.GetCustomAttribute<SqlVariableAttribute>(true);
//This property is an sql variable, so lets add it
if (varprops == null)
{
@@ -470,7 +465,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
if (cmd.CommandType != CommandType.Text || cmd.CommandText.Contains(varprops.VariableName))
{
//Add the parameter to the command list
- cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.Nullable).Direction = varprops.Direction;
+ cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.IsNullable).Direction = varprops.Direction;
}
}
//Prepare the sql statement
@@ -504,7 +499,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
foreach (PropertyInfo prop in objtype.GetProperties())
{
//try to get the variable attribute of the propery
- SqlVariable varprops = prop.GetCustomAttribute<SqlVariable>(true);
+ SqlVariableAttribute? varprops = prop.GetCustomAttribute<SqlVariableAttribute>(true);
//This property is an sql variable, so lets add it
if (varprops == null)
{
@@ -514,7 +509,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
if (cmd.CommandType != CommandType.Text || cmd.CommandText.Contains(varprops.VariableName))
{
//Add the parameter to the command list
- cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.Nullable).Direction = varprops.Direction;
+ cmd.AddParameter(varprops.VariableName, prop.GetValue(obj), varprops.DataType, varprops.Size, varprops.IsNullable).Direction = varprops.Direction;
}
}
//Prepare the sql statement
diff --git a/VNLib.Plugins.Extensions.Data/SQL/SqlColumnName.cs b/VNLib.Plugins.Extensions.Data/SQL/SqlColumnNameAttribute.cs
index 0039fb5..c18dab9 100644
--- a/VNLib.Plugins.Extensions.Data/SQL/SqlColumnName.cs
+++ b/VNLib.Plugins.Extensions.Data/SQL/SqlColumnNameAttribute.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
-* File: SqlColumnName.cs
+* File: SqlColumnNameAttribute.cs
*
-* SqlColumnName.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
+* SqlColumnNameAttribute.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Extensions.Data is free software: you can redistribute it and/or modify
@@ -30,12 +30,12 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// Property attribute that specifies the property represents an SQL column in the database
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
- public class SqlColumnName : Attribute
+ public sealed class SqlColumnNameAttribute : Attribute
{
public bool Nullable { get; }
public bool Unique { get; }
public bool PrimaryKey { get; }
- public string ColumnName { get; init; }
+ public string ColumnName { get; }
/// <summary>
/// Specifies the property is an SQL column name
/// </summary>
@@ -43,7 +43,7 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// <param name="primaryKey"></param>
/// <param name="nullable"></param>
/// <param name="unique"></param>
- public SqlColumnName(string columnName, bool primaryKey = false, bool nullable = true, bool unique = false)
+ public SqlColumnNameAttribute(string columnName, bool primaryKey = false, bool nullable = true, bool unique = false)
{
this.ColumnName = columnName;
this.PrimaryKey = primaryKey;
@@ -51,15 +51,4 @@ namespace VNLib.Plugins.Extensions.Data.SQL
this.Unique = unique;
}
}
-
- /// <summary>
- /// Allows a type to declare itself as a <see cref="System.Data.DataTable"/> with the specified name
- /// </summary>
- [AttributeUsage(AttributeTargets.Class, AllowMultiple =false, Inherited = true)]
- public class SqlTableName : Attribute
- {
- public string TableName { get; }
-
- public SqlTableName(string tableName) => TableName = tableName;
- }
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/SQL/SqlTableNameAttribute.cs b/VNLib.Plugins.Extensions.Data/SQL/SqlTableNameAttribute.cs
new file mode 100644
index 0000000..9c870ea
--- /dev/null
+++ b/VNLib.Plugins.Extensions.Data/SQL/SqlTableNameAttribute.cs
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Data
+* File: SqlColumnName.cs
+*
+* SqlColumnName.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Data is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Data is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Plugins.Extensions.Data.SQL
+{
+
+ /// <summary>
+ /// Allows a type to declare itself as a <see cref="System.Data.DataTable"/> with the specified name
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple =false, Inherited = true)]
+ public sealed class SqlTableNameAttribute : Attribute
+ {
+ public string TableName { get; }
+
+ public SqlTableNameAttribute(string tableName) => TableName = tableName;
+ }
+} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/SQL/SqlVariable.cs b/VNLib.Plugins.Extensions.Data/SQL/SqlVariable.cs
index d33854a..b18d27b 100644
--- a/VNLib.Plugins.Extensions.Data/SQL/SqlVariable.cs
+++ b/VNLib.Plugins.Extensions.Data/SQL/SqlVariable.cs
@@ -31,13 +31,13 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// Property attribute that specifies the property is to be used for a given command variable
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
- public class SqlVariable : Attribute
+ public sealed class SqlVariableAttribute : Attribute
{
- public string VariableName { get; init; }
- public DbType DataType { get; init; }
- public ParameterDirection Direction { get; init; }
- public int Size { get; init; }
- public bool Nullable { get; init; }
+ public string VariableName { get; }
+ public DbType DataType { get; }
+ public ParameterDirection Direction { get; }
+ public int Size { get; }
+ public bool IsNullable { get; }
/// <summary>
/// Specifies the property to be used as an SQL variable
/// </summary>
@@ -46,13 +46,13 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// <param name="direction">Data direction during execution</param>
/// <param name="size">Column size</param>
/// <param name="isNullable">Is this property allowed to be null</param>
- public SqlVariable(string variableName, DbType dataType, ParameterDirection direction, int size, bool isNullable)
+ public SqlVariableAttribute(string variableName, DbType dataType, ParameterDirection direction, int size, bool isNullable)
{
this.VariableName = variableName;
this.DataType = dataType;
this.Direction = direction;
this.Size = size;
- this.Nullable = isNullable;
+ this.IsNullable = isNullable;
}
}
}
diff --git a/VNLib.Plugins.Extensions.Data/SQL/TableManager.cs b/VNLib.Plugins.Extensions.Data/SQL/TableManager.cs
index 14c4e64..a7f7873 100644
--- a/VNLib.Plugins.Extensions.Data/SQL/TableManager.cs
+++ b/VNLib.Plugins.Extensions.Data/SQL/TableManager.cs
@@ -43,12 +43,13 @@ namespace VNLib.Plugins.Extensions.Data.SQL
/// </summary>
protected string TableName { get; }
- public TableManager(Func<DbConnection> factory, string tableName)
+ protected TableManager(Func<DbConnection> factory, string tableName)
{
this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
this.TableName = !string.IsNullOrWhiteSpace(tableName) ? tableName : throw new ArgumentNullException(nameof(tableName));
}
- public TableManager(Func<DbConnection> factory)
+
+ protected TableManager(Func<DbConnection> factory)
{
this.Factory = factory ?? throw new ArgumentNullException(nameof(factory));
this.TableName = "";
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs
new file mode 100644
index 0000000..d7f6e29
--- /dev/null
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageContext.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Data
+* File: LWStorageContext.cs
+*
+* LWStorageContext.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Data is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Data is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/.
+*/
+
+using Microsoft.EntityFrameworkCore;
+
+namespace VNLib.Plugins.Extensions.Data.Storage
+{
+#nullable disable
+ internal sealed class LWStorageContext : TransactionalDbContext
+ {
+ private readonly string TableName;
+ public DbSet<LWStorageEntry> Descriptors { get; set; }
+
+ public LWStorageContext(DbContextOptions options, string tableName)
+ :base(options)
+ {
+ TableName = tableName;
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ //Set table name
+ modelBuilder.Entity<LWStorageEntry>()
+ .ToTable(TableName);
+ }
+ }
+} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs
index 3766a97..72665f3 100644
--- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageDescriptor.cs
@@ -26,13 +26,15 @@ using System;
using System.IO;
using System.Text.Json;
using System.Collections;
+using System.IO.Compression;
using System.Threading.Tasks;
using System.Collections.Generic;
+using System.Text.Json.Serialization;
using VNLib.Utils;
using VNLib.Utils.Async;
using VNLib.Utils.Extensions;
-using System.Text.Json.Serialization;
+using VNLib.Utils.Memory;
namespace VNLib.Plugins.Extensions.Data.Storage
{
@@ -43,7 +45,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
public sealed class LWStorageDescriptor : AsyncUpdatableResource, IObjectStorage, IEnumerable<KeyValuePair<string, string>>, IIndexable<string, string>
{
- public static readonly JsonSerializerOptions SerializerOptions = new()
+ private static readonly JsonSerializerOptions SerializerOptions = new()
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.Strict,
@@ -54,25 +56,28 @@ namespace VNLib.Plugins.Extensions.Data.Storage
DefaultBufferSize = Environment.SystemPageSize,
};
- private Dictionary<string, string> StringStorage;
+
+ internal LWStorageEntry Entry { get; }
+
+ private readonly Lazy<Dictionary<string, string>> StringStorage;
/// <summary>
/// The currnt descriptor's identifier string within its backing table. Usually the primary key.
/// </summary>
- public string DescriptorID { get; init; }
+ public string DescriptorID => Entry.Id;
/// <summary>
/// The identifier of the user for which this descriptor belongs to
/// </summary>
- public string UserID { get; init; }
+ public string UserID => Entry.UserId!;
/// <summary>
/// The <see cref="DateTime"/> when the descriptor was created
/// </summary>
- public DateTimeOffset Created { get; init; }
+ public DateTimeOffset Created => Entry.Created;
/// <summary>
/// The last time this descriptor was updated
/// </summary>
- public DateTimeOffset LastModified { get; init; }
-
+ public DateTimeOffset LastModified => Entry.LastModified;
+
///<inheritdoc/>
protected override AsyncUpdateCallback UpdateCb { get; }
///<inheritdoc/>
@@ -80,23 +85,31 @@ namespace VNLib.Plugins.Extensions.Data.Storage
///<inheritdoc/>
protected override JsonSerializerOptions JSO => SerializerOptions;
- internal LWStorageDescriptor(LWStorageManager manager)
+ internal LWStorageDescriptor(LWStorageManager manager, LWStorageEntry entry)
{
+ Entry = entry;
UpdateCb = manager.UpdateDescriptorAsync;
DeleteCb = manager.RemoveDescriptorAsync;
+ StringStorage = new(OnStringStoreLoad);
}
- internal async ValueTask PrepareAsync(Stream data)
+ internal Dictionary<string, string> OnStringStoreLoad()
{
- try
+ if(Entry.Data == null || Entry.Data.Length == 0)
+ {
+ return new(StringComparer.OrdinalIgnoreCase);
+ }
+ else
{
- //Deserialze async
- StringStorage = await VnEncoding.JSONDeserializeFromBinaryAsync<Dictionary<string,string>>(data, SerializerOptions);
+ //Calc and alloc decode buffer
+ int bufferSize = (int)(Entry.Data.Length * 1.75);
+ using UnsafeMemoryHandle<byte> decodeBuffer = Memory.UnsafeAlloc<byte>(bufferSize);
+
+ //Decode and deserialize the data
+ return BrotliDecoder.TryDecompress(Entry.Data, decodeBuffer, out int written)
+ ? JsonSerializer.Deserialize<Dictionary<string, string>>(Entry.Data, SerializerOptions) ?? new(StringComparer.OrdinalIgnoreCase)
+ : throw new InvalidDataException("Failed to decompress data");
}
- //Ignore a json exceton, a new store will be generated
- catch (JsonException)
- { }
- StringStorage ??= new();
}
/// <inheritdoc/>
@@ -106,8 +119,9 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="ObjectDisposedException"></exception>
public T? GetObject<T>(string key)
{
+ Check();
//De-serialize and return object
- return StringStorage.TryGetValue(key, out string? val) ? val.AsJsonObject<T>(SerializerOptions) : default;
+ return StringStorage.Value.TryGetValue(key, out string? val) ? val.AsJsonObject<T>(SerializerOptions) : default;
}
/// <inheritdoc/>
@@ -140,7 +154,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
public string GetStringValue(string key)
{
Check();
- return StringStorage.TryGetValue(key, out string? val) ? val : string.Empty;
+ return StringStorage.Value.TryGetValue(key, out string? val) ? val : string.Empty;
}
/// <summary>
@@ -161,13 +175,13 @@ namespace VNLib.Plugins.Extensions.Data.Storage
if (string.IsNullOrWhiteSpace(value))
{
//If the value is null and properies exist, remove the entry
- StringStorage.Remove(key);
+ StringStorage.Value.Remove(key);
Modified |= true;
}
else
{
//Set the value
- StringStorage[key] = value;
+ StringStorage.Value[key] = value;
//Set modified flag
Modified |= true;
}
@@ -195,10 +209,22 @@ namespace VNLib.Plugins.Extensions.Data.Storage
Check();
return Modified ? (new(FlushPendingChangesAsync())) : ValueTask.CompletedTask;
}
+
+ ///<inheritdoc/>
+ public override async ValueTask ReleaseAsync()
+ {
+ await base.ReleaseAsync();
+ //Cleanup dict on exit
+ if (StringStorage.IsValueCreated)
+ {
+ StringStorage.Value.Clear();
+ }
+ }
+
///<inheritdoc/>
- public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => StringStorage.GetEnumerator();
+ public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => StringStorage.Value.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
///<inheritdoc/>
- protected override object GetResource() => StringStorage;
+ protected override object GetResource() => StringStorage.Value;
}
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs
new file mode 100644
index 0000000..5c5da61
--- /dev/null
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageEntry.cs
@@ -0,0 +1,44 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Data
+* File: LwStorageEntry.cs
+*
+* LwStorageEntry.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Data is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Data is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Plugins.Extensions.Data. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Plugins.Extensions.Data.Abstractions;
+
+namespace VNLib.Plugins.Extensions.Data.Storage
+{
+
+ internal sealed class LWStorageEntry : DbModelBase, IUserEntity
+ {
+ public override string Id { get; set; }
+
+ public override DateTime Created { get; set; }
+
+ public override DateTime LastModified { get; set; }
+
+ public string? UserId { get; set; }
+
+ public byte[]? Data { get; set; }
+ }
+} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs
index 63d41af..027fa90 100644
--- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageManager.cs
@@ -25,13 +25,16 @@
using System;
using System.IO;
using System.Data;
+using System.Linq;
using System.Threading;
-using System.Data.Common;
+using System.IO.Compression;
using System.Threading.Tasks;
-using VNLib.Utils;
+using Microsoft.EntityFrameworkCore;
-using VNLib.Plugins.Extensions.Data.SQL;
+using VNLib.Utils;
+using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
namespace VNLib.Plugins.Extensions.Data.Storage
{
@@ -39,73 +42,29 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <summary>
/// Provides single table database object storage services
/// </summary>
- public sealed class LWStorageManager : EnumerableTable<LWStorageDescriptor>
- {
- const int DTO_SIZE = 7;
- const int MAX_DATA_SIZE = 8000;
-
- //Mssql statments
- private const string GET_DESCRIPTOR_STATMENT_ID_MSQQL = "SELECT TOP 1\r\n[Id],\r\n[UserID],\r\n[Data],\r\n[Created],\r\n[LastModified]\r\nFROM @table\r\nWHERE Id=@Id;";
- private const string GET_DESCRIPTOR_STATMENT_UID_MSQL = "SELECT TOP 1\r\n[Id],\r\n[UserID],\r\n[Data],\r\n[Created],\r\n[LastModified]\r\nFROM @table\r\nWHERE UserID=@UserID;";
-
- private const string GET_DESCRIPTOR_STATMENT_ID = "SELECT\r\n[Id],\r\n[UserID],\r\n[Data],\r\n[Created],\r\n[LastModified]\r\nFROM @table\r\nWHERE Id=@Id\r\nLIMIT 1;";
- private const string GET_DESCRIPTOR_STATMENT_UID = "SELECT\r\n[Id],\r\n[UserID],\r\n[Data],\r\n[Created],\r\n[LastModified]\r\nFROM @table\r\nWHERE UserID=@UserID\r\nLIMIT 1;";
-
- private const string CREATE_DESCRIPTOR_STATMENT = "INSERT INTO @table\r\n(UserID,Id,Created,LastModified)\r\nVALUES (@UserID,@Id,@Created,@LastModified);";
-
- private const string UPDATE_DESCRIPTOR_STATMENT = "UPDATE @table\r\nSET [Data]=@Data\r\n,[LastModified]=@LastModified\r\nWHERE Id=@Id;";
- private const string REMOVE_DESCRIPTOR_STATMENT = "DELETE FROM @table\r\nWHERE Id=@Id";
- private const string CLEANUP_STATEMENT = "DELETE FROM @table\r\nWHERE [created]<@timeout;";
- private const string ENUMERATION_STATMENT = "SELECT [Id],\r\n[UserID],\r\n[Data],\r\n[LastModified],\r\n[Created]\r\nFROM @table;";
-
- private readonly string GetFromUD;
- private readonly string Cleanup;
- private readonly int keySize;
-
+ public sealed class LWStorageManager
+ {
/// <summary>
/// The generator function that is invoked when a new <see cref="LWStorageDescriptor"/> is to
/// be created without an explicit id
/// </summary>
public Func<string> NewDescriptorIdGenerator { get; init; } = static () => Guid.NewGuid().ToString("N");
+ private readonly DbContextOptions DbOptions;
+ private readonly string TableName;
+
+ private LWStorageContext GetContext() => new(DbOptions, TableName);
+
/// <summary>
/// Creates a new <see cref="LWStorageManager"/> with
/// </summary>
- /// <param name="factory">A <see cref="DbConnection"/> factory function that will generate and open connections to a database</param>
+ /// <param name="options">The db context options to create database connections with</param>
/// <param name="tableName">The name of the table to operate on</param>
- /// <param name="pkCharSize">The maximum number of characters of the DescriptorID and </param>
- /// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
- public LWStorageManager(Func<DbConnection> factory, string tableName, int pkCharSize) : base(factory, tableName)
+ public LWStorageManager(DbContextOptions options, string tableName)
{
- //Compile statments with specified tableid
- Insert = CREATE_DESCRIPTOR_STATMENT.Replace("@table", tableName);
-
- //Test connector type to compile MSSQL statments vs Sqlite/Mysql
- using (DbConnection testConnection = GetConnection())
- {
- //Determine if MSSql connections are being used
- bool isMsSql = testConnection.GetType().FullName!.Contains("SqlConnection", StringComparison.OrdinalIgnoreCase);
-
- if (isMsSql)
- {
- GetFromUD = GET_DESCRIPTOR_STATMENT_UID_MSQL.Replace("@table", tableName);
- Select = GET_DESCRIPTOR_STATMENT_ID_MSQQL.Replace("@table", tableName);
- }
- else
- {
- Select = GET_DESCRIPTOR_STATMENT_ID.Replace("@table", tableName);
- GetFromUD = GET_DESCRIPTOR_STATMENT_UID.Replace("@table", tableName);
- }
- }
-
- Update = UPDATE_DESCRIPTOR_STATMENT.Replace("@table", tableName);
- Delete = REMOVE_DESCRIPTOR_STATMENT.Replace("@table", tableName);
- Cleanup = CLEANUP_STATEMENT.Replace("@table", tableName);
- //Set key size
- keySize = pkCharSize;
- //Set default generator
- Enumerate = ENUMERATION_STATMENT.Replace("@table", tableName);
+ DbOptions = options ?? throw new ArgumentNullException(nameof(options));
+ TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
}
/// <summary>
@@ -123,44 +82,52 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
throw new ArgumentNullException(nameof(userId));
}
+
//If no override id was specified, generate a new one
descriptorIdOverride ??= NewDescriptorIdGenerator();
- //Set created time
- DateTimeOffset now = DateTimeOffset.UtcNow;
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync(cancellation);
- //Setup transaction with repeatable read iso level
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellation);
- //Create command for text command
- await using DbCommand cmd = Database.CreateTextCommand(Insert, transaction);
- //add parameters
- _ = cmd.AddParameter("@Id", descriptorIdOverride, DbType.String, keySize);
- _ = cmd.AddParameter("@UserID", userId, DbType.String, keySize);
- _ = cmd.AddParameter("@Created", now, DbType.DateTimeOffset, DTO_SIZE);
- _ = cmd.AddParameter("@LastModified", now, DbType.DateTimeOffset, DTO_SIZE);
- //Prepare operation
- await cmd.PrepareAsync(cancellation);
- //Exec and if successful will return > 0, so we can properly return a descriptor
- int result = await cmd.ExecuteNonQueryAsync(cancellation);
- //Commit transaction
- await transaction.CommitAsync(cancellation);
- if (result <= 0)
+
+ DateTime createdOrModifedTime = DateTime.UtcNow;
+
+ await using LWStorageContext ctx = GetContext();
+ await ctx.OpenTransactionAsync(cancellation);
+
+ //Make sure the descriptor doesnt exist only by its descriptor id
+ if (await ctx.Descriptors.AnyAsync(d => d.Id == descriptorIdOverride, cancellation))
{
- throw new LWDescriptorCreationException("Failed to create the new descriptor because the database retuned an invalid update row count");
+ throw new LWDescriptorCreationException($"A descriptor with id {descriptorIdOverride} already exists");
}
- //Rent new descriptor
- LWStorageDescriptor desciptor = new(this)
+
+ //Cache time
+ DateTime now = DateTime.UtcNow;
+
+ //Create the new descriptor
+ LWStorageEntry entry = new()
{
- DescriptorID = descriptorIdOverride,
- UserID = userId,
Created = now,
- LastModified = now
+ LastModified = now,
+ Id = descriptorIdOverride,
+ UserId = userId,
};
- //Set data to null
- await desciptor.PrepareAsync(null);
- return desciptor;
+
+ //Add and save changes
+ ctx.Descriptors.Add(entry);
+
+ ERRNO result = await ctx.SaveChangesAsync(cancellation);
+
+ if (!result)
+ {
+ //Rollback and raise exception
+ await ctx.RollbackTransctionAsync(cancellation);
+ throw new LWDescriptorCreationException("Failed to create descriptor, because changes could not be saved");
+ }
+ else
+ {
+ //Commit transaction and return the new descriptor
+ await ctx.CommitTransactionAsync(cancellation);
+ return new LWStorageDescriptor(this, entry);
+ }
}
+
/// <summary>
/// Attempts to retrieve <see cref="LWStorageDescriptor"/> for a given user-id. The caller is responsible for
/// consitancy state of the descriptor
@@ -171,45 +138,41 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="ArgumentNullException"></exception>
public async Task<LWStorageDescriptor?> GetDescriptorFromUIDAsync(string userid, CancellationToken cancellation = default)
{
+ //Allow null/empty entrys to just return null
if (string.IsNullOrWhiteSpace(userid))
{
throw new ArgumentNullException(nameof(userid));
}
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync(cancellation);
- //Setup transaction with repeatable read iso level
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellation);
- //Create a new command based on the command text
- await using DbCommand cmd = Database.CreateTextCommand(GetFromUD, transaction);
- //Add userid parameter
- _ = cmd.AddParameter("@UserID", userid, DbType.String, keySize);
- //Prepare operation
- await cmd.PrepareAsync(cancellation);
- //Get the reader
- DbDataReader reader = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow, cancellation);
- try
+
+ //Init db
+ await using LWStorageContext db = GetContext();
+ //Begin transaction
+ await db.OpenTransactionAsync(cancellation);
+ //Get entry
+ LWStorageEntry? entry = await (from s in db.Descriptors
+ where s.UserId == userid
+ select s)
+ .SingleOrDefaultAsync(cancellation);
+
+ //Close transactions and return
+ if (entry == null)
{
- //Make sure the record was found
- if (!await reader.ReadAsync(cancellation))
- {
- return null;
- }
- return await GetItemAsync(reader, CancellationToken.None);
+ await db.RollbackTransctionAsync(cancellation);
+ return null;
}
- finally
+ else
{
- //Close the reader
- await reader.CloseAsync();
- //Commit the transaction
- await transaction.CommitAsync(cancellation);
+ await db.CommitTransactionAsync(cancellation);
+ return new (this, entry);
}
}
+
/// <summary>
/// Attempts to retrieve the <see cref="LWStorageDescriptor"/> for the given descriptor id. The caller is responsible for
/// consitancy state of the descriptor
/// </summary>
/// <param name="descriptorId">Unique identifier for the descriptor</param>
+ /// <param name="cancellation">A token to cancel the opreeaiton</param>
/// <returns>The descriptor belonging to the user, or null if not found or error occurs</returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<LWStorageDescriptor?> GetDescriptorFromIDAsync(string descriptorId, CancellationToken cancellation = default)
@@ -219,35 +182,30 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
throw new ArgumentNullException(nameof(descriptorId));
}
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync(cancellation);
- //Setup transaction with repeatable read iso level
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.RepeatableRead, cancellation);
- //We dont have the routine stored
- await using DbCommand cmd = Database.CreateTextCommand(Select, transaction);
- //Set userid (unicode length)
- _ = cmd.AddParameter("@Id", descriptorId, DbType.String, keySize);
- //Prepare operation
- await cmd.PrepareAsync(cancellation);
- //Get the reader
- DbDataReader reader = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow, cancellation);
- try
+
+ //Init db
+ await using LWStorageContext db = GetContext();
+ //Begin transaction
+ await db.OpenTransactionAsync(cancellation);
+ //Get entry
+ LWStorageEntry? entry = await (from s in db.Descriptors
+ where s.Id == descriptorId
+ select s)
+ .SingleOrDefaultAsync(cancellation);
+
+ //Close transactions and return
+ if (entry == null)
{
- if (!await reader.ReadAsync(cancellation))
- {
- return null;
- }
- return await GetItemAsync(reader, CancellationToken.None);
+ await db.RollbackTransctionAsync(cancellation);
+ return null;
}
- finally
+ else
{
- //Close the reader
- await reader.CloseAsync();
- //Commit the transaction
- await transaction.CommitAsync(cancellation);
+ await db.CommitTransactionAsync(cancellation);
+ return new (this, entry);
}
}
+
/// <summary>
/// Cleanup entries before the specified <see cref="TimeSpan"/>. Entires are store in UTC time
/// </summary>
@@ -255,6 +213,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>The number of entires cleaned</returns>S
public Task<ERRNO> CleanupTableAsync(TimeSpan compareTime, CancellationToken cancellation = default) => CleanupTableAsync(DateTime.UtcNow.Subtract(compareTime), cancellation);
+
/// <summary>
/// Cleanup entries before the specified <see cref="DateTime"/>. Entires are store in UTC time
/// </summary>
@@ -263,21 +222,27 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <returns>The number of entires cleaned</returns>
public async Task<ERRNO> CleanupTableAsync(DateTime compareTime, CancellationToken cancellation = default)
{
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync(cancellation);
- //Begin a new transaction
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellation);
- //Setup the cleanup command for the current database
- await using DbCommand cmd = Database.CreateTextCommand(Cleanup, transaction);
- //Setup timeout parameter as a datetime
- cmd.AddParameter("@timeout", compareTime, DbType.DateTime);
- await cmd.PrepareAsync(cancellation);
- //Exec and if successful will return > 0, so we can properly return a descriptor
- int result = await cmd.ExecuteNonQueryAsync(cancellation);
+ //Init db
+ await using LWStorageContext db = GetContext();
+ //Begin transaction
+ await db.OpenTransactionAsync(cancellation);
+
+ //Get all expired entires
+ LWStorageEntry[] expired = await (from s in db.Descriptors
+ where s.Created < compareTime
+ select s)
+ .ToArrayAsync(cancellation);
+
+ //Delete
+ db.Descriptors.RemoveRange(expired);
+
+ //Save changes
+ ERRNO count = await db.SaveChangesAsync(cancellation);
+
//Commit transaction
- await transaction.CommitAsync(cancellation);
- return result;
+ await db.CommitTransactionAsync(cancellation);
+
+ return count;
}
/// <summary>
@@ -288,38 +253,55 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="LWStorageUpdateFailedException"></exception>
internal async Task UpdateDescriptorAsync(object descriptorObj, Stream data)
{
- LWStorageDescriptor descriptor = (descriptorObj as LWStorageDescriptor)!;
- int result = 0;
+ LWStorageEntry entry = (descriptorObj as LWStorageDescriptor)!.Entry;
+ ERRNO result = 0;
try
{
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync();
- //Setup transaction with repeatable read iso level
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable);
- //Create command for stored procedure
- await using DbCommand cmd = Database.CreateTextCommand(Update, transaction);
- //Add parameters
- _ = cmd.AddParameter("@Id", descriptor.DescriptorID, DbType.String, keySize);
- _ = cmd.AddParameter("@Data", data, DbType.Binary, MAX_DATA_SIZE);
- _ = cmd.AddParameter("@LastModified", DateTime.UtcNow, DbType.DateTime2, DTO_SIZE);
- //Prepare operation
- await cmd.PrepareAsync();
- //exec and store result
- result = await cmd.ExecuteNonQueryAsync();
- //Commit
- await transaction.CommitAsync();
+ await using LWStorageContext ctx = GetContext();
+ await ctx.OpenTransactionAsync(CancellationToken.None);
+
+ //Begin tracking
+ ctx.Descriptors.Attach(entry);
+
+ //Convert stream to vnstream
+ VnMemoryStream vms = (VnMemoryStream)data;
+ using (IMemoryHandle<byte> encBuffer = Memory.SafeAlloc<byte>((int)vms.Length))
+ {
+ //try to compress
+ if(!BrotliEncoder.TryCompress(vms.AsSpan(), encBuffer.Span, out int compressed))
+ {
+ throw new InvalidDataException("Failed to compress the descriptor data");
+ }
+ //Set the data
+ entry.Data = encBuffer.Span.ToArray();
+ }
+ //Update modified time
+ entry.LastModified = DateTime.UtcNow;
+
+ //Save changes
+ result = await ctx.SaveChangesAsync(CancellationToken.None);
+
+ //Commit or rollback
+ if (result)
+ {
+ await ctx.CommitTransactionAsync(CancellationToken.None);
+ }
+ else
+ {
+ await ctx.RollbackTransctionAsync(CancellationToken.None);
+ }
}
catch (Exception ex)
{
throw new LWStorageUpdateFailedException("", ex);
}
//If the result is 0 then the update failed
- if (result <= 0)
+ if (!result)
{
- throw new LWStorageUpdateFailedException($"Descriptor {descriptor.DescriptorID} failed to update", null);
+ throw new LWStorageUpdateFailedException($"Descriptor {entry.Id} failed to update");
}
}
+
/// <summary>
/// Function to remove the specified descriptor
/// </summary>
@@ -327,53 +309,39 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="LWStorageRemoveFailedException"></exception>
internal async Task RemoveDescriptorAsync(object descriptorObj)
{
- LWStorageDescriptor descriptor = (descriptorObj as LWStorageDescriptor)!;
+ LWStorageEntry descriptor = (descriptorObj as LWStorageDescriptor)!.Entry;
+ ERRNO result;
try
{
- //Open a new sql client
- await using DbConnection Database = GetConnection();
- await Database.OpenAsync();
- //Setup transaction with repeatable read iso level
- await using DbTransaction transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable);
- //Create sql command
- await using DbCommand cmd = Database.CreateTextCommand(Delete, transaction);
- //set descriptor id
- _ = cmd.AddParameter("@Id", descriptor.DescriptorID, DbType.String, keySize);
- //Prepare operation
- await cmd.PrepareAsync();
- //Execute (the descriptor my already be removed, as long as the transaction doesnt fail we should be okay)
- _ = await cmd.ExecuteNonQueryAsync();
- //Commit
- await transaction.CommitAsync();
+ //Init db
+ await using LWStorageContext db = GetContext();
+ //Begin transaction
+ await db.OpenTransactionAsync();
+
+ //Delete the user from the database
+ db.Descriptors.Remove(descriptor);
+
+ //Save changes and commit if successful
+ result = await db.SaveChangesAsync();
+
+ if (result)
+ {
+ await db.CommitTransactionAsync();
+ }
+ else
+ {
+ await db.RollbackTransctionAsync();
+ }
}
catch (Exception ex)
{
throw new LWStorageRemoveFailedException("", ex);
}
- }
-
- ///<inheritdoc/>
- protected async override Task<LWStorageDescriptor> GetItemAsync(DbDataReader reader, CancellationToken cancellationToken)
- {
- //Open binary stream for the data column
- await using Stream data = reader.GetStream("Data");
- //Create new descriptor
- LWStorageDescriptor desciptor = new(this)
+ if (!result)
{
- //Set desctiptor data
- DescriptorID = reader.GetString("Id"),
- UserID = reader.GetString("UserID"),
- Created = reader.GetDateTime("Created"),
- LastModified = reader.GetDateTime("LastModified")
- };
- //Load the descriptor's data
- await desciptor.PrepareAsync(data);
- return desciptor;
- }
- ///<inheritdoc/>
- protected override ValueTask CleanupItemAsync(LWStorageDescriptor item, CancellationToken cancellationToken)
- {
- return item.ReleaseAsync();
+ throw new LWStorageRemoveFailedException("Failed to delete the user account because of a database failure, the user may already be deleted");
+ }
}
+
}
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs
index 8e36d6c..806912c 100644
--- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageRemoveFailedException.cs
@@ -23,7 +23,7 @@
*/
using System;
-using VNLib.Utils;
+using VNLib.Utils.Resources;
namespace VNLib.Plugins.Extensions.Data.Storage
{
@@ -34,5 +34,11 @@ namespace VNLib.Plugins.Extensions.Data.Storage
public class LWStorageRemoveFailedException : ResourceDeleteFailedException
{
internal LWStorageRemoveFailedException(string error, Exception inner) : base(error, inner) { }
+
+ public LWStorageRemoveFailedException()
+ {}
+
+ public LWStorageRemoveFailedException(string message) : base(message)
+ {}
}
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs b/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs
index 96ea4eb..fe555bf 100644
--- a/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs
+++ b/VNLib.Plugins.Extensions.Data/Storage/LWStorageUpdateFailedException.cs
@@ -23,7 +23,7 @@
*/
using System;
-using VNLib.Utils;
+using VNLib.Utils.Resources;
namespace VNLib.Plugins.Extensions.Data.Storage
{
@@ -34,5 +34,10 @@ namespace VNLib.Plugins.Extensions.Data.Storage
public class LWStorageUpdateFailedException : ResourceUpdateFailedException
{
internal LWStorageUpdateFailedException(string error, Exception inner) : base(error, inner) { }
+
+ public LWStorageUpdateFailedException()
+ {}
+ public LWStorageUpdateFailedException(string message) : base(message)
+ {}
}
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/TransactionalDbContext.cs b/VNLib.Plugins.Extensions.Data/TransactionalDbContext.cs
index 1320dc1..6b835c5 100644
--- a/VNLib.Plugins.Extensions.Data/TransactionalDbContext.cs
+++ b/VNLib.Plugins.Extensions.Data/TransactionalDbContext.cs
@@ -25,12 +25,13 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
namespace VNLib.Plugins.Extensions.Data
{
- public abstract class TransactionalDbContext : DbContext, IAsyncDisposable, IDisposable
+ public abstract class TransactionalDbContext : DbContext, IAsyncDisposable
{
/// <summary>
/// <inheritdoc/>
@@ -47,13 +48,28 @@ namespace VNLib.Plugins.Extensions.Data
/// The transaction that was opened on the current context
/// </summary>
public IDbContextTransaction? Transaction { get; set; }
+
+
+#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize, ignore because base.Dispose() is called
///<inheritdoc/>
- public override void Dispose()
+ public sealed override void Dispose()
{
//dispose the transaction
- this.Transaction?.Dispose();
+ Transaction?.Dispose();
base.Dispose();
}
+
+ ///<inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ //If transaction has been created, dispose the transaction
+ if (Transaction != null)
+ {
+ await Transaction.DisposeAsync();
+ }
+ await base.DisposeAsync();
+ }
+#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
/// <summary>
/// Opens a single transaction on the current context. If a transaction is already open,
@@ -78,16 +94,6 @@ namespace VNLib.Plugins.Extensions.Data
{
return Transaction != null ? Transaction.RollbackAsync(token) : Task.CompletedTask;
}
- ///<inheritdoc/>
- public override async ValueTask DisposeAsync()
- {
- //If transaction has been created, dispose the transaction
- if(this.Transaction != null)
- {
- await this.Transaction.DisposeAsync();
- }
- await base.DisposeAsync();
- GC.SuppressFinalize(this);
- }
+
}
} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Data/VNLib.Plugins.Extensions.Data.csproj b/VNLib.Plugins.Extensions.Data/VNLib.Plugins.Extensions.Data.csproj
index 2a780aa..acca4b3 100644
--- a/VNLib.Plugins.Extensions.Data/VNLib.Plugins.Extensions.Data.csproj
+++ b/VNLib.Plugins.Extensions.Data/VNLib.Plugins.Extensions.Data.csproj
@@ -42,6 +42,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.11" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.11" />
</ItemGroup>
<ItemGroup>