DataMagic.Abstractions
1.2.0
dotnet add package DataMagic.Abstractions --version 1.2.0
NuGet\Install-Package DataMagic.Abstractions -Version 1.2.0
<PackageReference Include="DataMagic.Abstractions" Version="1.2.0" />
<PackageVersion Include="DataMagic.Abstractions" Version="1.2.0" />
<PackageReference Include="DataMagic.Abstractions" />
paket add DataMagic.Abstractions --version 1.2.0
#r "nuget: DataMagic.Abstractions, 1.2.0"
#:package DataMagic.Abstractions@1.2.0
#addin nuget:?package=DataMagic.Abstractions&version=1.2.0
#tool nuget:?package=DataMagic.Abstractions&version=1.2.0
DataMagic.Abstractions
DataMagic.Abstractions is a small .NET Standard 2.1 library of shared contracts and models for querying and updating data: filters, sorting, paging, search results, entity interfaces and partial updates.
It contains no query logic and no database driver. Filters and pagers only describe a request; your data access layer (EF Core, Dapper, MongoDB, ...) reads them and builds the actual query.
Installation
dotnet add package DataMagic.Abstractions
Contents
| Namespace | Contains |
|---|---|
DataMagic.Abstractions.Constants |
PagerKinds, SortDirections, DatabaseTypes |
DataMagic.Abstractions.Constants.Operators |
Comparison operators and text filter modes |
DataMagic.Abstractions.Interfaces |
IPager, ISearchResult<T>, IDatabaseOptions, IUpdatableProperty<T>, IImmutableEntity<TId, TTime>, IMutableEntity<TId, TTime> |
DataMagic.Abstractions.Models |
Date, SearchResult<T>, SortableProperty, UpdatableProperty<T> |
DataMagic.Abstractions.Models.Filters |
Filter models |
DataMagic.Abstractions.Models.Pagers |
IPager implementations |
DataMagic.Abstractions.Models.Databases |
IDatabaseOptions implementations |
Constants
PagerKinds
String values that identify a pager type. Each pager exposes one through IPager.Kind, so you can tell pagers apart after they cross an API boundary.
| Constant | Value | Used by |
|---|---|---|
PagerKinds.Default |
"default" |
DefaultPager |
PagerKinds.SkipLimit |
"skipLimit" |
SkipLimitPager |
PagerKinds.Index |
"index" |
IndexPager |
Enums
| Enum | Members | Used by |
|---|---|---|
SortDirections |
None, Ascending, Descending |
SortableProperty |
DatabaseTypes |
Mongo, Postgresql, Redis, SqlServer |
IDatabaseOptions |
NumericComparisonOperators |
SmallerThan, SmallerThanEqualTo, Equal, GreaterThanEqualTo, GreaterThan, None |
NumericFilter<T> |
DateComparisonOperators |
SmallerThan, SmallerThanEqualTo, Equal, GreaterThanEqualTo, GreaterThan, None |
DateFilter |
DateTimeComparisonOperators |
SmallerThan, SmallerThanEqualTo, Equal, GreaterThanEqualTo, GreaterThan |
DateTimeFilter |
TextFilterModes |
Equal, EqualIgnoreCase, Contains, StartWith, EndWith, None |
TextFilter |
An operator left unset takes the enum's first member. For the numeric, date and date-time operators that is SmallerThan, not None, so always set the operator explicitly.
Filters
Each filter pairs a value with the operator to apply. Range filters combine two filters into a lower and an upper bound.
| Class | Responsibility |
|---|---|
TextFilter |
Matches a string Value using a Mode |
NumericFilter<T> |
Compares a numeric Value using an Operator |
NumericRangeFilter<TFrom, TTo> |
Numeric range with From and To |
DateFilter |
Compares a Date value using an Operator |
DateRangeFilter |
Date range with optional From and To |
DateTimeFilter |
Compares a DateTime? value using an Operator |
DateTimeRangeFilter |
Date-time range with required constructor arguments from and to |
using DataMagic.Abstractions.Constants.Operators;
using DataMagic.Abstractions.Models;
using DataMagic.Abstractions.Models.Filters;
var name = new TextFilter { Value = "linh", Mode = TextFilterModes.Contains };
var price = new NumericRangeFilter<decimal, decimal>(
new NumericFilter<decimal>(10m, NumericComparisonOperators.GreaterThanEqualTo),
new NumericFilter<decimal>(50m, NumericComparisonOperators.SmallerThan));
var birthday = new DateRangeFilter
{
From = new DateFilter
{
Value = new Date(2000, 1, 1),
Operator = DateComparisonOperators.GreaterThanEqualTo
}
};
var createdTime = new DateTimeRangeFilter(
new DateTimeFilter(new DateTime(2026, 1, 1), DateTimeComparisonOperators.GreaterThanEqualTo),
new DateTimeFilter("2026-02-01", DateTimeComparisonOperators.SmallerThan));
NumericFilter<T>.ValidNumericTypes() returns the supported numeric types and their nullable forms. The constructor does not currently reject other types, so check T yourself if it comes from outside your code.
Your query layer applies the filter, for example:
if (name.Value != null && name.Mode == TextFilterModes.Contains)
query = query.Where(x => x.Name.Contains(name.Value));
Sorting
SortableProperty names a property and the direction to sort it in.
using DataMagic.Abstractions.Constants;
using DataMagic.Abstractions.Models;
var sort = new SortableProperty { Name = "createdTime", Direction = SortDirections.Descending };
Paging
IPager tells your data access layer what to fetch:
| Member | Responsibility |
|---|---|
Kind |
Pager type, one of PagerKinds |
ShouldItemsQueried() |
Whether to fetch the items. Always false when the page size is below 1 |
ShouldItemsCounted() |
Whether to count all matching records |
GetSkippedRecords() |
Number of records to skip |
GetTotalRecords() |
Number of records to take, which is the page size rather than the total count |
| Class | Input | Skipped records |
|---|---|---|
DefaultPager |
1-based page, totalRecords |
(page - 1) * totalRecords |
IndexPager |
0-based pageIndex, totalRecords |
pageIndex * totalRecords |
SkipLimitPager |
skippedRecords, totalRecords |
skippedRecords |
All pagers throw ArgumentException on out-of-range input.
using DataMagic.Abstractions.Interfaces;
using DataMagic.Abstractions.Models.Pagers;
IPager firstPage = new DefaultPager(1, 20);
IPager secondPage = new IndexPager(1, 20, true, true);
IPager offset = new SkipLimitPager(40, 20);
// Count only, without fetching items.
IPager countOnly = new DefaultPager(1, 20, false, true);
DefaultPager and SkipLimitPager query and count by default. IndexPager does neither unless you say so: new IndexPager(0, 20) returns false from both ShouldItemsQueried() and ShouldItemsCounted(), so pass both flags as shown above.
Use the pager in your query, for example:
var totalRecords = pager.ShouldItemsCounted() ? await query.CountAsync() : 0;
var items = pager.ShouldItemsQueried()
? await query.Skip((int)pager.GetSkippedRecords()).Take((int)pager.GetTotalRecords()).ToListAsync()
: new List<Product>();
Search results
ISearchResult<T> and its implementation SearchResult<T> hold one page of Items and the TotalRecords matching the query. A null item sequence becomes an empty array.
using DataMagic.Abstractions.Interfaces;
using DataMagic.Abstractions.Models;
ISearchResult<Product> result = new SearchResult<Product>(items, totalRecords);
var product = result.FirstOrDefault();
| Method | When there are no items |
|---|---|
First(), FirstAsync() |
Throws InvalidOperationException |
FirstOrDefault(), FirstOrDefaultAsync() |
Returns default |
Entities
| Interface | Responsibility |
|---|---|
IImmutableEntity<TId, TTime> |
Entity that never changes after creation. Exposes read-only Id and CreatedTime |
IMutableEntity<TId, TTime> |
Extends IImmutableEntity<TId, TTime> with a settable UpdatedTime |
TId and TTime are generic, so you choose the key and time types.
using DataMagic.Abstractions.Interfaces;
public class AuditLog : IImmutableEntity<Guid, DateTime>
{
public Guid Id { get; init; }
public DateTime CreatedTime { get; init; }
}
public class Product : IMutableEntity<Guid, DateTime>
{
public Guid Id { get; init; }
public DateTime CreatedTime { get; init; }
public DateTime UpdatedTime { get; set; }
}
The init accessor needs .NET 5 or later. On older targets use set instead.
UpdatedTime is declared as TTime?, but C# only makes that nullable when TTime is a reference type. With DateTime it is a plain DateTime, so an entity that was never updated holds default(DateTime) rather than null.
Partial updates
IUpdatableProperty<T> and its implementation UpdatableProperty<T> pair a Value with a HasModified flag. This separates "leave this field alone" from "set this field to null", which a plain nullable property cannot do.
using DataMagic.Abstractions.Models;
public class UpdateProductModel
{
public UpdatableProperty<string?>? Description { get; set; }
}
var model = new UpdateProductModel
{
Description = new UpdatableProperty<string?>(null, true)
};
if (model.Description?.HasModified == true)
product.Description = model.Description.Value;
Database options
IDatabaseOptions describes how to reach a database with separate fields instead of a connection string. Your data access layer builds the driver's connection settings from them.
| Member | Responsibility |
|---|---|
Type |
Database type, one of DatabaseTypes |
Host |
Host name or IP address. Defaults to localhost |
Port |
Server port. Defaults to the database's standard port |
Username, Password |
Credentials. Leave null when the server has no authentication |
UseTls |
Whether to encrypt the connection. Defaults to false |
| Class | Default port | Extra members |
|---|---|---|
MongoDatabaseOptions |
27017 |
DatabaseName, AuthenticationDatabase (defaults to admin), ReplicaSet |
PostgresqlDatabaseOptions |
5432 |
DatabaseName, Schema |
RedisDatabaseOptions |
6379 |
Database (logical database index, defaults to 0) |
SqlServerDatabaseOptions |
1433 |
DatabaseName, InstanceName, TrustServerCertificate |
Each class implements IDatabaseOptions directly. To support another database, implement the interface yourself.
All members except Type are settable, so the classes bind from configuration:
{
"Databases": {
"Mongo": {
"Host": "mongo.internal",
"Username": "asset-service",
"Password": "<from secret store>",
"DatabaseName": "assets"
}
}
}
using DataMagic.Abstractions.Models.Databases;
var mongo = builder.Configuration.GetSection("Databases:Mongo").Get<MongoDatabaseOptions>()!;
Then map the fields to the driver, for example with MongoDB.Driver:
var settings = new MongoClientSettings
{
Server = new MongoServerAddress(mongo.Host, mongo.Port),
UseTls = mongo.UseTls,
ReplicaSetName = mongo.ReplicaSet
};
if (mongo.Username != null)
settings.Credential = MongoCredential.CreateCredential(
mongo.AuthenticationDatabase, mongo.Username, mongo.Password);
var database = new MongoClient(settings).GetDatabase(mongo.DatabaseName);
Date
Date is a date without a time, intended for values received from a front end.
using DataMagic.Abstractions.Models;
var date = new Date(2026, 11, 2);
DateTime midnight = date.ToDateTime();
DateTime withTime = date.ToDateTime(14, 30, 0);
var isLater = date > new Date(2026, 1, 1);
var isSame = date.Equals(new Date(2026, 11, 2));
- The constructor throws
Exceptionfor a year before 1753, or for an invalid month or day. - Derive from
Dateand overrideIsValidYear,IsValidMonthorIsValidDateto change the rules. - Use
Equalsto compare dates. The==and!=operators currently overflow the stack and terminate the process. >,>=,<and<=work but throwNullReferenceExceptionif either side isnull.Datedoes not overrideGetHashCode, so do not use it as a dictionary key or in aHashSet.- Validation treats every fourth year as a leap year, so
new Date(1900, 2, 29)is accepted and fails later when converted to aDateTime.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
NuGet packages (9)
Showing the top 5 NuGet packages that depend on DataMagic.Abstractions:
| Package | Downloads |
|---|---|
|
DataMagic.MongoDatabase
Package Description |
|
|
DataMagic.Apis.Abstractions
Package Description |
|
|
ApiFeatures.Modules.Cores
Package Description |
|
|
ApiFeatures.Mqtt.Apis
Package Description |
|
|
IotVn.CoreFeatures.JsonConverters.Newtonsoft
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.