|
|
|
@ -10,118 +10,120 @@ using System.Collections.Immutable;
|
|
|
|
|
namespace Connected.Entities.Caching;
|
|
|
|
|
|
|
|
|
|
public abstract class EntityCacheClient<TEntity, TPrimaryKey> : StatefulCacheClient<TEntity, TPrimaryKey>, IEntityCacheClient<TEntity, TPrimaryKey>
|
|
|
|
|
where TEntity : class, IPrimaryKey<TPrimaryKey>, IEntity
|
|
|
|
|
where TPrimaryKey : notnull
|
|
|
|
|
where TEntity : class, IPrimaryKey<TPrimaryKey>, IEntity
|
|
|
|
|
where TPrimaryKey : notnull
|
|
|
|
|
{
|
|
|
|
|
protected EntityCacheClient(IEntityCacheContext context, string key) : base(context.Cache, key)
|
|
|
|
|
{
|
|
|
|
|
Context = context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEntityCacheContext Context { get; }
|
|
|
|
|
|
|
|
|
|
protected override sealed async Task OnInitializing()
|
|
|
|
|
{
|
|
|
|
|
using var ctx = Context.ContextProvider.Create();
|
|
|
|
|
|
|
|
|
|
var transaction = ctx.GetService<ITransactionContext?>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (await OnInitializing(ctx) is ImmutableList<TEntity> ds)
|
|
|
|
|
{
|
|
|
|
|
foreach (var r in ds)
|
|
|
|
|
Set(r.Id, r, TimeSpan.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Commit();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Rollback();
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<ImmutableList<TEntity>?> OnInitializing(IContext context)
|
|
|
|
|
{
|
|
|
|
|
if (context.GetService<IStorageProvider>() is not IStorageProvider db)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
return await (from dc in db.Open<TEntity>()
|
|
|
|
|
select dc).AsEntities();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task OnInvalidate(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
using var ctx = Context.ContextProvider.Create();
|
|
|
|
|
var transaction = ctx.GetService<ITransactionContext>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (OnInvalidating(ctx, id) is TEntity entity && entity is IPrimaryKey<TPrimaryKey> pk)
|
|
|
|
|
Set(pk.Id, entity, TimeSpan.Zero);
|
|
|
|
|
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Commit();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Rollback();
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<TEntity?> OnInvalidating(IContext context, TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
if (context.GetService<IStorageProvider>() is not IStorageProvider provider)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
return await (from dc in provider.Open<TEntity>()
|
|
|
|
|
where TypeComparer.Compare(dc.Id, id)
|
|
|
|
|
select dc).AsEntity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async Task IEntityCacheClient<TEntity, TPrimaryKey>.Refresh(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
await Refresh(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async Task IEntityCacheClient<TEntity, TPrimaryKey>.Remove(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
await Remove(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Set(TPrimaryKey id, TEntity instance)
|
|
|
|
|
{
|
|
|
|
|
Set(id, instance, TimeSpan.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Set(TPrimaryKey id, TEntity instance, TimeSpan duration)
|
|
|
|
|
{
|
|
|
|
|
if (instance is IConcurrentEntity<TPrimaryKey> concurrent)
|
|
|
|
|
{
|
|
|
|
|
if (Get(id) is TEntity existing && existing is IConcurrentEntity<TPrimaryKey> existingConcurrent)
|
|
|
|
|
{
|
|
|
|
|
lock (existingConcurrent)
|
|
|
|
|
{
|
|
|
|
|
if (existingConcurrent.Sync != concurrent.Sync)
|
|
|
|
|
throw new InvalidOperationException(SR.ErrConcurrent);
|
|
|
|
|
|
|
|
|
|
concurrent.GetType().GetProperty(nameof(IConcurrentEntity<TPrimaryKey>.Sync))?.SetValue(concurrent, concurrent.Sync + 1);
|
|
|
|
|
|
|
|
|
|
Set(id, instance, duration);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base.Set(id, instance, duration);
|
|
|
|
|
}
|
|
|
|
|
protected EntityCacheClient(IEntityCacheContext context, string key) : base(context.Cache, key)
|
|
|
|
|
{
|
|
|
|
|
Context = context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEntityCacheContext Context { get; }
|
|
|
|
|
|
|
|
|
|
protected override sealed async Task OnInitializing()
|
|
|
|
|
{
|
|
|
|
|
using var ctx = Context.ContextProvider.Create();
|
|
|
|
|
|
|
|
|
|
var transaction = ctx.GetService<ITransactionContext?>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (await OnInitializing(ctx) is ImmutableList<TEntity> ds)
|
|
|
|
|
{
|
|
|
|
|
foreach (var r in ds)
|
|
|
|
|
Set(r.Id, r, TimeSpan.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Commit();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Rollback();
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<ImmutableList<TEntity>?> OnInitializing(IContext context)
|
|
|
|
|
{
|
|
|
|
|
if (context.GetService<IStorageProvider>() is not IStorageProvider db)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
return await (from dc in db.Open<TEntity>()
|
|
|
|
|
select dc).AsEntities();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override async Task OnInvalidate(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
using var ctx = Context.ContextProvider.Create();
|
|
|
|
|
var transaction = ctx.GetService<ITransactionContext>();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (OnInvalidating(ctx, id) is TEntity entity && entity is IPrimaryKey<TPrimaryKey> pk)
|
|
|
|
|
Set(pk.Id, entity, TimeSpan.Zero);
|
|
|
|
|
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Commit();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
if (transaction is not null)
|
|
|
|
|
await transaction.Rollback();
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual async Task<TEntity?> OnInvalidating(IContext context, TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
if (context.GetService<IStorageProvider>() is not IStorageProvider provider)
|
|
|
|
|
return default;
|
|
|
|
|
|
|
|
|
|
var entities = provider.Open<TEntity>().AsEntities();
|
|
|
|
|
|
|
|
|
|
return await (from dc in provider.Open<TEntity>()
|
|
|
|
|
where TypeComparer.Compare(dc.Id, id)
|
|
|
|
|
select dc).AsEntity();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async Task IEntityCacheClient<TEntity, TPrimaryKey>.Refresh(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
await Refresh(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async Task IEntityCacheClient<TEntity, TPrimaryKey>.Remove(TPrimaryKey id)
|
|
|
|
|
{
|
|
|
|
|
await Remove(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Set(TPrimaryKey id, TEntity instance)
|
|
|
|
|
{
|
|
|
|
|
Set(id, instance, TimeSpan.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Set(TPrimaryKey id, TEntity instance, TimeSpan duration)
|
|
|
|
|
{
|
|
|
|
|
if (instance is IConcurrentEntity<TPrimaryKey> concurrent)
|
|
|
|
|
{
|
|
|
|
|
if (Get(id) is TEntity existing && existing is IConcurrentEntity<TPrimaryKey> existingConcurrent)
|
|
|
|
|
{
|
|
|
|
|
lock (existingConcurrent)
|
|
|
|
|
{
|
|
|
|
|
if (existingConcurrent.Sync != concurrent.Sync)
|
|
|
|
|
throw new InvalidOperationException(SR.ErrConcurrent);
|
|
|
|
|
|
|
|
|
|
concurrent.GetType().GetProperty(nameof(IConcurrentEntity<TPrimaryKey>.Sync))?.SetValue(concurrent, concurrent.Sync + 1);
|
|
|
|
|
|
|
|
|
|
Set(id, instance, duration);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base.Set(id, instance, duration);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|