MACRIM.Core
6.8.1
dotnet add package MACRIM.Core --version 6.8.1
NuGet\Install-Package MACRIM.Core -Version 6.8.1
<PackageReference Include="MACRIM.Core" Version="6.8.1" />
<PackageVersion Include="MACRIM.Core" Version="6.8.1" />
<PackageReference Include="MACRIM.Core" />
paket add MACRIM.Core --version 6.8.1
#r "nuget: MACRIM.Core, 6.8.1"
#:package MACRIM.Core@6.8.1
#addin nuget:?package=MACRIM.Core&version=6.8.1
#tool nuget:?package=MACRIM.Core&version=6.8.1
MACRIM.Caching Project
Purpose
The MACRIM.Caching
project extends the MACRIM framework’s caching capabilities to support table-based caching in Redis, replacing the in-memory LookupService
with a scalable, persistent solution. It caches IEntity
instances for SQL tables (or other data sources), enabling fast lookups for batch processing (5-10K rows) with dynamic indexing and segmented caching. The rebuilt RedisService
addresses compile errors from the original CacheService<T>
, integrating with IConfigService
for configuration-driven caching.
Phase 1: Table-Based Caching
Phase 1 implements caching in Redis, focusing on:
- Caching: Stores
IEntity
instances as Redis Hashes (segment:cacheName:segment:catalog
) with TTL fromexpires
. - Indexing: Indexes
jsondata
viakeypattern
expressions (e.g.,{catalog},{name},{catalog}:{name}
) using Redis Sets (segment:cacheName:index:value
). - Loading: Retrieves data via
IRepositoryBypass.SearchAsync
, usingLoadJSON
forIEntity
creation. Synchronized to ensure only one load per cache, with concurrent requests waiting. - Upsert: Updates cache with
IEntity
instances (SQL sync deferred to Phase 2). - Configuration: Uses
IConfigService
(_configs[segment]["commands"]["caching"][cacheName]
) fortable
,profile
,keypattern
,expires
,segment
. - Synchronization: Syncs on initial load, TTL expiration, and cache misses. No ongoing sync within TTL.
- Lookups: Supports lookups by any
keypattern
index entry (e.g.,svc123
) usingGetRecord
/GetRecordAsync
, with expressions evaluated viaApplyLookups
.
Usage Example
To perform a lookup like {?:all-services(svc123,(catalog))}
(retrieve a service by svc123
and return its catalog
):
Configure Cache:
<command name="all-services" table="services" profile="sql" expires="600" segment="true" keypattern="{catalog},{name},{servicename},{catalog}:{name}"> <methods> <method name="retrieve" type="MACRIM.Data.Extensions.Base.Search"> <parameters> <parameter name="model" value="services" /> <parameter name="profile" value="sql" /> <parameter name="segment" value="{var:tenant}" /> </parameters> </method> </methods>
Set Up DI:
services.AddSingleton<ITableCachingService, RedisService>();
Update ApplyLookups (as static extension method):
case "lookup": case "?": var cacheSvc = me.Context.Services.GetRequiredService<ITableCachingService>(); if (cacheSvc == null) break; var argStart = what.IndexOf('('); var instruct = argStart > -1 ? what.Substring(++argStart) : string.Empty; instruct = instruct.Length > 0 ? instruct.Remove(instruct.Length - 1) : string.Empty; what = argStart > -1 ? what.Substring(0, argStart - 1) : what; if (what == ".") { var pstart = instruct.IndexOf(','); var lkey = instruct.Split(',').FirstOrDefault(); var lexp = pstart > -1 ? instruct.Substring(++pstart) : string.Empty; if (lkey.StartsWith('(')) lkey = (string)me.ApplyLookups(lkey.Replace('(', '{').Replace(')', '}'), returnType, subReturnType, command, defaults); var el = me.LookupCache.ContainsKey(lkey) ? me.LookupCache[lkey] : me.Spawn(); if (el != null && el.Found) { oFound = el.ApplyLookups(lexp.Replace('(', '{').Replace(')', '}'), returnType, subReturnType, command, defaults); } } else { var pstart = instruct.IndexOf(','); var lkey = instruct.Split(',').FirstOrDefault(); var lexp = pstart > -1 ? instruct.Substring(++pstart) : string.Empty; if (string.IsNullOrEmpty(lkey) || string.IsNullOrEmpty(lexp)) { oFound = string.Empty; break; } var keyvalue = me.Attributes.GetValueOrDefault(lkey, ""); if (lkey.StartsWith('(')) keyvalue = (string)me.ApplyLookups(lkey.Replace('(', '{').Replace(')', '}'), returnType, subReturnType, command, defaults); if (string.IsNullOrEmpty(keyvalue)) { oFound = string.Empty; break; } var record = cacheSvc.GetRecord( keyvalue: keyvalue, cacheName: what, segment: me.Context?.Segment ?? "default" ); if (record != null && record.Found) { oFound = record.ApplyLookups(lexp.Replace('(', '{').Replace(')', '}'), returnType, subReturnType, command, defaults); } else { oFound = string.Empty; } } break;
Test Lookup:
- Input:
{?:all-services(svc123,(catalog))}
withme.Attributes["servicename"] = "svc123"
. - Output:
oFound = "cat1"
(fromrecord.ApplyLookups("{catalog}", ...)
). - Complex Input:
{?:all-services(svc123,(user:{id}:name:upper))}
→oFound = "JOHN"
.
- Input:
Phase 2: Queuing (Deferred)
Phase 2 will implement async SQL sync using Redis Lists, leveraging StartWatchingList
, Polly
retries, and a poison queue.
Key Components
- Interfaces (in
MACRIM.Core.Abstractions
):ITableCachingService
:LoadCacheAsync
,GetRecordAsync
,GetRecord
,UpsertRecordAsync
(version: a37df99a-233d-424d-9c2d-fd5b56875548).IQueueService
,QueueTask
(Phase 2).
- RedisService (in
MACRIM.Caching
):- Implements
ICachingService
andITableCachingService
(version: b9f8c7b5-4d2e-4f9b-9e2c-6f7a8b9c0f4e). - Uses
ConnectionHelper
for Redis. - Resolves
IRepositoryBypass
viaIContextService.Services
.
- Implements
- Configuration:
IEntity
with attributes (table
,profile
,keypattern
,expires
,segment
).
IEntity Mapping
- segment:
Attributes["segment"]
, partition key. - catalog:
Attributes["catalog"]
, typicallyKey
. - jsondata:
Attributes
(e.g.,{"segment": "seg1", "catalog": "cat1", "name": "John", "age": "30"}
). - Key:
Attributes["catalog"]
. - Element: Table name (e.g.,
contacts
). - Value: Null.
- Index:
keypattern
expressions (e.g.,Index["cat1"]
,Index["cat1:John"]
).
Implementation Status
- Phase 1 (Complete):
RedisService.cs
rebuilt for caching, supporting lookups by anykeypattern
index entry usingkeyvalue
(e.g.,svc123
).- Removed
profile
parameter, deriving it from configuration. - Fixed batch execution error (
ITransaction.ExecuteAsync
). - Added synchronous
GetRecord
forApplyLookups
, optimized for high-frequency calls without logging. - Synchronized
LoadCacheAsync
to ensure one load per cache, with concurrent requests waiting.
- Phase 2 (Deferred): Queuing pending prioritization.
Next Steps
Test Phase 1:
- Test
RedisService
with sample data (e.g.,contacts
,keypattern="{catalog},{name},{ssn},{catalog}:{name}"
). - Verify concurrent lookups (e.g., 20 queued processes) to ensure single cache load.
- Validate
{?:cacheName(keyvalue,(expression))}
inApplyLookups
(e.g.,{?:all-services(svc123,(catalog))}
).
- Test
Sync Enhancements:
- Decide if
sync_version
checks are needed for Phase 1 to detect table changes within TTL. - Plan Phase 2 for cache-to-SQL sync.
- Decide if
DI Confirmation:
- Confirm
Microsoft.Extensions.DependencyInjection
and singleton lifetime.
- Confirm
T Handling:
- Confirm
BaseEntityConvertible
forIRepository<T>
, or specify models (e.g.,Documents
).
- Confirm
Configuration:
- Provide example
IEntity
output for_configs[segment]["commands"]["caching"][cacheName]
.
- Provide example
Phase 2:
- Define queuing requirements when ready.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Azure.Messaging.ServiceBus (>= 7.19.0)
- Azure.Security.KeyVault.Certificates (>= 4.5.1)
- Azure.Security.KeyVault.Secrets (>= 4.5.0)
- Azure.Storage.Blobs (>= 12.24.0)
- Base64Helper (>= 0.0.7)
- BouncyCastle.Cryptography (>= 2.4.0)
- CsvHelper (>= 33.0.1)
- MailKit (>= 4.5.0)
- Microsoft.AspNetCore.Authentication.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Azure.Cosmos (>= 3.36.0)
- Microsoft.Azure.Functions.Extensions (>= 1.1.0)
- Microsoft.Azure.WebJobs (>= 3.0.39)
- Microsoft.Azure.WebJobs.Extensions.Storage (>= 5.2.1)
- Microsoft.CodeAnalysis.CSharp.Scripting (>= 4.14.0)
- Microsoft.Data.SqlClient (>= 5.1.5)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Configuration.AzureKeyVault (>= 3.1.24)
- Microsoft.Extensions.DependencyInjection (>= 9.0.2)
- Microsoft.Extensions.Http (>= 9.0.2)
- Microsoft.Extensions.Logging (>= 9.0.2)
- Microsoft.Extensions.Options (>= 9.0.2)
- MimeKit (>= 4.7.1)
- System.IdentityModel.Tokens.Jwt (>= 7.3.1)
- Twilio (>= 6.14.1)
NuGet packages (20)
Showing the top 5 NuGet packages that depend on MACRIM.Core:
Package | Downloads |
---|---|
MACRIM.Extensions
Package of IEntity extensions for MSSQL, Azure, PDF and more |
|
MACRIM.Encryption
Default encryption provider used in MACRIM.net |
|
MACRIM.Models
Support for Documents, Events, and Reports |
|
MACRIM.Web
Controllers, services and middleware supporting MACRIM.net web applications |
|
MACRIM.Transfers
IEntity Extensions related to file transfer technologies |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated | |
---|---|---|---|
6.8.1 | 185 | 7/31/2025 | |
6.6.15 | 272 | 6/15/2025 | |
6.6.15-alpha.2 | 300 | 6/12/2025 | |
6.5.15 | 172 | 5/24/2025 | |
5.5.1 | 274 | 6/13/2024 | |
4.12.15 | 592 | 12/15/2023 | |
4.11.1 | 479 | 11/2/2023 | |
4.10.15 | 453 | 10/20/2023 | |
4.2.1 | 391 | 1/30/2023 | |
2.11.5-alpha-5 | 633 | 10/24/2021 | |
2.10.23 | 1,719 | 10/24/2021 | |
2.10.22 | 13,240 | 10/23/2021 |