You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Common.Types/Common.Types/TaxRates/TaxRateOps.cs

252 lines
7.8 KiB

2 years ago
using System.Collections.Immutable;
using Connected.Entities;
using Connected.Entities.Storage;
using Connected.Notifications.Events;
using Connected.ServiceModel;
using Connected.Services;
namespace Common.Types.TaxRates;
internal class TaxRateOps
{
/// <summary>
/// Queries all <see cref="ITaxRate"/> records.
/// </summary>
internal sealed class Query : ServiceFunction<QueryArgs, ImmutableList<ITaxRate>?>
{
/// <summary>
/// Creates a new instance of the <see cref="Query"/> class.
/// </summary>
/// <param name="cache">The cache where the records are stored.</param>
public Query(ITaxRateCache cache, IStorageProvider storage)
{
Cache = cache;
Storage = storage;
}
private ITaxRateCache Cache { get; }
public IStorageProvider Storage { get; }
protected override async Task<ImmutableList<ITaxRate>?> OnInvoke()
{
var r = await (from dc in Storage.Open<TaxRate>() select dc).WithArguments(Arguments).AsEntities<ITaxRate>();
/*
* We simply return all records that exist in the system.
*/
return await (from dc in Cache select dc).WithArguments(Arguments).AsEntities<ITaxRate>();
}
}
/// <summary>
/// Returns <see cref="ITaxRate"/> with the specified id or null
/// if the record for the specified id does not exist.
/// </summary>
internal sealed class Select : ServiceFunction<PrimaryKeyArgs<int>, ITaxRate?>
{
public Select(ITaxRateCache cache)
{
Cache = cache;
}
private ITaxRateCache Cache { get; }
protected override async Task<ITaxRate?> OnInvoke()
{
/*
* Filter cache by the id.
*/
return await (from dc in Cache where dc.Id == Arguments.Id select dc).AsEntity();
}
}
/// <summary>
/// Returns <see cref="ITaxRate"/> with the specified name and rate or null if
/// the record with the specified arguments does not exist.
/// </summary>
internal sealed class SelectByRate : ServiceFunction<TaxRateArgs, ITaxRate?>
{
public SelectByRate(ITaxRateCache cache)
{
Cache = cache;
}
private ITaxRateCache Cache { get; }
protected override async Task<ITaxRate?> OnInvoke()
{
return await (from dc in Cache
where string.Equals(dc.Name, Arguments.Name, StringComparison.OrdinalIgnoreCase) && dc.Rate == Arguments.Rate
select dc).AsEntity();
}
}
/// <summary>
/// Returns List of <see cref="ITaxRate"/> for the specified set of ids. Use this method when joining data
/// to other entities.
/// </summary>
internal sealed class Lookup : ServiceFunction<PrimaryKeyListArgs<int>, ImmutableList<ITaxRate>?>
{
public Lookup(ITaxRateCache cache)
{
Cache = cache;
}
private ITaxRateCache Cache { get; }
protected override async Task<ImmutableList<ITaxRate>?> OnInvoke()
{
/*
* Use simple linq join for the specified set of ids.
*/
return await (from dc in Cache where Arguments.IdList.Contains(dc.Id) select dc).AsEntities<ITaxRate>();
}
}
/// <summary>
/// Inserts a new <see cref="ITaxRate"/> and returns its Id.
/// </summary>
internal sealed class Insert : ServiceFunction<InsertTaxRateArgs, int>
{
public Insert(ITaxRateService taxRateService, IStorageProvider storage, IEventService events, ITaxRateCache cache)
{
TaxRateService = taxRateService;
Storage = storage;
Events = events;
Cache = cache;
}
private ITaxRateService TaxRateService { get; }
private IStorageProvider Storage { get; }
private IEventService Events { get; }
private ITaxRateCache Cache { get; }
protected override async Task<int> OnInvoke()
{
/*
* First, create a new entity from the passed arguments and mark its state as new. This will
* signal the DatabaseContext to perform an insert operation when calling the Update.
*/
var entity = Arguments.AsEntity<TaxRate>(State.New);
/*
* Call update on the DatabaseContext. This call will return a new ITaxRate of the inserted
* entity.
*/
return (await Storage.Open<TaxRate>().Update(entity)).Id;
}
protected override async Task OnCommitted()
{
/*
* At this stage, the transaction has been commited which means
* record is definitely permanently stored in the database.
* We are making a simple call to the cache to refresh the item with the
* new id. Cache will query a database for new record and will store it
* in memory.
*/
await Cache.Refresh(Result);
/*
* Now trigger the inserted event which will notify all components hooked in the
* same process, out of process scale out instances and clients (wasm).
*/
await Events.Enqueue(this, TaxRateService, ServiceEvents.Inserted, Result);
}
}
/// <summary>
/// Permanently deletes a <see cref="ITaxRate"/> from the system.
/// </summary>
/// <remarks>
/// This method gets called after all <see cref="Server.Data.DataProtection.IDataProtectionMiddleware{TArgs}"/>
/// middleware passed successfuly.
/// </remarks>
internal sealed class Delete : ServiceAction<PrimaryKeyArgs<int>>
{
public Delete(ITaxRateService taxRateService, IStorageProvider storage, ITaxRateCache cache, IEventService events)
{
TaxRateService = taxRateService;
Storage = storage;
Cache = cache;
Events = events;
}
private ITaxRateService TaxRateService { get; }
private IStorageProvider Storage { get; }
private ITaxRateCache Cache { get; }
private IEventService Events { get; }
protected override async Task OnInvoke()
{
/*
* we don't need a reference to a record here because delete uses only
* an id. The overhead would only occur in cases if the record wouldn't exist
* but this is not the job of the operation. The Validators should theoretically
* reject such an operation.
*/
await Storage.Open<TaxRate>().Update(new TaxRate { Id = Arguments.Id, State = State.Deleted });
}
protected override async Task OnCommitted()
{
/*
* Once all the transactions have been commited remove the non existing record
* from the cache.
*/
await Cache.Remove(Arguments.Id);
/*
* And notify audience about the event.
*/
await Events.Enqueue(this, TaxRateService, ServiceEvents.Deleted, Arguments.Id);
}
}
/// <summary>
/// Updates the <see cref="ITaxRate"/> entity.
/// </summary>
internal sealed class Update : ServiceAction<UpdateTaxRateArgs>
{
public Update(ITaxRateService taxRateService, IStorageProvider storage, ITaxRateCache cache, IEventService events)
{
TaxRateService = taxRateService;
Storage = storage;
Cache = cache;
Events = events;
}
private IStorageProvider Storage { get; }
private ITaxRateCache Cache { get; }
private IEventService Events { get; }
private ITaxRateService TaxRateService { get; }
protected override async Task OnInvoke()
{
/*
* TaxRate is concurrency entity which means the platform takes care of
* data consistency. Data consistency means we can't overwrite updates made
* by others.
* Updating Concurrency entity thus requires a bit more logic. We must use a retry logic
* in case of Concurrency failure. We'll call Update method with reload lambda function.
*/
var entity = SetState(await Load());
await Storage.Open<TaxRate>().Update(entity, Arguments, async () =>
{
/*
* The concurrency exception occured.
* Refresh the entry from the cache. This will load a new version from
* a database.
*/
await Cache.Refresh(Arguments.Id);
return SetState(await Load());
});
}
private async Task<TaxRate?> Load() => await (from dc in Cache where dc.Id == Arguments.Id select dc).AsEntity();
protected override async Task OnCommitted()
{
/*
* Once the update is complete refresh the cache because the database assigned a new timestamp
* to an entity and any sunsequent update would fail anyway.
*/
await Cache.Refresh(Arguments.Id);
/*
* Now trigger the distributed event notifying the update has completed.
*/
await Events.Enqueue(this, TaxRateService, ServiceEvents.Updated, Arguments.Id);
}
}
}