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