|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|