new sqlite ops

This commit is contained in:
2025-09-04 16:05:41 +02:00
parent 0e3794c6f0
commit 9446e5716f
6 changed files with 389 additions and 5 deletions
@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using ServiceStack.OrmLite;
using zero.Models;
namespace zero.Sqlite;
public partial class DbOperations : IDbOperations
{
/// <inheritdoc />
public virtual Task<Result<T>> Delete<T>(T model) where T : ZeroIdEntity, new()
=> Delete<T>(model.Id);
/// <inheritdoc />
public virtual async Task<Result<T>> Delete<T>(string id) where T : ZeroIdEntity, new()
{
T model = await Load<T>(id);
if (model == null)
{
return Result<T>.Fail("@errors.ondelete.idnotfound");
}
if (model is ISupportsSoftDelete softDeleteModel)
{
softDeleteModel.IsDeleted = true;
}
else
{
await Db.DeleteByIdAsync<T>(model.Id);
}
return Result<T>.Success();
}
}
+191
View File
@@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using ServiceStack.OrmLite;
using zero.Extensions;
using zero.Models;
using zero.Utils;
namespace zero.Sqlite;
public partial class DbOperations : IDbOperations
{
/// <inheritdoc />
public virtual async Task<T> Load<T>(string id, string changeVector = null) where T : ZeroIdEntity, new()
{
if (id.IsNullOrWhiteSpace())
{
return null;
}
if (!changeVector.IsNullOrEmpty())
{
//return WhenActive(await GetRevision(changeVector)); // TODO
}
return WhenActive(await Db.SingleByIdAsync<T>(id));
}
/// <inheritdoc />
public virtual async Task<Dictionary<string, T>> Load<T>(IEnumerable<string> ids) where T : ZeroIdEntity, new()
{
ids = ids.Distinct().ToArray();
List<T> models = await Db.SelectByIdsAsync<T>(ids);
Dictionary<string, T> result = new();
foreach (string id in ids)
{
T model = models.FirstOrDefault(x => x.Id == id);
result.Add(id, WhenActive(model));
}
return result;
}
/// <inheritdoc />
public virtual async Task<List<T>> LoadAsList<T>(IEnumerable<string> ids) where T : ZeroIdEntity, new()
{
ids = ids.Distinct().ToArray();
List<T> models = await Db.SelectByIdsAsync<T>(ids);
List<T> result = [];
foreach (string id in ids)
{
T model = models.FirstOrDefault(x => x.Id == id);
if (WhenActive(model) != null)
{
result.Add(model);
}
}
return result;
}
// /// <inheritdoc />
// public virtual async Task<bool> Any<T>(Func<IRavenQueryable<T>, IQueryable<T>> querySelector = default) where T : ZeroIdEntity, new()
// {
// querySelector ??= x => x;
// return await querySelector(Session.Query<T>()).AnyAsync();
// }
//
//
// /// <inheritdoc />
// public virtual async Task<Paged<T>> Load<T>(int pageNumber, int pageSize, Func<IRavenQueryable<T>, IQueryable<T>> querySelector = default) where T : ZeroIdEntity, new()
// {
// IRavenQueryable<T> queryable = Session.Query<T>().Statistics(out QueryStatistics statistics);
// querySelector ??= x => x;
//
// List<T> result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync();
// return new Paged<T>(result, statistics.TotalResults, pageNumber, pageSize);
// }
//
//
// /// <inheritdoc />
// public virtual async Task<Paged<T>> Load<T, TIndex>(int pageNumber, int pageSize, Func<IRavenQueryable<T>, IQueryable<T>> querySelector = default)
// where T : ZeroIdEntity, new()
// where TIndex : AbstractCommonApiForIndexes, new()
// {
// IRavenQueryable<T> queryable = Session.Query<T, TIndex>().Statistics(out QueryStatistics statistics);
// querySelector ??= x => x;
//
// List<T> result = await querySelector(queryable).Paging(pageNumber, pageSize).ToListAsync();
// return new Paged<T>(result, statistics.TotalResults, pageNumber, pageSize);
// }
//
//
// /// <inheritdoc />
// public virtual async Task<List<T>> Load<T>(Func<IRavenQueryable<T>, IQueryable<T>> querySelector) where T : ZeroIdEntity, new()
// {
// IRavenQueryable<T> queryable = Session.Query<T>();
// querySelector ??= x => x;
//
// return await querySelector(queryable).ToListAsync();
// }
//
//
// /// <inheritdoc />
// public virtual async Task<List<T>> Load<T, TIndex>(Func<IRavenQueryable<T>, IQueryable<T>> querySelector)
// where T : ZeroIdEntity, new()
// where TIndex : AbstractCommonApiForIndexes, new()
// {
// IRavenQueryable<T> queryable = Session.Query<T, TIndex>();
// querySelector ??= x => x;
//
// return await querySelector(queryable).ToListAsync();
// }
//
//
// /// <inheritdoc />
// public virtual async Task<List<T>> Load<T>(Expression<Func<T, bool>> predicate) where T : ZeroIdEntity, new()
// {
// return await Session.Query<T>().Where(predicate).ToListAsync();
// }
//
//
// /// <inheritdoc />
// public virtual async Task<List<T>> Load<T, TIndex>(Expression<Func<T, bool>> predicate)
// where T : ZeroIdEntity, new()
// where TIndex : AbstractCommonApiForIndexes, new()
// {
// return await Session.Query<T, TIndex>().Where(predicate).ToListAsync();
// }
//
//
// /// <inheritdoc />
// public virtual async Task<Paged<TProjection>> Load<T, TIndex, TProjection>(int pageNumber, int pageSize, Func<IRavenQueryable<T>, IQueryable<T>> querySelector = default)
// where T : ZeroIdEntity, new()
// where TProjection : ZeroIdEntity, new()
// where TIndex : AbstractCommonApiForIndexes, new()
// {
// IRavenQueryable<T> queryable = Session.Query<T, TIndex>().Statistics(out QueryStatistics statistics);
// querySelector ??= x => x;
//
// List<TProjection> result = await querySelector(queryable).ProjectInto<TProjection>().Paging(pageNumber, pageSize).ToListAsync();
// return new Paged<TProjection>(result, statistics.TotalResults, pageNumber, pageSize);
// }
//
//
// /// <inheritdoc />
// public virtual async Task<List<T>> LoadAll<T>() where T : ZeroIdEntity, new()
// {
// List<T> items = new();
//
// await foreach (T item in Stream<T>(null))
// {
// items.Add(item);
// }
//
// return items;
// }
//
//
// /// <inheritdoc />
// public virtual async IAsyncEnumerable<T> Stream<T>(Func<IRavenQueryable<T>, IQueryable<T>> expression) where T : ZeroIdEntity, new()
// {
// IRavenQueryable<T> query = Session.Query<T>();
// IQueryable<T> queryable = query;
//
// if (expression != null)
// {
// queryable = expression(query);
// }
//
// var stream = await Session.Advanced.StreamAsync(queryable);
//
// while (await stream.MoveNextAsync())
// {
// if (WhenActive(stream.Current.Document) == default)
// {
// continue;
// }
//
// yield return stream.Current.Document;
// }
// }
}
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation.Results;
using ServiceStack.OrmLite;
using zero.Models;
using zero.Extensions;
namespace zero.Sqlite;
public partial class DbOperations : IDbOperations
{
/// <inheritdoc />
public virtual Task<Result<T>> Create<T>(T model, Func<T, Task<ValidationResult>> validate = null) where T : ZeroIdEntity, new() => Save(model, validate);
/// <inheritdoc />
public virtual Task<Result<T>> Update<T>(T model, Func<T, Task<ValidationResult>> validate = null) where T : ZeroIdEntity, new() => Save(model, validate, true);
/// <inheritdoc />
protected virtual async Task<Result<T>> Save<T>(T model, Func<T, Task<ValidationResult>> validate = null, bool update = false) where T : ZeroIdEntity, new()
{
if (model == null)
{
return Result<T>.Fail("@errors.onsave.empty");
}
T previousModel = null;
// check if the Id for a model already exists
if (!model.Id.IsNullOrEmpty())
{
previousModel = await Db.SingleByIdAsync<T>(model.Id);
if (update && previousModel == null)
{
return Result<T>.Fail("@errors.onsave.noidmatch");
}
else if (!update && previousModel != null)
{
return Result<T>.Fail("@errors.oncreate.idmismatch");
}
}
// validate flavor
if (model is ISupportsFlavors flavorModel && !flavorModel.Flavor.IsNullOrEmpty())
{
if (!Flavors.Exists<T>(flavorModel.Flavor))
{
return Result<T>.Fail("@errors.onsave.flavornotfound");
}
}
// prepare model
PrepareForSave(model);
// run validator
if (validate != null)
{
ValidationResult validation = await validate(model);
if (!validation.IsValid)
{
return Result<T>.Fail(validation);
}
}
// create ID before-hand so interceptors can use it
if (!update)
{
model.Id = await GenerateId(model);
}
// store our model
await Db.SaveAsync(model);
return Result<T>.Success(model);
}
}
+72 -1
View File
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Data;
using FluentValidation.Results;
using Microsoft.Extensions.DependencyInjection;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using ServiceStack.OrmLite;
using zero.Context;
using zero.Models;
using zero.Utils;
@@ -19,12 +22,22 @@ public partial class DbOperations : IDbOperations
protected IServiceProvider Services { get; private set; }
protected IDbConnection Db { get; private set; }
public DbOperations(StoreContext context)
public DbOperations(StoreContext context, IDbConnection db)
{
Context = context.Context;
Services = context.Services;
Flavors = context.Options.For<FlavorOptions>();
Db = db;
}
/// <inheritdoc />
public bool EnsureTableExists<T>() where T : ZeroIdEntity
{
return Db.CreateTableIfNotExists<T>();
}
@@ -80,16 +93,39 @@ public partial class DbOperations : IDbOperations
return await validator.ValidateAsync(model);
}
/// <inheritdoc />
public virtual T WhenActive<T>(T model) where T : ZeroIdEntity, new()
{
return model; // TODO should we really use this? I tried to get data in a custom backend but couldn't because of this
//return model != null && (model is not ZeroEntity || (model as ZeroEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default;
}
}
public interface IDbOperations
{
/// <summary>
/// Create a table if not existing
/// </summary>
bool EnsureTableExists<T>() where T : ZeroIdEntity;
/// <summary>
/// Generate model Id by using configured document store conventions
/// </summary>
Task<string> GenerateId<T>(T model) where T : ZeroIdEntity;
/// <summary>
/// Do only return the model when it is set to active or inactive entities are included with IncludeInactive()
/// </summary>
T WhenActive<T>(T model) where T : ZeroIdEntity, new();
/// <summary>
/// Validates an entity
/// </summary>
Task<ValidationResult> Validate<T>(T model) where T : ZeroIdEntity, new();
/// <summary>
/// Generate values for all properties (incl. nested) which contain the [GenerateId] attribute
/// </summary>
@@ -99,4 +135,39 @@ public interface IDbOperations
/// Automatically fill base properties of a ZeroEntity
/// </summary>
T PrepareForSave<T>(T model) where T : ZeroIdEntity;
/// <summary>
/// Get an entity by Id
/// </summary>
Task<T> Load<T>(string id, string changeVector = null) where T : ZeroIdEntity, new();
/// <summary>
/// Get entities by ids
/// </summary>
Task<Dictionary<string, T>> Load<T>(IEnumerable<string> ids) where T : ZeroIdEntity, new();
/// <summary>
/// Get entities by ids
/// </summary>
Task<List<T>> LoadAsList<T>(IEnumerable<string> ids) where T : ZeroIdEntity, new();
/// <summary>
/// Creates an entity with an optional validator
/// </summary>
Task<Result<T>> Create<T>(T model, Func<T, Task<ValidationResult>> validate = null) where T : ZeroIdEntity, new();
/// <summary>
/// Updates an entity with an optional validator
/// </summary>
Task<Result<T>> Update<T>(T model, Func<T, Task<ValidationResult>> validate = null) where T : ZeroIdEntity, new();
/// <summary>
/// Deletes an entity
/// </summary>
Task<Result<T>> Delete<T>(T model) where T : ZeroIdEntity, new();
/// <summary>
/// Deletes an entity
/// </summary>
Task<Result<T>> Delete<T>(string id) where T : ZeroIdEntity, new();
}
+6 -1
View File
@@ -1,6 +1,11 @@
namespace zero.Sqlite;
using System;
using System.Data;
namespace zero.Sqlite;
public class SqliteOptions
{
public string ConnectionString { get; set; }
public Action<IDbConnection> OnConnectionCreate { get; set; }
}
+5 -3
View File
@@ -36,17 +36,19 @@ internal class ZeroSqliteModule : ZeroModule
protected IDbConnectionFactory CreateDbConnectionFactory(IServiceProvider services)
{
IZeroOptions options = services.GetService<IZeroOptions>();
SqliteOptions ravenOptions = options.For<SqliteOptions>();
return new OrmLiteConnectionFactory(ravenOptions.ConnectionString, SqliteDialect.Provider);
SqliteOptions sqliteOptions = options.For<SqliteOptions>();
return new OrmLiteConnectionFactory(sqliteOptions.ConnectionString, SqliteDialect.Provider);
}
protected IDbConnection CreateDbConnection(IServiceProvider services)
{
IDbConnectionFactory factory = services.GetService<IDbConnectionFactory>();
IZeroOptions options = services.GetService<IZeroOptions>();
SqliteOptions sqliteOptions = options.For<SqliteOptions>();
IDbConnection db = factory.CreateDbConnection();
db.Open();
//db.CreateTableIfNotExists<News>();
sqliteOptions.OnConnectionCreate?.Invoke(db);
return db;
}
}