diff --git a/Directory.Build.props b/Directory.Build.props
index d4baf53..3ba56c2 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,6 +4,7 @@
Soulv Software (Pty) Ltd
Copyright (c) Soulv Software 2025
true
+ 13
https://github.com/MarkDerman/OrdinaryInfrastructure
MIT
https://github.com/MarkDerman/OrdinaryInfrastructure
diff --git a/Domain/Core/AbstractIdentityQuerySpecification.cs b/Domain/Core/AbstractIdentityQuerySpecification.cs
new file mode 100644
index 0000000..aa5bf73
--- /dev/null
+++ b/Domain/Core/AbstractIdentityQuerySpecification.cs
@@ -0,0 +1,25 @@
+using System.Linq.Expressions;
+using System.Numerics;
+
+namespace Odin.Domain;
+
+///
+public abstract class AbstractIdentityQuerySpecification
+ : AbstractQuerySpecification
+ where TAggregateRoot : class, IIdentityAggregateRoot
+ where TId : struct, IEqualityOperators
+{
+ ///
+ /// Default constructor for AbstractQuerySpecification
+ ///
+ ///
+ protected AbstractIdentityQuerySpecification(Expression>? criteria) : base(criteria)
+ {
+ }
+ ///
+ ///
+ ///
+ protected AbstractIdentityQuerySpecification()
+ {
+ }
+}
\ No newline at end of file
diff --git a/Domain/Core/AbstractQuerySpecification.cs b/Domain/Core/AbstractQuerySpecification.cs
new file mode 100644
index 0000000..b6ffcaf
--- /dev/null
+++ b/Domain/Core/AbstractQuerySpecification.cs
@@ -0,0 +1,93 @@
+using System.Linq.Expressions;
+
+namespace Odin.Domain;
+
+///
+public abstract class AbstractQuerySpecification
+ : IQuerySpecification
+ where TAggregateRoot : class, IAggregateRoot
+{
+ ///
+ /// Default constructor for AbstractQuerySpecification
+ ///
+ ///
+ protected AbstractQuerySpecification(Expression>? criteria)
+ {
+ Criteria = criteria;
+ }
+
+ ///
+ ///
+ ///
+ protected AbstractQuerySpecification()
+ {
+ }
+
+ ///
+ public Expression>? Criteria { get; protected set; }
+
+ ///
+ public IReadOnlyList>>? Includes => _includes;
+ private List>>? _includes;
+
+ ///
+ public Expression>? OrderBy { get; private set; }
+
+ ///
+ public Expression>? OrderByDescending { get; private set; }
+
+ ///
+ public int Take { get; private set; }
+
+ ///
+ public int Skip { get; private set; }
+
+ ///
+ public bool IsPagingEnabled { get; private set; }
+
+ ///
+ /// Adds a query include
+ ///
+ ///
+ protected void AddCriteria(Expression> includeExpression)
+ {
+ if (_includes == null) _includes = new List>>();
+ _includes.Add(includeExpression);
+ }
+
+ ///
+ /// Adds a query include
+ ///
+ ///
+ protected void AddInclude(Expression> includeExpression)
+ {
+ if (_includes == null) _includes = new List>>();
+ _includes.Add(includeExpression);
+ }
+
+ ///
+ /// Adds ordering ascending
+ ///
+ ///
+ protected void ApplyOrderBy(Expression> orderByExpression)
+ => OrderBy = orderByExpression;
+
+ ///
+ /// Adds ordering descending
+ ///
+ ///
+ protected void ApplyOrderByDescending(Expression> orderByExpression)
+ => OrderByDescending = orderByExpression;
+
+ ///
+ /// Applies pagination options.
+ ///
+ ///
+ ///
+ protected void ApplyPaging(int skip, int take)
+ {
+ Skip = skip;
+ Take = take;
+ IsPagingEnabled = true;
+ }
+}
\ No newline at end of file
diff --git a/Domain/Core/DomainEvent.cs b/Domain/Core/DomainEvent.cs
new file mode 100644
index 0000000..833b3ff
--- /dev/null
+++ b/Domain/Core/DomainEvent.cs
@@ -0,0 +1,28 @@
+
+namespace Odin.Domain
+{
+ ///
+ /// Base class for a domain event.
+ ///
+ public abstract class DomainEvent
+ {
+ ///
+ /// Default constructor
+ ///
+ ///
+ protected DomainEvent(DateTimeOffset now)
+ {
+ OccurredAt = now;
+ }
+
+ ///
+ /// Whether the event has been published or not.
+ ///
+ public bool IsPublished { get; set; }
+
+ ///
+ /// Gets the timestamp when the event occurred.
+ ///
+ public DateTimeOffset OccurredAt { get; protected set; }
+ }
+}
\ No newline at end of file
diff --git a/Domain/Core/DomainException.cs b/Domain/Core/DomainException.cs
new file mode 100644
index 0000000..4ba1df7
--- /dev/null
+++ b/Domain/Core/DomainException.cs
@@ -0,0 +1,8 @@
+namespace Odin.Domain;
+
+///
+/// Represents an exception originating from the core application domain.
+///
+public class DomainException : ApplicationException
+{
+}
\ No newline at end of file
diff --git a/Domain/Core/IAggregateRoot.cs b/Domain/Core/IAggregateRoot.cs
new file mode 100644
index 0000000..42da69c
--- /dev/null
+++ b/Domain/Core/IAggregateRoot.cs
@@ -0,0 +1,9 @@
+namespace Odin.Domain;
+
+///
+/// Marker interface to identify an 'aggregate root' entity
+/// (in domain driven design language).
+///
+public interface IAggregateRoot
+{
+}
\ No newline at end of file
diff --git a/Domain/Core/IIdentityAggregateRoot.cs b/Domain/Core/IIdentityAggregateRoot.cs
new file mode 100644
index 0000000..27a242f
--- /dev/null
+++ b/Domain/Core/IIdentityAggregateRoot.cs
@@ -0,0 +1,20 @@
+using System.Numerics;
+
+namespace Odin.Domain;
+
+///
+/// Marker interface to identify an 'aggregate root' entity
+/// that has a primitive unique identifier property,
+/// and typically a single column primary key.
+/// (in domain driven design language).
+///
+// ReSharper disable once TypeParameterCanBeVariant
+public interface IIdentityAggregateRoot : IAggregateRoot
+ where TId : struct, IEqualityOperators
+{
+ ///
+ /// Unique identifier of the entity.
+ /// Commonly int16\32\64, Guid or string.
+ ///
+ TId Id { get; }
+}
\ No newline at end of file
diff --git a/Domain/Core/IQuerySpecification.cs b/Domain/Core/IQuerySpecification.cs
new file mode 100644
index 0000000..e684d33
--- /dev/null
+++ b/Domain/Core/IQuerySpecification.cs
@@ -0,0 +1,47 @@
+using System.Linq.Expressions;
+
+namespace Odin.Domain;
+
+///
+/// Represents a specification for filter criteria, preloading includes,
+/// ordering and pagination for a repository query.
+///
+///
+public interface IQuerySpecification
+ where TAggregateRoot : class, IAggregateRoot
+{
+ ///
+ /// The filter criteria (Where clause)
+ ///
+ Expression>? Criteria { get; }
+
+ ///
+ /// Eager loading (Include clauses)
+ ///
+ IReadOnlyList>>? Includes { get; }
+
+ ///
+ /// Ordering ascending
+ ///
+ Expression>? OrderBy { get; }
+
+ ///
+ /// Ordering descending
+ ///
+ Expression>? OrderByDescending { get; }
+
+ ///
+ /// Pagination - Take
+ ///
+ int Take { get; }
+
+ ///
+ /// Pagination - Skip
+ ///
+ int Skip { get; }
+
+ ///
+ /// Whether pagination is enabled
+ ///
+ bool IsPagingEnabled { get; }
+}
\ No newline at end of file
diff --git a/Domain/Core/IRepository.cs b/Domain/Core/IRepository.cs
new file mode 100644
index 0000000..6ae2933
--- /dev/null
+++ b/Domain/Core/IRepository.cs
@@ -0,0 +1,97 @@
+using System.Linq.Expressions;
+using System.Numerics;
+
+namespace Odin.Domain
+{
+ ///
+ /// Represents a repository for persisting entities to a data store.
+ ///
+ /// The type of the aggregate root.
+ public interface IRepository : IAsyncDisposable
+ where TAggregateRoot : class, IAggregateRoot
+ {
+ ///
+ /// Adds an entity to the context but does not persist it to the database.
+ ///
+ /// The entity to add.
+ void Add(TAggregateRoot entity);
+
+ ///
+ /// Adds a range of entities to the context but does not persist them to the database.
+ ///
+ /// The entities to add.
+ void AddRange(IEnumerable entities);
+
+ ///
+ /// Updates an entity in the database.
+ ///
+ /// The entity to update.
+ void Update(TAggregateRoot entity);
+
+ ///
+ /// Deletes an entity from the database.
+ ///
+ /// The entity to delete.
+ void Delete(TAggregateRoot entity);
+
+ ///
+ /// Attempts to fetch a single entity based on the query specified.
+ ///
+ ///
+ ///
+ ///
+ Task FetchAsync(IQuerySpecification querySpecification,
+ CancellationToken ct = default);
+
+ ///
+ /// Fetches many entities based on the query specified.
+ ///
+ ///
+ ///
+ ///
+ Task> FetchManyAsync(
+ IQuerySpecification querySpecification, CancellationToken ct = default);
+
+ ///
+ /// Gets the unit of work, used to commit changes to the database,
+ /// and possibly fire domain events.
+ ///
+ IUnitOfWork UnitOfWork { get; }
+ }
+
+ ///
+ /// Represents a repository for persisting entities with a single column
+ /// primary key to a data store.
+ ///
+ ///
+ ///
+ public interface IRepository : IRepository
+ where TAggregateRoot : class, IIdentityAggregateRoot
+ where TId : struct, IEqualityOperators
+ {
+ ///
+ /// Fetches an entity by its identifier, returning null if not found.
+ ///
+ ///
+ ///
+ ///
+ Task FetchByIdAsync(TId id, CancellationToken ct = default);
+
+ ///
+ /// Fetches an entity by its identifier, returning null if not found.
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task FetchByIdAsync(TId id, IReadOnlyList>>? includes
+ , CancellationToken ct = default);
+
+ ///
+ /// Fetches entities from a list of their identifiers, returning an empty list if none found.
+ ///
+ ///
+ ///
+ Task> FetchManyByIdAsync(params ReadOnlySpan ids);
+ }
+}
\ No newline at end of file
diff --git a/Domain/Core/IUnitOfWork.cs b/Domain/Core/IUnitOfWork.cs
new file mode 100644
index 0000000..48a733e
--- /dev/null
+++ b/Domain/Core/IUnitOfWork.cs
@@ -0,0 +1,17 @@
+
+namespace Odin.Domain
+{
+ ///
+ /// Intended to wrap DbContext.SaveChangesAsync to support domain event publishing
+ /// at database persistence time.
+ ///
+ public interface IUnitOfWork
+ {
+ ///
+ /// Commit database changes and publish any unpublished domain events.
+ ///
+ ///
+ ///
+ Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
+ }
+}
\ No newline at end of file
diff --git a/Domain/Core/Odin.Domain.csproj b/Domain/Core/Odin.Domain.csproj
new file mode 100644
index 0000000..c3856cb
--- /dev/null
+++ b/Domain/Core/Odin.Domain.csproj
@@ -0,0 +1,17 @@
+
+
+ net8.0;net9.0;net10.0
+ true
+ enable
+ icon.png
+ README.md
+
+
+
+ 1591;1573;
+
+
+
+
+
+
diff --git a/Domain/Core/WhereIdentityIsSpecification.cs b/Domain/Core/WhereIdentityIsSpecification.cs
new file mode 100644
index 0000000..ce1fdc3
--- /dev/null
+++ b/Domain/Core/WhereIdentityIsSpecification.cs
@@ -0,0 +1,23 @@
+using System.Linq.Expressions;
+using System.Numerics;
+
+namespace Odin.Domain;
+
+///
+/// Allows querying of an aggregate root by Id.
+///
+///
+///
+public class WhereIdIsEqualTo : AbstractIdentityQuerySpecification
+ where TAggregateRoot : class, IIdentityAggregateRoot
+ where TId : struct, IEqualityOperators
+{
+ ///
+ /// Specify the Id to retrieve.
+ ///
+ ///
+ public WhereIdIsEqualTo(TId id)
+ {
+ Criteria = entity => entity.Id.Equals(id);
+ }
+}
\ No newline at end of file
diff --git a/Domain/EntityFramework/AbstractIdentityRepository.cs b/Domain/EntityFramework/AbstractIdentityRepository.cs
new file mode 100644
index 0000000..5670952
--- /dev/null
+++ b/Domain/EntityFramework/AbstractIdentityRepository.cs
@@ -0,0 +1,52 @@
+using System.Linq.Expressions;
+using System.Numerics;
+using Microsoft.EntityFrameworkCore;
+
+namespace Odin.Domain.EntityFramework;
+
+///
+///
+///
+///
+///
+///
+public abstract class AbstractIdentityRepository
+ : AbstractRepository, IRepository
+ where TDbContext : DbContext, IUnitOfWork
+ where TAggregateRoot : class, IIdentityAggregateRoot
+ where TId : struct, IEqualityOperators
+{
+ ///
+ ///
+ ///
+ ///
+ protected AbstractIdentityRepository(TDbContext dbContext) : base(dbContext)
+ {
+ }
+
+ ///
+ public async Task FetchByIdAsync(TId id, CancellationToken ct = default)
+ {
+ var query = new WhereIdIsEqualTo(id);
+ return await FetchAsync(query, ct);
+ }
+
+ ///
+ public Task FetchByIdAsync(TId id,
+ IReadOnlyList>>? includes, CancellationToken ct = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public Task> FetchManyByIdAsync(params ReadOnlySpan ids)
+ {
+ var idList = ids.ToArray();
+ return FetchManyByIdAsyncInternal(idList);
+ }
+
+ private async Task> FetchManyByIdAsyncInternal(TId[] idList)
+ {
+ return await DbSet.Where(x => idList.Contains(x.Id)).ToListAsync();
+ }
+}
\ No newline at end of file
diff --git a/Domain/EntityFramework/AbstractQuerySpecification.cs b/Domain/EntityFramework/AbstractQuerySpecification.cs
new file mode 100644
index 0000000..c7a16eb
--- /dev/null
+++ b/Domain/EntityFramework/AbstractQuerySpecification.cs
@@ -0,0 +1,65 @@
+using System.Linq.Expressions;
+
+namespace Odin.Domain.EntityFramework;
+
+///
+public abstract class AbstractQuerySpecification(Expression> criteria)
+ : IQuerySpecification
+ where TAggregateRoot : class, IAggregateRoot
+{
+ private readonly List>> _includes = [];
+
+ ///
+ public Expression> Criteria { get; } = criteria;
+
+ ///
+ public IReadOnlyList>> Includes => _includes;
+
+ ///
+ public Expression>? OrderBy { get; private set; }
+
+ ///
+ public Expression>? OrderByDescending { get; private set; }
+
+ ///
+ public int Take { get; private set; }
+
+ ///
+ public int Skip { get; private set; }
+
+ ///
+ public bool IsPagingEnabled { get; private set; }
+
+ ///
+ /// Adds a query include
+ ///
+ ///
+ protected void AddInclude(Expression> includeExpression)
+ => _includes.Add(includeExpression);
+
+ ///
+ /// Adds ordering ascending
+ ///
+ ///
+ protected void ApplyOrderBy(Expression> orderByExpression)
+ => OrderBy = orderByExpression;
+
+ ///
+ /// Adds ordering descending
+ ///
+ ///
+ protected void ApplyOrderByDescending(Expression> orderByExpression)
+ => OrderByDescending = orderByExpression;
+
+ ///
+ /// Applies pagination options.
+ ///
+ ///
+ ///
+ protected void ApplyPaging(int skip, int take)
+ {
+ Skip = skip;
+ Take = take;
+ IsPagingEnabled = true;
+ }
+}
\ No newline at end of file
diff --git a/Domain/EntityFramework/AbstractRepository.cs b/Domain/EntityFramework/AbstractRepository.cs
new file mode 100644
index 0000000..6757656
--- /dev/null
+++ b/Domain/EntityFramework/AbstractRepository.cs
@@ -0,0 +1,432 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace Odin.Domain.EntityFramework
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ public abstract class AbstractRepository : IRepository, IDisposable
+ where TDbContext : DbContext, IUnitOfWork
+ where TAggregateRoot : class, IAggregateRoot
+ {
+ ///
+ /// The context for database operations
+ ///
+ protected readonly TDbContext DbContext;
+
+ ///
+ /// The DbSet for the aggregate root type
+ ///
+ protected readonly DbSet DbSet;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public AbstractRepository(TDbContext dbContext)
+ {
+ DbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+ DbSet = DbContext.Set();
+ }
+
+ ///
+ public async Task> FetchManyAsync(IQuerySpecification querySpecification,
+ CancellationToken ct = default)
+ {
+ return await ApplySpecification(querySpecification).ToListAsync(ct);
+ }
+
+ ///
+ public async Task FetchAsync(IQuerySpecification querySpecification, CancellationToken ct = default)
+ {
+ return await ApplySpecification(querySpecification).FirstOrDefaultAsync(ct);
+ }
+
+ private IQueryable ApplySpecification(IQuerySpecification spec)
+ {
+ IQueryable query = DbSet.AsQueryable();
+
+ // Apply includes
+ foreach (var include in spec.Includes)
+ {
+ query = query.Include(include);
+ }
+
+ // Apply criteria
+ if (spec.Criteria != null)
+ {
+ query = query.Where(spec.Criteria);
+ }
+
+ // Apply ordering
+ if (spec.OrderBy != null)
+ {
+ query = query.OrderBy(spec.OrderBy);
+ }
+ else if (spec.OrderByDescending != null)
+ {
+ query = query.OrderByDescending(spec.OrderByDescending);
+ }
+
+ // Apply paging
+ if (spec.IsPagingEnabled)
+ {
+ query = query.Skip(spec.Skip).Take(spec.Take);
+ }
+
+ return query;
+ }
+
+ ///
+ public IUnitOfWork UnitOfWork => DbContext;
+
+ ///
+ public void Delete(TAggregateRoot entity)
+ {
+ DbSet.Remove(entity);
+ }
+
+ ///
+ public void Add(TAggregateRoot entity)
+ {
+ DbSet.Add(entity);
+ }
+
+ ///
+ public void AddRange(IEnumerable entities)
+ {
+ DbSet.AddRange(entities);
+ }
+
+ ///
+ public virtual void Update(TAggregateRoot entity)
+ {
+ DbSet.Update(entity);
+ }
+
+ ///
+ public void Dispose()
+ {
+ DbContext.Dispose();
+ }
+
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ await DbContext.DisposeAsync();
+ }
+
+ // private static async Task> ToPagedListAsync(
+ // IQueryable queryable,
+ // int pageNo,
+ // int pageSize,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var count = await queryable.CountAsync(cancellationToken);
+ // var skip = ((pageNo - 1) * pageSize);
+ //
+ // var results = await queryable
+ // .Skip(skip)
+ // .Take(pageSize)
+ // .ToListAsync(cancellationToken);
+ // return new PagedList(count, pageNo, pageSize, results);
+ // }
+
+
+ // public virtual async Task FindAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression).SingleOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task FindAsync(
+ // Expression> filterExpression,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression, queryOptions).SingleOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task FindAsync(
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(queryOptions).SingleOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression: null).ToListAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression).ToListAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // Expression> filterExpression,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression, queryOptions).ToListAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // int pageNo,
+ // int pageSize,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var query = QueryInternal(filterExpression: null);
+ // return await ToPagedListAsync(
+ // query,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // Expression> filterExpression,
+ // int pageNo,
+ // int pageSize,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var query = QueryInternal(filterExpression);
+ // return await ToPagedListAsync(
+ // query,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // Expression> filterExpression,
+ // int pageNo,
+ // int pageSize,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var query = QueryInternal(filterExpression, queryOptions);
+ // return await ToPagedListAsync(
+ // query,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(queryOptions).ToListAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task> FindAllAsync(
+ // int pageNo,
+ // int pageSize,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var query = QueryInternal(queryOptions);
+ // return await ToPagedListAsync(
+ // query,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public virtual async Task CountAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression).CountAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task CountAsync(
+ // Func, IQueryable>? queryOptions = default,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(queryOptions).CountAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task AnyAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(filterExpression).AnyAsync(cancellationToken);
+ // }
+ //
+ // public virtual async Task AnyAsync(
+ // Func, IQueryable>? queryOptions = default,
+ // CancellationToken cancellationToken = default)
+ // {
+ // return await QueryInternal(queryOptions).AnyAsync(cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression: null);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.ToListAsync(cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.ToListAsync(cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.ToListAsync(cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // Expression> filterExpression,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression, queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.ToListAsync(cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // int pageNo,
+ // int pageSize,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression: null);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await ToPagedListAsync(
+ // projection,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // int pageNo,
+ // int pageSize,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await ToPagedListAsync(
+ // projection,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // Expression> filterExpression,
+ // int pageNo,
+ // int pageSize,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await ToPagedListAsync(
+ // projection,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public async Task> FindAllProjectToAsync(
+ // Expression> filterExpression,
+ // int pageNo,
+ // int pageSize,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression, queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await ToPagedListAsync(
+ // projection,
+ // pageNo,
+ // pageSize,
+ // cancellationToken);
+ // }
+ //
+ // public async Task FindProjectToAsync(
+ // Expression> filterExpression,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.FirstOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // public async Task FindProjectToAsync(
+ // Expression> filterExpression,
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(filterExpression, queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.FirstOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // public async Task FindProjectToAsync(
+ // Func, IQueryable> queryOptions,
+ // CancellationToken cancellationToken = default)
+ // {
+ // var queryable = QueryInternal(queryOptions);
+ // var projection = queryable.ProjectTo(_mapper.ConfigurationProvider);
+ // return await projection.FirstOrDefaultAsync(cancellationToken);
+ // }
+ //
+ // protected virtual IQueryable QueryInternal(Expression>? filterExpression)
+ // {
+ // var queryable = CreateQuery();
+ // if (filterExpression != null)
+ // {
+ // queryable = queryable.Where(filterExpression);
+ // }
+ // return queryable;
+ // }
+ //
+ // protected virtual IQueryable QueryInternal(
+ // Expression> filterExpression,
+ // Func, IQueryable> queryOptions)
+ // {
+ // var queryable = CreateQuery();
+ // queryable = queryable.Where(filterExpression);
+ // var result = queryOptions(queryable);
+ // return result;
+ // }
+ //
+ // protected virtual IQueryable QueryInternal(Func, IQueryable>? queryOptions)
+ // {
+ // var queryable = CreateQuery();
+ // if (queryOptions != null)
+ // {
+ // queryable = queryOptions(queryable);
+ // }
+ // return queryable;
+ // }
+ //
+ // protected virtual IQueryable CreateQuery()
+ // {
+ // return DbSet;
+ // }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Domain/EntityFramework/Odin.Domain.EntityFramework.csproj b/Domain/EntityFramework/Odin.Domain.EntityFramework.csproj
new file mode 100644
index 0000000..eb4df4e
--- /dev/null
+++ b/Domain/EntityFramework/Odin.Domain.EntityFramework.csproj
@@ -0,0 +1,32 @@
+
+
+ net8.0;net9.0;net10.0
+ true
+ enable
+ icon.png
+ README.md
+
+
+
+ 1591;1573;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Domain/README.md b/Domain/README.md
new file mode 100644
index 0000000..ac91b85
--- /dev/null
+++ b/Domain/README.md
@@ -0,0 +1,2 @@
+## About Odin.Domain
+~~~~
\ No newline at end of file
diff --git a/Odin.sln b/Odin.sln
index f09b82a..3a1cde0 100644
--- a/Odin.sln
+++ b/Odin.sln
@@ -103,6 +103,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Odin.Patterns.CommandHandle
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patterns", "Patterns", "{605A7674-8EA4-458D-9FEB-A86C927AC0F0}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{5B5DADD3-07F8-40E9-949A-82BEB18093CC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Odin.Domain", "Domain\Core\Odin.Domain.csproj", "{9EF181C9-41C7-4C02-97AB-AD0D1DDFA323}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Odin.Domain.EntityFramework", "Domain\EntityFramework\Odin.Domain.EntityFramework.csproj", "{F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -233,6 +239,14 @@ Global
{382C1CC0-F981-4682-9EFD-4471955327C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{382C1CC0-F981-4682-9EFD-4471955327C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{382C1CC0-F981-4682-9EFD-4471955327C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9EF181C9-41C7-4C02-97AB-AD0D1DDFA323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9EF181C9-41C7-4C02-97AB-AD0D1DDFA323}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9EF181C9-41C7-4C02-97AB-AD0D1DDFA323}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9EF181C9-41C7-4C02-97AB-AD0D1DDFA323}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CE323D9C-635B-EFD3-5B3F-7CE371D8A86A} = {BF440C74-E223-3CBF-8FA7-83F7D164F7C3}
@@ -266,5 +280,7 @@ Global
{12C40512-CA83-4209-9318-6CBCABF8C798} = {9E3E1A13-9A74-4895-98A9-D96F4E0ED4B7}
{E450FC74-0DBE-320A-FE7A-87255CB4DFAB} = {73BA62BB-2B41-2DC4-C714-51B3D4C2A215}
{382C1CC0-F981-4682-9EFD-4471955327C3} = {605A7674-8EA4-458D-9FEB-A86C927AC0F0}
+ {9EF181C9-41C7-4C02-97AB-AD0D1DDFA323} = {5B5DADD3-07F8-40E9-949A-82BEB18093CC}
+ {F0C4BEDA-4B20-4EC8-8265-EFF66D2B1B15} = {5B5DADD3-07F8-40E9-949A-82BEB18093CC}
EndGlobalSection
EndGlobal