new sqlite ops
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user