43 Commits

Author SHA1 Message Date
Developer 02
e0737299cf Bump version to 1.1.1 in project file
Updated DigitalData.Core.Exceptions.csproj to set <Version>, <AssemblyVersion>, and <FileVersion> to 1.1.1, replacing the previous 1.1.0 values. No other changes were made.
2026-01-21 22:06:35 +01:00
Developer 02
2db99edcda Add constructor to BadRequestException for inner exceptions
Added a new constructor to BadRequestException that accepts both a message and an inner exception, enabling improved exception chaining and more detailed error reporting.
2026-01-21 22:05:16 +01:00
65186b4f47 chore(Infrastructure): bump to 2.6.1 2026-01-12 10:23:26 +01:00
eb3a5b8991 Apply AsNoTracking to raw/interpolated queries in DbRepository
QueryRaw and QueryInterpolated now always return results with AsNoTracking applied, ensuring entities are not tracked by EF Core. Both methods are now virtual, allowing for easier customization in derived classes. This change improves performance and prevents unintended side effects from entity tracking.
2026-01-12 10:02:56 +01:00
908d85c648 refactor(Core.Abstraction.Application): bump to 1.6.0 2025-12-19 10:23:45 +01:00
d0f055e066 Bump version to 2.6.0 in csproj metadata
Updated <Version>, <AssemblyVersion>, and <FileVersion> fields in DigitalData.Core.Infrastructure.csproj from 2.5.2 to 2.6.0 to reflect the new release.
2025-12-19 10:13:25 +01:00
7f9e6155fe Add repository registration to DI setup
Added RegsRepository.InvokeAll(services) to the dependency injection configuration, ensuring repository types are registered alongside entities and DbSet factories.
2025-12-19 10:11:14 +01:00
1e3cba6fdf Refactor DependencyInjection and RepositoryConfiguration classes
Restructure code for improved readability and maintainability. Move RepositoryConfiguration as a nested public class inside DependencyInjection, group related service registration logic, and clarify method organization. No functional changes.
2025-12-19 10:09:04 +01:00
daa36d767d Refactor repository registration to be context-based
Changed RegisterDefaultRepository to register repositories by DbContext only, removing the entity-specific generic parameter. Added a private InvokeAll<T> helper to process queued service registrations. This streamlines repository registration and improves code maintainability.
2025-12-19 10:03:25 +01:00
3021fd36f6 Implement IRepository in DbRepository<TDbContext>
DbRepository<TDbContext> now implements the IRepository interface, ensuring it conforms to the expected repository contract and interface requirements. This change improves consistency and enforces interface adherence across repository implementations.
2025-12-19 10:01:20 +01:00
2c704c1231 Add raw SQL support to repository interfaces and DI
Extended IRepository interfaces and DbRepository implementation to support raw and interpolated SQL query execution (sync/async) and querying. Updated DependencyInjection to allow registration of default repository implementations. Added Microsoft.EntityFrameworkCore.Abstractions as a dependency. Performed minor refactoring and cleanup.
2025-12-19 09:54:56 +01:00
2bf2bb2276 Refactor namespace and add System.Linq import
Removed conditional compilation around the namespace in DbRepository.cs, making it always use standard braces. Added System.Linq using directive to support LINQ operations.
2025-12-19 09:51:22 +01:00
Developer 02
d2302560f1 Add repository registration to DependencyInjection
Added a `RegsRepository` queue to manage repository registrations.
Introduced `RegisterDefaultRepository<TDbContext, TEntity>()` to
enqueue scoped repository registrations. Updated `RegisterAllServices`
to process the `RegsRepository` queue during service registration.
2025-12-19 09:25:58 +01:00
Developer 02
42c0dc7206 Refactor method names for clarity in IRepository/DbRepository
Renamed methods in `IRepository` and `DbRepository` to replace
`Sql` with `Query` for improved clarity and consistency.

- Updated `IRepository` methods:
  - `ExecuteSqlRawAsync` -> `ExecuteQueryRawAsync`
  - `ExecuteSqlInterpolatedAsync` -> `ExecuteQueryInterpolatedAsync`
  - `ExecuteSqlRaw` -> `ExecuteQueryRaw`
  - `ExecuteSqlInterpolated` -> `ExecuteQueryInterpolated`

- Updated `DbRepository` methods:
  - `ExecuteSqlRawAsync` -> `ExecuteQueryRawAsync`
  - `ExecuteSqlInterpolatedAsync` -> `ExecuteQueryInterpolatedAsync`
  - `ExecuteSqlRaw` -> `ExecuteQueryRaw`
  - `ExecuteSqlInterpolated` -> `ExecuteQueryInterpolated`

- Fixed `DbRepository` class declaration by removing the incorrect
  constraint requiring `TDbContext` to implement `IRepository`.
2025-12-19 09:20:18 +01:00
Developer 02
82686db38b Add QueryRaw and QueryInterpolated methods to repository
Added `QueryRaw` and `QueryInterpolated` methods to the
`IRepository` interface for raw and interpolated SQL queries,
conditionally compiled for the `NET` target framework.

Removed the `Sql` method from `DbRepository` and replaced it
with implementations of `QueryRaw` and `QueryInterpolated`
using `Entities.FromSqlRaw` and `Entities.FromSqlInterpolated`.

Updated the `Query` property in `DbRepository` to use
`Entities.AsNoTracking()` for read-only queries.
2025-12-19 09:05:59 +01:00
Developer 02
ce5c59dfc2 Update DbRepository to require IRepository constraint
The `DbRepository` class now enforces that the generic type
parameter `TDbContext` must implement the `IRepository`
interface in addition to inheriting from `DbContext`. This
ensures that `DbRepository` can leverage functionality
provided by the `IRepository` interface, improving type
safety and consistency.
2025-12-19 08:57:18 +01:00
Developer 02
5c3db6886a Add EF Core support and refactor IRepository interface
Updated project dependencies to include `Microsoft.EntityFrameworkCore.Abstractions` for multiple target frameworks (`net462`, `net7.0`, `net8.0`, `net9.0`). Added `Microsoft.Extensions.*` package references to the project file.

Enhanced `IRepository` interface with methods for executing raw and interpolated SQL queries (`ExecuteSqlRawAsync`, `ExecuteSqlInterpolatedAsync`, etc.). Adjusted method declarations to support conditional compilation for `NET` and `NETFRAMEWORK`.

Refactored namespace structure in `IRepository.cs` to simplify and remove unnecessary conditional compilation directives.
2025-12-18 22:09:45 +01:00
Developer 02
144178a504 Add async SQL execution methods to DbRepository
Added `ExecuteSqlRawAsync` and `ExecuteSqlInterpolatedAsync` methods to the `DbRepository` class for asynchronous SQL execution. Updated the `System.Linq` namespace import to support LINQ operations. Simplified conditional compilation directives by consistently enclosing the class declaration in braces `{}` across frameworks. Adjusted closing braces to align with the updated structure.
2025-12-18 21:54:22 +01:00
6717aa37ab Add generic DbRepository for raw SQL execution
Introduced DbRepository<TDbContext> to provide basic database operations, including methods for executing raw SQL commands via ExecuteSqlRaw and ExecuteSqlInterpolated. The class exposes the underlying DbContext for use in derived classes. Existing DbRepository<TDbContext, TEntity> remains unchanged.
2025-12-18 13:44:45 +01:00
bf418e986b Add Sql method to DbRepository and EFCore.Relational refs
Added a Sql method to DbRepository for executing raw SQL queries
via FromSqlRaw. Included Microsoft.EntityFrameworkCore.Relational
package references for all target frameworks to support this
functionality. Cleaned up unused using directives.
2025-12-18 13:21:35 +01:00
9f2a13df6f bump to 2.5.2 2025-11-06 00:25:15 +01:00
0cf6d2690a chore(Infrastructure): bump to 2.5.0 2025-11-05 14:22:22 +01:00
afbbac7b81 core(Abstraction.Application): bump to 1.5.0 2025-11-05 14:21:29 +01:00
d16a4b6bb4 refactor(repository): make UpdateAsync more flexible with Action<TEntity>
- Introduced Action<TEntity>-based UpdateAsync method for more flexible updates
- Updated TDto-based UpdateAsync to delegate to the new method
- Reduced code duplication and Mapper dependency
- Preserved existing Create, Read, and Delete functionality
2025-11-05 14:20:12 +01:00
5567f6731b chore(Core.Abstractions): bump to 4.3.0 2025-10-29 16:25:32 +01:00
fc297d92fa feat(factory): add IServiceScopeFactory property to Factory class
Added ScopeFactory property to the Factory class to expose IServiceScopeFactory through GetRequiredService<IServiceScopeFactory>().
This enables scoped service creation from the factory instance.
2025-10-29 16:08:43 +01:00
50a056d110 feat(Factory): add PostBuildBehavior to control post-build modification behavior in Factory
Introduced PostBuildBehavior enum and BehaveOnPostBuild() method to configure behavior when modifying the service collection after the service provider is built.
Replaced EnsureNotBuilt() with EnsureBuilt() to support configurable handling (throw exception or ignore).
This improves flexibility in scenarios where post-build service modifications should be optionally allowed or silently ignored.
2025-10-29 15:57:36 +01:00
d87f36898b chore(Abstraction.Application): bump to 1.4.0 2025-10-23 10:30:50 +02:00
62e43024a6 chore(Abstractions): bump to 4.2.0 2025-10-23 10:29:41 +02:00
daa3f4d5be feat: add IServiceProvider extension to resolve IRepository<TEntity> 2025-10-23 10:27:57 +02:00
10ff9b9745 refactor(Factory): rename Instance as Shared 2025-10-23 10:19:59 +02:00
ea2340974a refactor(FactoryTests): created unit tests for Factory 2025-10-23 10:18:14 +02:00
fbf9488c55 feat(Factory): prevent service modifications after provider is built
Added an `IsBuilt` property and `EnsureNotBuilt()` helper to `Factory` class
to restrict modifications to the service collection once the service provider
has been built. This ensures immutability and prevents runtime inconsistencies
after initialization.
2025-10-23 09:53:50 +02:00
ddbb70081e fix(factory): return requested service from IServiceProvider in GetService method 2025-10-23 09:49:38 +02:00
c538a8df8c feat(Factory): add Factory class implementing IServiceProvider and IServiceCollection
- Introduced Factory class in DigitalData.Core.Abstractions namespace
- Implements both IServiceProvider and IServiceCollection interfaces
- Provides singleton instance via static readonly Instance property
- Supports lazy initialization of IServiceProvider using Lazy<T>
- Includes full collection management (Add, Remove, Clear, etc.)
- Ensures compatibility with both .NET and .NET Framework via conditional directives
2025-10-23 09:47:18 +02:00
f500d9d974 chore(Abstractions): update package references and add dependency injection packages
- Added Microsoft.Extensions.DependencyInjection and Abstractions for net462
- Added Microsoft.Extensions.DependencyInjection for net7.0, net8.0, and net9.0
- Updated Microsoft.Extensions.* package versions for net8.0 and net9.0 to 9.0.10
2025-10-23 09:27:26 +02:00
f2808d090f refactor(Infrastructure): bump to 2.4.5 2025-10-22 17:42:57 +02:00
0084e6f758 refactor(Abstraction.Application): bump to 1.3.7 2025-10-22 17:37:39 +02:00
90ce4e487c refactor: remove IRepository and DbRepository 2025-10-22 17:34:28 +02:00
1febae72c2 refactor(DbRepositoryFactory): remvoed 2025-10-22 17:26:31 +02:00
3b825d4ea3 refactor(Abstraction.Repository): removed 2025-10-13 13:48:46 +02:00
a06adfdcdf chore: bump Abstraction.Application to v1.3.6 and Infrastructure to v2.4.4 2025-10-13 12:05:03 +02:00
00e5f6c0e9 refactor(Repository): add Query getter metod to Repository calss and interface to be able to create read-only query without expression 2025-10-13 11:55:49 +02:00
22 changed files with 708 additions and 612 deletions

View File

@@ -12,9 +12,9 @@
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture abstraction</PackageTags>
<Version>1.3.5</Version>
<AssemblyVersion>1.3.5</AssemblyVersion>
<FileVersion>1.3.5</FileVersion>
<Version>1.6.0</Version>
<AssemblyVersion>1.6.0</AssemblyVersion>
<FileVersion>1.6.0</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -37,7 +37,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
@@ -52,24 +52,28 @@
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="3.1.32" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.15" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="9.0.5" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,53 +0,0 @@
#if NETFRAMEWORK
using System;
#endif
using DigitalData.Core.Abstraction.Application.Repository;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Abstraction.Application
#if NET
;
#elif NETFRAMEWORK
{
#endif
public interface IFactory : IServiceCollection, IServiceProvider
{
#if NET
public
#endif
IRepository Repository { get; }
}
public class Factory : ServiceCollection, IFactory, IServiceCollection, IServiceProvider
{
private readonly Lazy<IServiceProvider> _rootProvider;
private Factory()
{
_rootProvider = new Lazy<IServiceProvider>(this.BuildServiceProvider);
}
public object
#if NET
?
#endif
GetService(Type serviceType)
{
return _rootProvider.Value.GetService(serviceType);
}
public IRepository Repository => _rootProvider.Value.GetRequiredService<IRepository>();
public static readonly IFactory Shared = new Factory();
}
public static class Factory<TEntity>
{
public static IRepository<TEntity> Repository() => Factory.Shared.GetRequiredService<IRepository<TEntity>>();
}
#if NETFRAMEWORK
}
#endif

View File

@@ -1,79 +0,0 @@
using System.Linq.Expressions;
using DigitalData.Core.Abstractions.Interfaces;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
#if NET
;
#elif NETFRAMEWORK
{
#endif
public static class Extensions
{
#region IRepository
#region Read
public static IEnumerable<TEntity> GetAll<TEntity>(this IRepository repository) where TEntity : IEntity
=> repository.Entity<TEntity>().GetAll();
public static Task<IEnumerable<TEntity>> GetAllAsync<TEntity>(this IRepository repository, CancellationToken cancel = default) where TEntity : IEntity
=> repository.Entity<TEntity>().GetAllAsync(cancel);
public static IQueryable<TEntity> Where<TEntity>(this IRepository repository, Expression<Func<TEntity, bool>> expression)
where TEntity : IEntity
=> repository.Entity<TEntity>().Where(expression);
#endregion
#region Create
public static Task<TEntity> CreateAsync<TEntity>(this IRepository repository, TEntity entity, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().CreateAsync(entity, cancel);
public static Task<TEntity> CreateAsync<TEntity, TDto>(this IRepository repository, TDto dto, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().CreateAsync(dto, cancel);
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity>(this IRepository repository, IEnumerable<TEntity> entities, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().CreateAsync(entities, cancel);
public static Task<IEnumerable<TEntity>> CreateAsync<TEntity, TDto>(this IRepository repository, IEnumerable<TDto> dtos, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().CreateAsync(dtos, cancel);
#endregion Create
#region Update
public static Task UpdateAsync<TEntity, TDto>(this IRepository repository, TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().UpdateAsync(dto, expression, cancel);
public static Task UpdateAsync<TEntity, TDto>(this IRepository repository, TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
where TEntity : IEntity
where TDto : IDto<TEntity>
=> repository.Entity<TEntity>().UpdateAsync(dto, query, cancel);
#endregion
#region Delete
public static Task DeleteAsync<TEntity>(this IRepository repository, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().DeleteAsync(expression, cancel);
public static Task DeleteAsync<TEntity>(this IRepository repository, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
where TEntity : IEntity
=> repository.Entity<TEntity>().DeleteAsync(query, cancel);
#endregion
#endregion
}
#if NETFRAMEWORK
}
#endif

View File

@@ -1,5 +1,5 @@
using DigitalData.Core.Abstractions.Interfaces;
using System.Linq.Expressions;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
@@ -9,97 +9,132 @@ using System.Linq;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
#if NET
;
#elif NETFRAMEWORK
{
public interface IRepository
{
#endif
public interface IRepository<TEntity>
{
#region Create
#if NET
public
#endif
Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default);
Task<int> ExecuteQueryRawAsync([NotParameterized] string sql, IEnumerable<object> parameters, CancellationToken cancel = default);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default);
Task<int> ExecuteQueryInterpolatedAsync(FormattableString sql, CancellationToken cancel = default);
#if NET
public
#endif
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default);
int ExecuteQueryRaw([NotParameterized] string sql, params object[] parameters);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default);
#endregion Create
#region Read
#if NET
public
#endif
IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression);
#if NET
public
#endif
IEnumerable<TEntity> GetAll();
#if NET
public
#endif
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default);
#endregion Read
#region Update
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#endregion Update
#region Delete
#if NET
public
#endif
Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#endregion Delete
#region Obsolete
[Obsolete("Use CreateAsync, UpdateAsync or DeleteAsync")]
#if NET
public
#endif
IQueryable<TEntity> Read();
[Obsolete("Use IRepository<TEntity>.Where")]
#if NET
public
#endif
IQueryable<TEntity> ReadOnly();
#endregion
}
public interface IRepository
{
IRepository<TEntity> Entity<TEntity>() where TEntity : IEntity;
}
#if NETFRAMEWORK
int ExecuteQueryInterpolated(FormattableString sql);
}
#endif
public interface IRepository<TEntity>
{
#region Create
#if NET
public
#endif
Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default);
#if NET
public
#endif
Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default);
#if NET
public
#endif
Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default);
#endregion Create
#region Read
#if NET
public
#endif
IQueryable<TEntity> Query { get; }
#if NET
public
#endif
IQueryable<TEntity> QueryRaw([NotParameterized] string sql, params object[] parameters);
#if NET
public
#endif
IQueryable<TEntity> QueryInterpolated([NotParameterized] FormattableString sql);
#if NET
public
#endif
IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression);
#if NET
public
#endif
IEnumerable<TEntity> GetAll();
#if NET
public
#endif
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default);
#endregion Read
#region Update
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync(Action<TEntity> modification, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#if NET
public
#endif
Task UpdateAsync(Action<TEntity> modification, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#endregion Update
#region Delete
#if NET
public
#endif
Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default);
#if NET
public
#endif
Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default);
#endregion Delete
#region Obsolete
[Obsolete("Use CreateAsync, UpdateAsync or DeleteAsync")]
#if NET
public
#endif
IQueryable<TEntity> Read();
[Obsolete("Use IRepository<TEntity>.Where")]
#if NET
public
#endif
IQueryable<TEntity> ReadOnly();
#endregion
}
}

View File

@@ -1,18 +0,0 @@
namespace DigitalData.Core.Abstraction.Application.Repository
#if NET
;
#elif NETFRAMEWORK
{
#endif
public interface IRepositoryFactory
{
#if NET
public
#endif
IRepository<TEntity> Get<TEntity>();
}
#if NETFRAMEWORK
}
#endif

View File

@@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
#endif
namespace DigitalData.Core.Abstraction.Application.Repository
{
public static class ServiceProviderExtensions
{
public static IRepository<TEntity> Repository<TEntity>(this IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<IRepository<TEntity>>();
}
}
}

View File

@@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -16,9 +16,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>4.1.1</Version>
<AssemblyVersion>4.1.1</AssemblyVersion>
<FileVersion>4.1.1</FileVersion>
<Version>4.3.0</Version>
<AssemblyVersion>4.3.0</AssemblyVersion>
<FileVersion>4.3.0</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -42,19 +42,27 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,142 @@
using Microsoft.Extensions.DependencyInjection;
using System.Collections;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
#endif
namespace DigitalData.Core.Abstractions
{
public class Factory : IServiceProvider, IServiceCollection
{
public static readonly Factory Shared = new Factory();
private readonly IServiceCollection _serviceCollection = new ServiceCollection();
private readonly Lazy<IServiceProvider> _lazyServiceProvider;
private bool IsBuilt => _lazyServiceProvider.IsValueCreated;
private PostBuildBehavior _pbBehavior = PostBuildBehavior.ThrowException;
public Factory BehaveOnPostBuild(PostBuildBehavior postBuildBehavior)
{
_pbBehavior = postBuildBehavior;
return this;
}
public Factory()
{
_lazyServiceProvider = new Lazy<IServiceProvider>(() => _serviceCollection.BuildServiceProvider());
}
#region Service Provider
public object
#if NET
?
#endif
GetService(Type serviceType)
{
return _lazyServiceProvider.Value.GetService(serviceType);
}
public IServiceScopeFactory ScopeFactory => this.GetRequiredService<IServiceScopeFactory>();
#endregion
#region Service Collection
public int Count => _serviceCollection.Count;
public bool IsReadOnly => _serviceCollection.IsReadOnly;
public ServiceDescriptor this[int index]
{
get => _serviceCollection[index];
set
{
if (EnsureBuilt())
return;
_serviceCollection[index] = value;
}
}
public int IndexOf(ServiceDescriptor item)
{
return _serviceCollection.IndexOf(item);
}
public void Insert(int index, ServiceDescriptor item)
{
if (EnsureBuilt())
return;
_serviceCollection.Insert(index, item);
}
public void RemoveAt(int index)
{
if (EnsureBuilt())
return;
_serviceCollection.RemoveAt(index);
}
public void Add(ServiceDescriptor item)
{
if (EnsureBuilt())
return;
_serviceCollection.Add(item);
}
public void Clear()
{
if (EnsureBuilt())
return;
_serviceCollection.Clear();
}
public bool Contains(ServiceDescriptor item)
{
return _serviceCollection.Contains(item);
}
public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
{
_serviceCollection.CopyTo(array, arrayIndex);
}
public bool Remove(ServiceDescriptor item)
{
if (EnsureBuilt())
return false;
return _serviceCollection.Remove(item);
}
public IEnumerator<ServiceDescriptor> GetEnumerator()
{
return _serviceCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (_serviceCollection as IEnumerable).GetEnumerator();
}
#endregion
#region Helpers
private bool EnsureBuilt()
{
if (IsBuilt)
{
return _pbBehavior == PostBuildBehavior.ThrowException
? throw new InvalidOperationException("Service provider has already been built. No further service modifications are allowed.")
: true;
}
else
return false;
}
#endregion
}
public enum PostBuildBehavior
{
ThrowException,
Ignore
}
}

View File

@@ -19,4 +19,13 @@ public class BadRequestException : Exception
public BadRequestException(string? message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error message and inner exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that caused the current exception.</param>
public BadRequestException(string? message, Exception? innerException) : base(message, innerException)
{
}
}

View File

@@ -17,9 +17,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>1.1.0</Version>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<Version>1.1.1</Version>
<AssemblyVersion>1.1.1</AssemblyVersion>
<FileVersion>1.1.1</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,9 +1,9 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
using DigitalData.Core.Infrastructure.Factory;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
#if NETFRAMEWORK
using System.Collections.Generic;
using System;
@@ -13,131 +13,150 @@ using System.Threading.Tasks;
#endif
namespace DigitalData.Core.Infrastructure
#if NET
;
#elif NETFRAMEWORK
{
#endif
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{
protected internal readonly TDbContext Context;
public class DbRepository<TDbContext> : IRepository where TDbContext : DbContext
{
protected internal readonly TDbContext Context;
protected internal readonly DbSet<TEntity> Entities;
public DbRepository(TDbContext context)
{
Context = context;
}
public IMapper
public Task<int> ExecuteQueryRawAsync([NotParameterized] string sql, IEnumerable<object> parameters, CancellationToken cancel = default)
{
return Context.Database.ExecuteSqlRawAsync(sql, parameters, cancel);
}
public Task<int> ExecuteQueryInterpolatedAsync(FormattableString sql, CancellationToken cancel = default)
{
return Context.Database.ExecuteSqlInterpolatedAsync(sql, cancel);
}
public int ExecuteQueryRaw([NotParameterized] string sql, params object[] parameters)
{
return Context.Database.ExecuteSqlRaw(sql, parameters);
}
public int ExecuteQueryInterpolated(FormattableString sql)
{
return Context.Database.ExecuteSqlInterpolated(sql);
}
}
public class DbRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class
{
protected internal readonly TDbContext Context;
protected internal readonly DbSet<TEntity> Entities;
public IMapper
#if NET
?
#endif
Mapper { get; }
Mapper { get; }
public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper
public DbRepository(TDbContext context, DbSetFactory<TDbContext, TEntity> factory, IMapper
#if NET
?
#endif
mapper = null)
{
Context = context;
Entities = factory.Create(context);
Mapper = mapper;
}
mapper = null)
{
Context = context;
Entities = factory.Create(context);
Mapper = mapper;
}
#region Create
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
{
Entities.Add(entity);
await Context.SaveChangesAsync(cancel);
return entity;
}
#region Create
public virtual async Task<TEntity> CreateAsync(TEntity entity, CancellationToken cancel = default)
{
Entities.Add(entity);
await Context.SaveChangesAsync(cancel);
return entity;
}
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default)
{
Entities.AddRange(entities);
await Context.SaveChangesAsync(cancel);
return entities;
}
public virtual async Task<IEnumerable<TEntity>> CreateAsync(IEnumerable<TEntity> entities, CancellationToken cancel = default)
{
Entities.AddRange(entities);
await Context.SaveChangesAsync(cancel);
return entities;
}
public virtual Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default)
=> CreateAsync(Mapper
public virtual Task<TEntity> CreateAsync<TDto>(TDto dto, CancellationToken cancel = default)
=> CreateAsync(Mapper
#if NET
!
#endif
.Map<TEntity>(dto), cancel);
.Map<TEntity>(dto), cancel);
public virtual Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default)
=> CreateAsync(Mapper
public virtual Task<IEnumerable<TEntity>> CreateAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancel = default)
=> CreateAsync(Mapper
#if NET
!
#endif
.Map<IEnumerable<TEntity>>(dtos), cancel);
#endregion Create
.Map<IEnumerable<TEntity>>(dtos), cancel);
#endregion Create
#region Read
public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression) => Entities.AsNoTracking().Where(expression);
#region Read
public virtual IQueryable<TEntity> Query => Entities.AsNoTracking();
public virtual IEnumerable<TEntity> GetAll() => Entities.AsNoTracking().ToList();
public virtual IQueryable<TEntity> QueryRaw([NotParameterized] string sql, params object[] parameters) => Entities.FromSqlRaw(sql, parameters).AsNoTracking();
public virtual async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default) => await Entities.AsNoTracking().ToListAsync(cancel);
#endregion Read
public virtual IQueryable<TEntity> QueryInterpolated([NotParameterized] FormattableString sql) => Entities.FromSqlInterpolated(sql).AsNoTracking();
#region Update
public virtual Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => UpdateAsync(dto, q => q.Where(expression), cancel);
public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression) => Entities.AsNoTracking().Where(expression);
public virtual async Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
var entities = await query(Entities).ToListAsync(cancel);
public virtual IEnumerable<TEntity> GetAll() => Entities.AsNoTracking().ToList();
for (int i = entities.Count - 1; i >= 0; i--)
{
Mapper
public virtual async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken cancel = default) => await Entities.AsNoTracking().ToListAsync(cancel);
#endregion Read
#region Update
public virtual Task UpdateAsync<TDto>(TDto dto, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => UpdateAsync(dto, q => q.Where(expression), cancel);
public virtual Task UpdateAsync<TDto>(TDto dto, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
=> UpdateAsync(entity => Mapper
#if NET
!
!
#endif
.Map(dto, entities[i]);
Entities.Update(entities[i]);
}
.Map(dto, entity), query, cancel);
await Context.SaveChangesAsync(cancel);
}
#endregion Update
#region Delete
public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => DeleteAsync(q => q.Where(expression), cancel);
public virtual async Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
var entities = await query(Entities).ToListAsync(cancel);
for (int i = entities.Count - 1; i >= 0; i--)
public virtual async Task UpdateAsync(Action<TEntity> modification, Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
Entities.Remove(entities[i]);
var entities = await query(Entities).ToListAsync(cancel);
for (int i = entities.Count - 1; i >= 0; i--)
modification.Invoke(entities[i]);
await Context.SaveChangesAsync(cancel);
}
await Context.SaveChangesAsync(cancel);
public virtual Task UpdateAsync(Action<TEntity> modification, Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default)
=> UpdateAsync(modification, q => q.Where(expression), cancel);
#endregion Update
#region Delete
public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancel = default) => DeleteAsync(q => q.Where(expression), cancel);
public virtual async Task DeleteAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> query, CancellationToken cancel = default)
{
var entities = await query(Entities).ToListAsync(cancel);
for (int i = entities.Count - 1; i >= 0; i--)
{
Entities.Remove(entities[i]);
}
await Context.SaveChangesAsync(cancel);
}
#endregion Delete
#region Obsolete
[Obsolete("Use IRepository<TEntity>.Where")]
public virtual IQueryable<TEntity> Read() => Entities.AsQueryable();
[Obsolete("Use IRepository<TEntity>.Get")]
public virtual IQueryable<TEntity> ReadOnly() => Entities.AsNoTracking();
#endregion
}
#endregion Delete
#region Obsolete
[Obsolete("Use IRepository<TEntity>.Where")]
public virtual IQueryable<TEntity> Read() => Entities.AsQueryable();
[Obsolete("Use IRepository<TEntity>.Get")]
public virtual IQueryable<TEntity> ReadOnly() => Entities.AsNoTracking();
#endregion
}
public class DbRepository : IRepository
{
private readonly IRepositoryFactory _factory;
public DbRepository(IRepositoryFactory factory)
{
_factory = factory;
}
public IRepository<TEntity> Entity<TEntity>() where TEntity : IEntity => _factory.Get<TEntity>();
}
#if NETFRAMEWORK
}
#endif
}

View File

@@ -11,141 +11,137 @@ using System.Linq;
#endif
namespace DigitalData.Core.Infrastructure
#if NET
;
#elif NETFRAMEWORK
{
#endif
public static class DependencyInjection
{
public static IServiceCollection AddDbRepository(this IServiceCollection services, Action<RepositoryConfiguration> options)
public static class DependencyInjection
{
// register services from configuration
var cfg = new RepositoryConfiguration();
options.Invoke(cfg);
cfg.RegisterAllServices(services);
// register db repository
services.AddSingleton<IRepository, DbRepository>();
// register db repository factory
services.AddSingleton<IRepositoryFactory, DbRepositoryFactory>();
return services;
}
public class RepositoryConfiguration
{
// 1. register from assembly
private readonly Queue<Action<IServiceCollection>> RegsFromAssembly = new Queue<Action<IServiceCollection>>();
// 2. register entities (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsEntity = new Queue<Action<IServiceCollection>>();
// 3. register db set factories (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsDbSetFactory = new Queue<Action<IServiceCollection>>();
internal void RegisterAllServices(IServiceCollection services)
public static IServiceCollection AddDbRepository(this IServiceCollection services, Action<RepositoryConfiguration> options)
{
// 1. register from assembly
RegsFromAssembly.InvokeAll(services);
// register services from configuration
var cfg = new RepositoryConfiguration();
options.Invoke(cfg);
cfg.RegisterAllServices(services);
// 2. register entities (can overwrite)
RegsEntity.InvokeAll(services);
// 3. register db set factories (can overwrite)
RegsDbSetFactory.InvokeAll(services);
return services;
}
internal RepositoryConfiguration() { }
public void RegisterFromAssembly<TDbContext>(Assembly assembly) where TDbContext : DbContext
public class RepositoryConfiguration
{
void reg(IServiceCollection services)
// 1. register from assembly
private readonly Queue<Action<IServiceCollection>> RegsFromAssembly = new Queue<Action<IServiceCollection>>();
// 2. register entities (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsEntity = new Queue<Action<IServiceCollection>>();
// 3. register db set factories (can overwrite)
private readonly Queue<Action<IServiceCollection>> RegsDbSetFactory = new Queue<Action<IServiceCollection>>();
// 4. register repository
private readonly Queue<Action<IServiceCollection>> RegsRepository = new Queue<Action<IServiceCollection>>();
internal void RegisterAllServices(IServiceCollection services)
{
// scan all types in the Assembly
var entityTypes = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttribute<TableAttribute>() != null);
// 1. register from assembly
RegsFromAssembly.InvokeAll(services);
foreach (var entityType in entityTypes)
// 2. register entities (can overwrite)
RegsEntity.InvokeAll(services);
// 3. register db set factories (can overwrite)
RegsDbSetFactory.InvokeAll(services);
// 4. register repository
RegsRepository.InvokeAll(services);
}
internal RepositoryConfiguration() { }
public void RegisterFromAssembly<TDbContext>(Assembly assembly) where TDbContext : DbContext
{
void reg(IServiceCollection services)
{
#region Repository
/// register repository
// create generic DbRepository<DbContext, TEntity> type
var repositoryType = typeof(DbRepository<,>).MakeGenericType(typeof(TDbContext), entityType);
var interfaceType = typeof(IRepository<>).MakeGenericType(entityType);
// scan all types in the Assembly
var entityTypes = assembly.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttribute<TableAttribute>() != null);
// add into DI container as Scoped
services.AddScoped(interfaceType, repositoryType);
#endregion Repository
foreach (var entityType in entityTypes)
{
#region Repository
/// register repository
// create generic DbRepository<DbContext, TEntity> type
var repositoryType = typeof(DbRepository<,>).MakeGenericType(typeof(TDbContext), entityType);
var interfaceType = typeof(IRepository<>).MakeGenericType(entityType);
#region DbSetFactory
/// register DbSetFactory
var addDbSetFactoryMethod = typeof(DependencyInjection)
.GetMethod(nameof(AddDbSetFactory),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
// add into DI container as Scoped
services.AddScoped(interfaceType, repositoryType);
#endregion Repository
var genericMethod = addDbSetFactoryMethod
#region DbSetFactory
/// register DbSetFactory
var addDbSetFactoryMethod = typeof(DependencyInjection)
.GetMethod(nameof(AddDbSetFactory),
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
var genericMethod = addDbSetFactoryMethod
#if NET
!
#endif
.MakeGenericMethod(typeof(TDbContext), entityType);
genericMethod.Invoke(null, new [] { services, null });
#endregion DbSetFactory
.MakeGenericMethod(typeof(TDbContext), entityType);
genericMethod.Invoke(null, new [] { services, null });
#endregion DbSetFactory
}
}
RegsFromAssembly.Enqueue(reg);
}
RegsFromAssembly.Enqueue(reg);
}
public void RegisterEntity<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>>
public void RegisterEntity<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>>
#if NET
?
#endif
dbSetFactory = null)
where TDbContext : DbContext
where TEntity : class
{
void reg(IServiceCollection services)
=> services
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
.AddDbSetFactory(dbSetFactory);
dbSetFactory = null)
where TDbContext : DbContext
where TEntity : class
{
void reg(IServiceCollection services)
=> services
.AddScoped<IRepository<TEntity>, DbRepository<TDbContext, TEntity>>()
.AddDbSetFactory(dbSetFactory);
RegsEntity.Enqueue(reg);
RegsEntity.Enqueue(reg);
}
public void RegisterDbSetFactory<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>> dbSetFactory)
where TDbContext : DbContext
where TEntity : class
=> RegsDbSetFactory.Enqueue(s => s.AddDbSetFactory(dbSetFactory));
public void RegisterDefaultRepository<TDbContext>()
where TDbContext : DbContext
=> RegsRepository.Enqueue(s => s.AddScoped<IRepository, DbRepository<TDbContext>>());
}
public void RegisterDbSetFactory<TDbContext, TEntity>(Func<TDbContext, DbSet<TEntity>> dbSetFactory)
where TDbContext : DbContext
where TEntity : class
=> RegsDbSetFactory.Enqueue(s => s.AddDbSetFactory(dbSetFactory));
}
private static void InvokeAll<T>(this Queue<Action<T>> queue, T services)
{
while (queue.Count > 0)
queue.Dequeue().Invoke(services);
}
private static void InvokeAll<T>(this Queue<Action<T>> queue, T services)
{
while (queue.Count > 0)
queue.Dequeue().Invoke(services);
}
internal static IServiceCollection AddDbSetFactory<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>>
internal static IServiceCollection AddDbSetFactory<TDbContext, TEntity>(this IServiceCollection services, Func<TDbContext, DbSet<TEntity>>
#if NET
?
#endif
create = null)
where TDbContext : DbContext
where TEntity : class
{
create = null)
where TDbContext : DbContext
where TEntity : class
{
#if NET
create ??= ctx => ctx.Set<TEntity>();
create ??= ctx => ctx.Set<TEntity>();
#elif NETFRAMEWORK
if(create is null)
create = ctx => ctx.Set<TEntity>();
if(create is null)
create = ctx => ctx.Set<TEntity>();
#endif
services.AddSingleton(_ => new DbSetFactory<TDbContext, TEntity>(create));
return services;
services.AddSingleton(_ => new DbSetFactory<TDbContext, TEntity>(create));
return services;
}
}
}
#if NETFRAMEWORK
}
#endif
}

View File

@@ -13,9 +13,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<RepositoryType>digital data core abstractions clean architecture</RepositoryType>
<PackageTags>digital data core infrastructure clean architecture</PackageTags>
<Version>2.4.3</Version>
<AssemblyVersion>2.4.3</AssemblyVersion>
<FileVersion>2.4.3</FileVersion>
<Version>2.6.1</Version>
<AssemblyVersion>2.6.1</AssemblyVersion>
<FileVersion>2.6.1</FileVersion>
</PropertyGroup>
<ItemGroup>
@@ -41,18 +41,22 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.32" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.32" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.15" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.15" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.5" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,28 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using Microsoft.Extensions.DependencyInjection;
#if NETFRAMEWORK
using System;
#endif
namespace DigitalData.Core.Infrastructure.Factory
#if NET
;
#elif NETFRAMEWORK
{
#endif
public class DbRepositoryFactory : IRepositoryFactory
{
private readonly IServiceProvider _provider;
public DbRepositoryFactory(IServiceProvider provider)
{
_provider = provider;
}
public IRepository<TEntity> Get<TEntity>() => _provider.GetRequiredService<IRepository<TEntity>>();
}
#if NETFRAMEWORK
}
#endif

View File

@@ -0,0 +1,173 @@
using Microsoft.Extensions.DependencyInjection;
using DigitalData.Core.Abstractions;
namespace DigitalData.Core.Tests.Abstractions
{
[TestFixture]
public class FactoryTests
{
private Factory _factory;
[SetUp]
public void Setup()
{
_factory = new Factory();
}
[Test]
public void Add_ServiceDescriptor_ShouldIncreaseCount()
{
// Arrange
var descriptor = ServiceDescriptor.Singleton(typeof(string), "test");
// Act
_factory.Add(descriptor);
// Assert
Assert.That(_factory.Count, Is.EqualTo(1));
Assert.That(_factory.Contains(descriptor), Is.True);
}
[Test]
public void Clear_ShouldRemoveAllServices()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "test"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 42));
// Act
_factory.Clear();
// Assert
Assert.That(_factory.Count, Is.EqualTo(0));
}
[Test]
public void GetService_ShouldReturnRegisteredInstance()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "Hello World"));
// Act
var result = _factory.GetService(typeof(string));
// Assert
Assert.That(result, Is.EqualTo("Hello World"));
}
[Test]
public void GetService_UnregisteredType_ShouldReturnNull()
{
// Act
var result = _factory.GetService(typeof(int));
// Assert
Assert.That(result, Is.Null);
}
[Test]
public void ModifyAfterBuild_ShouldThrowInvalidOperationException()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
var _ = _factory.GetService(typeof(string)); // trigger build
// Act & Assert
Assert.Throws<InvalidOperationException>(() => _factory.Add(ServiceDescriptor.Singleton(typeof(int), 1)));
}
[Test]
public void Remove_ShouldWorkBeforeBuild()
{
// Arrange
var descriptor = ServiceDescriptor.Singleton(typeof(string), "Test");
_factory.Add(descriptor);
// Act
var removed = _factory.Remove(descriptor);
// Assert
Assert.That(removed, Is.True);
Assert.That(_factory.Count, Is.EqualTo(0));
}
[Test]
public void Insert_ShouldInsertAtCorrectIndex()
{
// Arrange
var d1 = ServiceDescriptor.Singleton(typeof(string), "A");
var d2 = ServiceDescriptor.Singleton(typeof(int), 1);
_factory.Add(d1);
_factory.Insert(0, d2);
// Act
var first = _factory[0];
// Assert
Assert.That(first.ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void Enumerator_ShouldIterateOverServices()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "a"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 5));
// Act
var list = _factory.ToList();
// Assert
Assert.That(list.Count, Is.EqualTo(2));
Assert.That(list.Any(sd => sd.ServiceType == typeof(string)), Is.True);
Assert.That(list.Any(sd => sd.ServiceType == typeof(int)), Is.True);
}
[Test]
public void CopyTo_ShouldCopyElements()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 10));
var array = new ServiceDescriptor[2];
// Act
_factory.CopyTo(array, 0);
// Assert
Assert.That(array[0].ServiceType, Is.EqualTo(typeof(string)));
Assert.That(array[1].ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void RemoveAt_ShouldRemoveItemAtIndex()
{
// Arrange
_factory.Add(ServiceDescriptor.Singleton(typeof(string), "x"));
_factory.Add(ServiceDescriptor.Singleton(typeof(int), 5));
// Act
_factory.RemoveAt(0);
// Assert
Assert.That(_factory.Count, Is.EqualTo(1));
Assert.That(_factory[0].ServiceType, Is.EqualTo(typeof(int)));
}
[Test]
public void Indexer_Set_ShouldReplaceItem()
{
// Arrange
var original = ServiceDescriptor.Singleton(typeof(string), "A");
var replacement = ServiceDescriptor.Singleton(typeof(string), "B");
_factory.Add(original);
// Act
_factory[0] = replacement;
// Assert
Assert.That(_factory[0], Is.EqualTo(replacement));
}
}
}

View File

@@ -1,115 +0,0 @@
using DigitalData.Core.Tests.Mock;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Infrastructure;
namespace DigitalData.Core.Tests.Infrastructure;
public class DbRepositoryTests
{
private IHost _host;
private IRepository Repo;
[SetUp]
public void Setup()
{
var builder = Host.CreateApplicationBuilder();
builder.Services.AddDbContext<MockDbContext>(opt => opt.UseInMemoryDatabase("MockDB"));
builder.Services.AddDbRepository(opt =>
{
opt.RegisterFromAssembly<MockDbContext>(typeof(User).Assembly);
});
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
_host = builder.Build();
Repo = _host.Services.GetRequiredService<IRepository>();
}
[TearDown]
public void TearDown()
{
if (_host is IDisposable disposableHost)
disposableHost.Dispose();
}
[TestCase(true, TestName = "WhenGivenMultipleUsers")]
[TestCase(false, TestName = "WhenGivenSingleUser")]
public void CreateAsync_ShouldNotThrow(bool multiple)
{
// Arrange
var faker = Fake.CreateUserFaker();
// Act & Assert
if (multiple)
Assert.DoesNotThrowAsync(async () => await Repo.CreateAsync(faker.Generate(Random.Shared.Next(1, 10))));
else
Assert.DoesNotThrowAsync(async () => await Repo.CreateAsync(faker.Generate()));
}
[TestCase(true, TestName = "WhenDtoUsed")]
[TestCase(false, TestName = "WhenEntityUsed")]
public async Task ReadAsync_ShouldReturnCreated(bool useDto)
{
// Act
var createdUser = useDto
? await Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto)
: await Repo.CreateAsync(Fake.User);
var readUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(readUser, Is.EqualTo(createdUser));
});
}
[Test]
public async Task ReadAsync_ShouldReturnUpdated()
{
// Arrange
var createdUser = await Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto);
var userUpdateDto = new UserUpdateDto() { Age = 10, Email = "Bar", FirstName = "Foo" };
// Act
await Repo.UpdateAsync<User, UserUpdateDto>(userUpdateDto, u => u.Id == createdUser!.Id);
var upToDateUser = await Repo.Where<User>(u => u.Id == createdUser!.Id).FirstOrDefaultAsync();
// Assert
Assert.Multiple(() =>
{
Assert.That(upToDateUser, Is.Not.Null);
Assert.That(upToDateUser?.FirstName, Is.EqualTo(userUpdateDto.FirstName));
Assert.That(upToDateUser?.Email, Is.EqualTo(userUpdateDto.Email));
Assert.That(upToDateUser?.Age, Is.EqualTo(userUpdateDto.Age));
});
}
[Test]
public async Task ReadAsync_ShouldNotReturnDeleted()
{
// Arrange
var createdUser = await Repo.CreateAsync<User, UserCreateDto>(Fake.UserCreateDto);
var readUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// Act
await Repo.DeleteAsync<User>(u => u.Id == createdUser.Id);
var deletedUser = await Repo.Where<User>(u => u.Id == createdUser.Id).FirstOrDefaultAsync();
// Assert
Assert.Multiple(() =>
{
Assert.That(readUser, Is.Not.Null);
Assert.That(deletedUser, Is.Null);
});
}
}

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
using System.ComponentModel.DataAnnotations.Schema;
namespace DigitalData.Core.Tests.Mock;
@@ -13,4 +13,4 @@ public class User : UserBase, IEntity
public override bool Equals(object? obj)
=> (obj is User user && user.GetHashCode() == GetHashCode())
|| (obj is UserBase userBase && userBase.GetHashCode() == base.GetHashCode());
}
}

View File

@@ -1,7 +1,7 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
namespace DigitalData.Core.Tests.Mock;
public class UserCreateDto : UserBase, IDto<User>
{
}
}

View File

@@ -1,7 +1,7 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
namespace DigitalData.Core.Tests.Mock;
public class UserReadDto : UserBase, IDto<User>
{
}
}

View File

@@ -1,7 +1,7 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Abstractions.Interfaces;
namespace DigitalData.Core.Tests.Mock;
public class UserUpdateDto : UserBase, IDto<User>
{
}
}

View File

@@ -55,8 +55,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Abstractio
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{E18417C2-D9F5-437A-9ED5-473DD6260066}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Abstraction.Repository", "DigitalData.Core.Abstraction.Repository\DigitalData.Core.Abstraction.Repository.csproj", "{07E36C52-C402-43D7-B8C5-0626D2B3E388}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -130,10 +128,6 @@ Global
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Debug|Any CPU.Build.0 = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{420C35A7-0EDE-4E2E-8500-484B57B0367A}.Release|Any CPU.Build.0 = Release|Any CPU
{07E36C52-C402-43D7-B8C5-0626D2B3E388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07E36C52-C402-43D7-B8C5-0626D2B3E388}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07E36C52-C402-43D7-B8C5-0626D2B3E388}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07E36C52-C402-43D7-B8C5-0626D2B3E388}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -160,7 +154,6 @@ Global
{8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{420C35A7-0EDE-4E2E-8500-484B57B0367A} = {E18417C2-D9F5-437A-9ED5-473DD6260066}
{E18417C2-D9F5-437A-9ED5-473DD6260066} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{07E36C52-C402-43D7-B8C5-0626D2B3E388} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}