aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Data/src
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-02-14 14:35:03 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-02-14 14:35:03 -0500
commit54760bfabb36c96f666ca7f77028d0d6a9c812fc (patch)
treecd0d010ef461ecd8a7259df0ade979471a6e527e /lib/VNLib.Plugins.Extensions.Data/src
parent107f9774df2ad484f3edc5f401c738f647f00f01 (diff)
Squashed commit of the following:
commit d72bd53e20770be4ced0d627567ecf567d1ce9f4 Author: vnugent <public@vaughnnugent.com> Date: Mon Feb 12 18:34:52 2024 -0500 refactor: #1 convert sql libraries to assets for better code splitting commit 736b873e32447254b3aadbb5c6252818c25e8fd4 Author: vnugent <public@vaughnnugent.com> Date: Sun Feb 4 01:30:25 2024 -0500 submit pending changes
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Data/src')
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IConcurrentDbContext.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDataStore.cs4
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDbContextHandle.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Abstractions/ITransactionalDbContext.cs58
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/DBContextBase.cs85
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/DbStore.cs4
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreExtensions.cs186
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreHelperExtensions.cs89
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs37
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/TransactionalDbContext.cs139
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj2
12 files changed, 234 insertions, 418 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IConcurrentDbContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IConcurrentDbContext.cs
deleted file mode 100644
index f3308c5..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IConcurrentDbContext.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: IConcurrentDbContext.cs
-*
-* IConcurrentDbContext.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 Affero General Public License as
-* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Threading;
-using System.Threading.Tasks;
-using System.Transactions;
-
-namespace VNLib.Plugins.Extensions.Data.Abstractions
-{
- /// <summary>
- /// Represents a database context that can manage concurrency via transactions
- /// </summary>
- public interface IConcurrentDbContext : ITransactionalDbContext
- {
- /// <summary>
- /// Opens a single transaction on the current context. If a transaction is already open,
- /// it is disposed and a new transaction is begun.
- /// </summary>
- /// <param name="isolationLevel">The isolation level of the transaction</param>
- /// <param name="cancellationToken">A token to cancel the operations</param>
- Task OpenTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default);
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDataStore.cs b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDataStore.cs
index bdd1b6c..e966176 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDataStore.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDataStore.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -37,7 +37,7 @@ namespace VNLib.Plugins.Extensions.Data.Abstractions
string GetNewRecordId();
/// <summary>
- /// Gets a new <see cref="TransactionalDbContext"/> ready for use
+ /// Gets a new <see cref="IDbContextHandle"/> ready for use
/// </summary>
/// <returns></returns>
IDbContextHandle GetNewContext();
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDbContextHandle.cs b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDbContextHandle.cs
index 73734b5..0d06467 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDbContextHandle.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/IDbContextHandle.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/ITransactionalDbContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/ITransactionalDbContext.cs
deleted file mode 100644
index dd906d1..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Abstractions/ITransactionalDbContext.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: TransactionalDbContext.cs
-*
-* TransactionalDbContext.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 Affero General Public License as
-* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Threading;
-using System.Threading.Tasks;
-
-using Microsoft.EntityFrameworkCore.Storage;
-
-namespace VNLib.Plugins.Extensions.Data.Abstractions
-{
- /// <summary>
- /// Represents a database context that can manage concurrency via transactions
- /// </summary>
- public interface ITransactionalDbContext
- {
- /// <summary>
- /// The transaction that was opened on the current context
- /// </summary>
- IDbContextTransaction? Transaction { get; set; }
-
- /// <summary>
- /// Invokes the <see cref="IDbContextTransaction.Commit"/> on the current context
- /// </summary>
- Task CommitTransactionAsync(CancellationToken token = default);
-
- /// <summary>
- /// Opens a single transaction on the current context. If a transaction is already open,
- /// it is disposed and a new transaction is begun.
- /// </summary>
- Task OpenTransactionAsync(CancellationToken cancellationToken = default);
-
- /// <summary>
- /// Invokes the <see cref="IDbContextTransaction.Rollback"/> on the current context
- /// </summary>
- Task RollbackTransctionAsync(CancellationToken token = default);
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/DBContextBase.cs b/lib/VNLib.Plugins.Extensions.Data/src/DBContextBase.cs
new file mode 100644
index 0000000..e933452
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Data/src/DBContextBase.cs
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Data
+* File: DBContextBase.cs
+*
+* DBContextBase.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 Affero General Public License as
+* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using Microsoft.EntityFrameworkCore;
+
+using VNLib.Utils;
+using VNLib.Plugins.Extensions.Data.Abstractions;
+
+
+namespace VNLib.Plugins.Extensions.Data
+{
+ /// <summary>
+ /// Provides abstract implementation of a database context that can manage concurrency via transactions
+ /// </summary>
+ public abstract class DBContextBase : DbContext, IAsyncDisposable, IDbContextHandle
+ {
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ protected DBContextBase()
+ { }
+
+ /// <summary>
+ /// <inheritdoc/>
+ /// </summary>
+ protected DBContextBase(DbContextOptions options) : base(options)
+ { }
+
+ ///<inheritdoc/>
+ IQueryable<T> IDbContextHandle.Set<T>() => base.Set<T>();
+
+ ///<inheritdoc/>
+ void IDbContextHandle.Add<T>(T entity) => base.Add(entity);
+
+ ///<inheritdoc/>
+ void IDbContextHandle.Remove<T>(T entity) => base.Remove<T>(entity);
+
+ ///<inheritdoc/>
+ public virtual void AddRange<T>(IEnumerable<T> entities) where T : class
+ {
+ DbSet<T> set = base.Set<T>();
+ set.AddRange(entities);
+ }
+
+ ///<inheritdoc/>
+ public virtual async Task<ERRNO> SaveAndCloseAsync(bool commit, CancellationToken cancellation = default)
+ {
+ return await base.SaveChangesAsync(cancellation);
+ }
+
+ ///<inheritdoc/>
+ public virtual void RemoveRange<T>(IEnumerable<T> entities) where T : class
+ {
+ DbSet<T> set = base.Set<T>();
+ set.RemoveRange(entities);
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/DbStore.cs b/lib/VNLib.Plugins.Extensions.Data/src/DbStore.cs
index b5f327e..5593507 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/DbStore.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/DbStore.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -38,7 +38,7 @@ namespace VNLib.Plugins.Extensions.Data
{
/// <summary>
- /// Gets a new <see cref="TransactionalDbContext"/> ready for use
+ /// Gets a new <see cref="IDbContextHandle"/> ready for use
/// </summary>
/// <returns></returns>
public abstract IDbContextHandle GetNewContext();
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreExtensions.cs b/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreExtensions.cs
index e053900..e3d4f6b 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreExtensions.cs
@@ -1,11 +1,11 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
-* File: DbStore.cs
+* File: DbStoreExtensions.cs
*
-* DbStore.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
+* DbStoreExtensions.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
@@ -23,9 +23,9 @@
*/
using System;
+using System.Data;
using System.Linq;
using System.Threading;
-using System.Transactions;
using System.Threading.Tasks;
using System.Collections.Generic;
@@ -51,8 +51,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<ERRNO> AddOrUpdateAsync<T>(this IDataStore<T> store, T record, CancellationToken cancellation = default)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadCommitted, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
IQueryable<T> query;
@@ -73,7 +75,7 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
T? entry = await query.SingleOrDefaultAsync(cancellation);
//Check if creted
- if (entry == null)
+ if (entry is null)
{
//Create a new template id
record.Id = store.GetNewRecordId();
@@ -100,8 +102,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<ERRNO> UpdateAsync<T>(this IDataStore<T> store, T record, CancellationToken cancellation = default)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.Serializable, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the application
IQueryable<T> query = store.QueryTable.UpdateQueryBuilder(ctx, record);
@@ -130,8 +134,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<ERRNO> CreateAsync<T>(this IDataStore<T> store, T record, CancellationToken cancellation = default)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Create a new template id
record.Id = store.GetNewRecordId();
@@ -155,8 +161,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<long> GetCountAsync<T>(this IDataStore<T> store, CancellationToken cancellation = default)
where T : class, IDbModel
{
- //Open db connection
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Async get the number of records of the given entity type
long count = await ctx.Set<T>().LongCountAsync(cancellation);
@@ -177,7 +185,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<long> GetCountAsync<T>(this IDataStore<T> store, string specifier, CancellationToken cancellation = default)
where T : class, IDbModel
{
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Async get the number of records of the given entity type
long count = await store.QueryTable.GetCountQueryBuilder(ctx, specifier).LongCountAsync(cancellation);
@@ -199,8 +210,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<T?> GetSingleAsync<T>(this IDataStore<T> store, string key, CancellationToken cancellation = default)
where T : class, IDbModel
{
- //Open db connection
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the single template by its id
T? record = await (from entry in ctx.Set<T>()
@@ -223,8 +236,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<T?> GetSingleAsync<T>(this IDataStore<T> store, params string[] specifiers)
where T : class, IDbModel
{
- //Open db connection
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the single item by specifiers
T? record = await store.QueryTable.GetSingleQueryBuilder(ctx, specifiers).SingleOrDefaultAsync();
@@ -245,8 +260,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<T?> GetSingleAsync<T>(this IDataStore<T> store, T record, CancellationToken cancellation = default)
where T : class, IDbModel
{
- //Open db connection
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the single template by its id
T? entry = await store.QueryTable.GetSingleQueryBuilder(ctx, record).SingleOrDefaultAsync(cancellation);
@@ -272,8 +289,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
{
int previous = collection.Count;
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the single template by its id
await store.QueryTable.GetCollectionQueryBuilder(ctx, specifier)
@@ -303,8 +322,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
{
int previous = collection.Count;
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the single template by its id
await store.QueryTable.GetCollectionQueryBuilder(ctx, args)
@@ -331,8 +352,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<ERRNO> DeleteAsync<T>(this IDataStore<T> store, T record, CancellationToken cancellation = default)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.RepeatableRead, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get a query for a a single item
IQueryable<T> query = store.QueryTable.GetSingleQueryBuilder(ctx, record);
@@ -340,7 +363,7 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
//Get the entry if it exists
T? entry = await query.SingleOrDefaultAsync(cancellation);
- if (entry == null)
+ if (entry is null)
{
await ctx.SaveAndCloseAsync(false, cancellation);
return false;
@@ -364,7 +387,7 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
where T : class, IDbModel
{
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.RepeatableRead, cancellation);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get a query for a a single item
IQueryable<T> query = store.QueryTable.GetSingleQueryBuilder(ctx, key);
@@ -372,14 +395,14 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
//Get the entry if it exists
T? entry = await query.SingleOrDefaultAsync(cancellation);
- if (entry == null)
+ if (entry is null)
{
await ctx.SaveAndCloseAsync(false, cancellation);
return false;
}
else
{
- //Remove the entry
+ //Remove the entry then exit
ctx.Remove(entry);
return await ctx.SaveAndCloseAsync(true, cancellation);
}
@@ -394,15 +417,17 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<ERRNO> DeleteAsync<T>(this IDataStore<T> store, params string[] specifiers)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.RepeatableRead);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get the template by its id
IQueryable<T> query = store.QueryTable.DeleteQueryBuilder(ctx, specifiers);
T? entry = await query.SingleOrDefaultAsync();
- if (entry == null)
+ if (entry is null)
{
return false;
}
@@ -429,8 +454,10 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
//Store preivous count
int previous = collection.Count;
- //Open db connection
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted, cancellation);
+ ArgumentNullException.ThrowIfNull(store);
+
+ //Open new db context
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get a page offset and a limit for the
await ctx.Set<T>()
@@ -459,11 +486,14 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
public static async Task<int> GetPageAsync<T>(this IDataStore<T> store, ICollection<T> collection, int page, int limit, params string[] constraints)
where T : class, IDbModel
{
+ ArgumentNullException.ThrowIfNull(store);
+ ArgumentNullException.ThrowIfNull(collection);
+
//Store preivous count
int previous = collection.Count;
//Open new db context
- await using IDbContextHandle ctx = await store.OpenAsync(IsolationLevel.ReadUncommitted);
+ await using IDbContextHandle ctx = store.GetNewContext();
//Get a page of records constrained by the given arguments
await store.QueryTable.GetPageQueryBuilder(ctx, constraints)
@@ -480,50 +510,102 @@ namespace VNLib.Plugins.Extensions.Data.Extensions
return collection.Count - previous;
}
-
- public static Task<ERRNO> AddBulkAsync<T>(this IDataStore<T> store, IEnumerable<T> records, string userId, bool overwriteTime = true, CancellationToken cancellation = default)
- where T : class, IDbModel, IUserEntity
+ /// <summary>
+ /// Specifies the user-id for each record in collection of user entities
+ /// during enumeration.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="current"></param>
+ /// <param name="userId">The user-id to specify</param>
+ /// <returns>A new enumeration instance that will add the new user-id to each record upon enumerating</returns>
+ public static IEnumerable<T> WithUserId<T>(this IEnumerable<T> current, string? userId)
+ where T : IUserEntity
{
- //Assign user-id when numerated
- IEnumerable<T> withUserId = records.Select(p =>
+ return current.Select(p =>
{
p.UserId = userId;
return p;
});
+ }
+
+ /// <summary>
+ /// Updates the created and last modified times for each record in the collection
+ /// with the current time in UTC.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="current"></param>
+ /// <param name="overwriteTime">A value that indicates if the record's created time should be overwritten or ignored</param>
+ /// <returns>A new enumeration that will updated record times upon enumerating</returns>
+ public static IEnumerable<T> WithCurrentUtcTime<T>(this IEnumerable<T> current, bool overwriteTime) where T : IDbModel
+ {
+ return WithTime(current, DateTime.UtcNow, overwriteTime);
+ }
- return store.AddBulkAsync(withUserId, overwriteTime, cancellation);
+ /// <summary>
+ /// Updates the created and last modified times for each record in the collection
+ /// with the current system local time.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="current"></param>
+ /// <param name="overwriteTime">A value that indicates if the record's created time should be overwritten or ignored</param>
+ /// <returns>A new enumeration that will updated record times upon enumerating</returns>
+ public static IEnumerable<T> WithCurrentLocalTime<T>(this IEnumerable<T> current, bool overwriteTime) where T : IDbModel
+ {
+ return WithTime(current, DateTime.Now, overwriteTime);
}
- public static async Task<ERRNO> AddBulkAsync<T>(this IDataStore<T> store, IEnumerable<T> records, bool overwriteTime = true, CancellationToken cancellation = default)
- where T : class, IDbModel
+ /// <summary>
+ /// Updates the created and last modified times for each record in the collection
+ /// with the specified time.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="current"></param>
+ /// <param name="now">A structure to the time to set seach record's last modifed, and optionally created time</param>
+ /// <param name="overwriteTime">A value that indicates if the record's created time should be overwritten or ignored</param>
+ /// <returns>A new enumeration that will updated record times upon enumerating</returns>
+ public static IEnumerable<T> WithTime<T>(this IEnumerable<T> current, DateTime now, bool overwriteTime)
+ where T : IDbModel
{
- DateTime now = DateTime.UtcNow;
+ return current.Select(p =>
+ {
+ //Only overwrite the created time if it is the default value or the overwrite flag is set
+ if (overwriteTime || p.Created == default)
+ {
+ p.Created = now;
+ }
- //Open context and transaction
- await using IDbContextHandle database = await store.OpenAsync(IsolationLevel.ReadCommitted, cancellation);
+ //Always update the last modified time
+ p.LastModified = now;
+ return p;
+ });
+ }
- //Get the entity set
- IQueryable<T> set = database.Set<T>();
+ /// <summary>
+ /// Adds a collection of records directly to the store without any additional processing
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="store"></param>
+ /// <param name="records">The collection of records to add</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task the complets with the number of records update</returns>
+ public static async Task<ERRNO> AddBulkAsync<T>(this IDataStore<T> store, IEnumerable<T> records, CancellationToken cancellation = default)
+ where T : class, IDbModel
+ {
+ ArgumentNullException.ThrowIfNull(store);
+ ArgumentNullException.ThrowIfNull(records);
+
+ //Open new db context
+ await using IDbContextHandle database = store.GetNewContext();
//Generate random ids for the feeds and set user-id
foreach (T entity in records)
{
entity.Id = store.GetNewRecordId();
-
- //If the entity has the default created time, update it, otherwise leave it as is
- if (overwriteTime || entity.Created == default)
- {
- entity.Created = now;
- }
-
- //Update last-modified time
- entity.LastModified = now;
}
//Add bulk items to database
database.AddRange(records);
return await database.SaveAndCloseAsync(true, cancellation);
}
-
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreHelperExtensions.cs b/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreHelperExtensions.cs
deleted file mode 100644
index 55230cf..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/Extensions/DbStoreHelperExtensions.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: DbStoreHelperExtensions.cs
-*
-* DbStoreHelperExtensions.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 Affero General Public License as
-* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Threading;
-using System.Transactions;
-using System.Threading.Tasks;
-
-using VNLib.Plugins.Extensions.Data.Abstractions;
-
-namespace VNLib.Plugins.Extensions.Data.Extensions
-{
- internal static class DbStoreHelperExtensions
- {
- /// <summary>
- /// If the current context instance inherits the <see cref="IConcurrentDbContext"/> interface,
- /// attempts to open a transaction with the specified isolation level.
- /// </summary>
- /// <param name="tdb"></param>
- /// <param name="isolationLevel">The transaction isolation level</param>
- /// <param name="cancellationToken">A token to cancel the operation</param>
- /// <returns></returns>
- internal static Task OpenTransactionAsync(this ITransactionalDbContext tdb, IsolationLevel isolationLevel, CancellationToken cancellationToken = default)
- {
- if (tdb is IConcurrentDbContext ccdb)
- {
- return ccdb.OpenTransactionAsync(isolationLevel, cancellationToken);
- }
- else
- {
- //Just ignore the isolation level
- return tdb.OpenTransactionAsync(cancellationToken);
- }
- }
-
- /// <summary>
- /// Opens a new database connection. If the context supports transactions, it will
- /// open a transaction with the specified isolation level.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="store"></param>
- /// <param name="level">The transaction isolation level</param>
- /// <param name="cancellation">A token to cancel the transaction operation</param>
- /// <returns>A task that resolves the new open <see cref="IDbContextHandle"/> </returns>
- public static async Task<IDbContextHandle> OpenAsync<T>(this IDataStore<T> store, IsolationLevel level, CancellationToken cancellation = default)
- where T : class, IDbModel
- {
- //Open new db context
- IDbContextHandle ctx = store.GetNewContext();
-
- //Support transactions and start them if the context supports it
- if(ctx is ITransactionalDbContext tdb)
- {
- try
- {
- //Open transaction
- await tdb.OpenTransactionAsync(level, cancellation);
- }
- catch
- {
- await ctx.DisposeAsync();
- throw;
- }
- }
-
- return ctx;
- }
- }
-}
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
index fabd54e..032d380 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageContext.cs
@@ -29,7 +29,7 @@ using Microsoft.EntityFrameworkCore;
namespace VNLib.Plugins.Extensions.Data.Storage
{
#nullable disable
- internal sealed class LWStorageContext : TransactionalDbContext
+ internal sealed class LWStorageContext : DBContextBase
{
private readonly string TableName;
public DbSet<LWStorageEntry> Descriptors { get; set; }
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
index bc506c7..f098def 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
+++ b/lib/VNLib.Plugins.Extensions.Data/src/Storage/LWStorageManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
@@ -32,8 +32,6 @@ using Microsoft.EntityFrameworkCore;
using VNLib.Utils;
using VNLib.Utils.Async;
-using VNLib.Plugins.Extensions.Data.Extensions;
-
namespace VNLib.Plugins.Extensions.Data.Storage
{
@@ -78,10 +76,7 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="LWDescriptorCreationException"></exception>
public async Task<LWStorageDescriptor> CreateDescriptorAsync(string userId, string? descriptorIdOverride = null, CancellationToken cancellation = default)
{
- if (string.IsNullOrWhiteSpace(userId))
- {
- throw new ArgumentNullException(nameof(userId));
- }
+ ArgumentException.ThrowIfNullOrWhiteSpace(userId);
//If no override id was specified, generate a new one
descriptorIdOverride ??= NewDescriptorIdGenerator();
@@ -89,7 +84,6 @@ namespace VNLib.Plugins.Extensions.Data.Storage
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))
@@ -129,16 +123,11 @@ 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));
- }
+ ArgumentException.ThrowIfNullOrWhiteSpace(userid);
//Init db
- await using LWStorageContext db = GetContext();
- //Begin transaction
- await db.OpenTransactionAsync(cancellation);
+ await using LWStorageContext db = GetContext();
+
//Get entry
LWStorageEntry? entry = await (from s in db.Descriptors
where s.UserId == userid
@@ -161,16 +150,11 @@ namespace VNLib.Plugins.Extensions.Data.Storage
/// <exception cref="ArgumentNullException"></exception>
public async Task<LWStorageDescriptor?> GetDescriptorFromIDAsync(string descriptorId, CancellationToken cancellation = default)
{
- //Allow null/empty entrys to just return null
- if (string.IsNullOrWhiteSpace(descriptorId))
- {
- throw new ArgumentNullException(nameof(descriptorId));
- }
+ ArgumentException.ThrowIfNullOrWhiteSpace(descriptorId);
//Init db
- await using LWStorageContext db = GetContext();
- //Begin transaction
- await db.OpenTransactionAsync(cancellation);
+ await using LWStorageContext db = GetContext();
+
//Get entry
LWStorageEntry? entry = await (from s in db.Descriptors
where s.Id == descriptorId
@@ -201,8 +185,6 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
//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
@@ -224,7 +206,6 @@ namespace VNLib.Plugins.Extensions.Data.Storage
try
{
await using LWStorageContext ctx = GetContext();
- await ctx.OpenTransactionAsync(System.Transactions.IsolationLevel.RepeatableRead, cancellation);
//Begin tracking
ctx.Descriptors.Attach(entry);
@@ -254,8 +235,6 @@ namespace VNLib.Plugins.Extensions.Data.Storage
{
//Init db
await using LWStorageContext db = GetContext();
- //Begin transaction
- await db.OpenTransactionAsync(cancellation);
//Delete the user from the database
db.Descriptors.Remove(descriptor);
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/TransactionalDbContext.cs b/lib/VNLib.Plugins.Extensions.Data/src/TransactionalDbContext.cs
deleted file mode 100644
index cbb9799..0000000
--- a/lib/VNLib.Plugins.Extensions.Data/src/TransactionalDbContext.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Data
-* File: TransactionalDbContext.cs
-*
-* TransactionalDbContext.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 Affero General Public License as
-* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Storage;
-
-using VNLib.Utils;
-using VNLib.Plugins.Extensions.Data.Abstractions;
-
-
-namespace VNLib.Plugins.Extensions.Data
-{
- /// <summary>
- /// Abstract implementation of <see cref="ITransactionalDbContext"/> that provides a transactional context for database operations
- /// </summary>
- public abstract class TransactionalDbContext : DbContext, IAsyncDisposable, ITransactionalDbContext, IDbContextHandle
- {
- /// <summary>
- /// <inheritdoc/>
- /// </summary>
- protected TransactionalDbContext()
- { }
-
- /// <summary>
- /// <inheritdoc/>
- /// </summary>
- protected TransactionalDbContext(DbContextOptions options) : base(options)
- { }
-
- ///<inheritdoc/>
- public IDbContextTransaction? Transaction { get; set; }
-
- ///<inheritdoc/>
- public async Task OpenTransactionAsync(CancellationToken cancellationToken = default)
- {
- //open a new transaction on the current database
- this.Transaction = await base.Database.BeginTransactionAsync(cancellationToken);
- }
-
- ///<inheritdoc/>
- public Task CommitTransactionAsync(CancellationToken token = default)
- {
- return Transaction != null ? Transaction.CommitAsync(token) : Task.CompletedTask;
- }
-
- ///<inheritdoc/>
- public Task RollbackTransctionAsync(CancellationToken token = default)
- {
- return Transaction != null ? Transaction.RollbackAsync(token) : Task.CompletedTask;
- }
-
- ///<inheritdoc/>
- IQueryable<T> IDbContextHandle.Set<T>() => base.Set<T>();
-
- ///<inheritdoc/>
- void IDbContextHandle.Add<T>(T entity) => base.Add(entity);
-
- ///<inheritdoc/>
- void IDbContextHandle.Remove<T>(T entity) => base.Remove<T>(entity);
-
- ///<inheritdoc/>
- public virtual void AddRange<T>(IEnumerable<T> entities) where T : class
- {
- DbSet<T> set = base.Set<T>();
- set.AddRange(entities);
- }
-
- ///<inheritdoc/>
- public virtual async Task<ERRNO> SaveAndCloseAsync(bool commit, CancellationToken cancellation = default)
- {
- //Save db changes
- ERRNO result = await base.SaveChangesAsync(cancellation);
- if (commit)
- {
- await CommitTransactionAsync(cancellation);
- }
- else
- {
- await RollbackTransctionAsync(cancellation);
- }
- return result;
- }
-
- ///<inheritdoc/>
- public virtual void RemoveRange<T>(IEnumerable<T> entities) where T : class
- {
- DbSet<T> set = base.Set<T>();
- set.RemoveRange(entities);
- }
-
-#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize, ignore because base.Dispose() is called
- ///<inheritdoc/>
- public sealed override void Dispose()
- {
- //dispose the transaction
- 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
- }
-} \ No newline at end of file
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
index 7e9136f..cc181e1 100644
--- a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
+++ b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj
@@ -47,7 +47,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
</ItemGroup>
<ItemGroup>