xhafan / coreddd Goto Github PK
View Code? Open in Web Editor NEWA set of open-source .NET libraries helping with DDD (domain-driven design) and CQRS (command query responsibility segregation).
License: MIT License
A set of open-source .NET libraries helping with DDD (domain-driven design) and CQRS (command query responsibility segregation).
License: MIT License
This would support having different connection strings in appsettings.{environment}.json files with one version of hibernate.cfg.xml
When registering NHibernate configurator like this:
services.AddCoreDddNhibernate(new NhibernateConfigurator(connectionString: "a connection string"));
dependency injection does not dispose the NHibernate configurator instance.
ASP.NET: Raising delayed domain events on application_endrequest where domain event handler throws an exception would cause the already committed unit of work to incorrectly rollback.
Got this random exception when starting a transaction in NhibernateUnitOfWork.BeginTransaction
, line Session.BeginTransaction(isolationLevel)
:
NHibernate.TransactionException: Begin failed with SQL exception
---> Npgsql.NpgsqlException (0x80004005): Exception while reading from stream
---> System.TimeoutException: Timeout during reading attempt
at Npgsql.Internal.NpgsqlReadBuffer.<Ensure>g__EnsureLong|41_0(NpgsqlReadBuffer buffer, Int32 count, Boolean async, Boolean readingNotifications)
at Npgsql.Internal.NpgsqlConnector.RawOpen(SslMode sslMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken, Boolean isFirstAttempt)
at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|191_1(NpgsqlConnector conn, SslMode sslMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken, Boolean isFirstAttempt)
at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.ConnectorPool.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.ConnectorPool.<Get>g__RentAsync|28_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|45_0(Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnection.Open()
at NHibernate.Connection.DriverConnectionProvider.GetConnection(String connectionString)
at NHibernate.AdoNet.ConnectionManager.GetConnection()
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
--- End of inner exception stack trace ---
at NHibernate.Transaction.AdoTransaction.Begin(IsolationLevel isolationLevel)
at NHibernate.Impl.AbstractSessionImpl.BeginTransaction(IsolationLevel isolationLevel)
at CoreDdd.Nhibernate.UnitOfWorks.NhibernateUnitOfWork.BeginTransaction(IsolationLevel isolationLevel)
at CoreDdd.AspNetCore.Middlewares.BaseUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next, IUnitOfWork unitOfWork)
And when disposing the NhibernateUnitOfWork
instance, got another exception:
NHibernate.TransactionException: Transaction not successfully started
at NHibernate.Transaction.AdoTransaction.CheckBegun()
at NHibernate.Transaction.AdoTransaction.Commit()
at CoreDdd.Nhibernate.UnitOfWorks.NhibernateUnitOfWork.Commit()
at CoreDdd.Nhibernate.UnitOfWorks.NhibernateUnitOfWork.Dispose()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.DisposeAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Http.Features.RequestServicesFeature.<DisposeAsync>g__Awaited|9_0(RequestServicesFeature servicesFeature, ValueTask vt)
at Microsoft.AspNetCore.Server.Kestrel.Core
The second exception is avoidable by an improved detection that the transaction has not been started on the NhibernateUnitOfWork
instance.
Support NativeAOT and trimming. Take inspiration from: https://www.npgsql.org/doc/release-notes/8.0.html#nativeaot-and-trimming-support
When using UnitOfWorkMiddleware
or UnitOfWorkDependencyInjectionMiddleware
and delayed domain event raising (DomainEvents.Initialize(..., isDelayedDomainEventHandlingEnabled: true);
, the delayed domain events are raised even if the main transaction fails and is rolled back.
Currently only session-factory-scoped interceptors are supported via overriding BaseNhibernateConfigurator.AdditionalConfiguration
:
protected override void AdditionalConfiguration(Configuration configuration)
{
configuration.SetInterceptor(new SomeInterceptor());
}
Quick idea for the support for session-scoped intereceptors:
public NhibernateUnitOfWork(INhibernateConfigurator configurator, IInterceptor interceptor = null)
{
_configurator = configurator;
_interceptor = interceptor;
}
...
public void BeginTransaction(System.Data.IsolationLevel isolationLevel = System.Data.IsolationLevel.Unspecified)
{
_isInTransactionScope = Transaction.Current != null;
var sessionFactory = _configurator.GetSessionFactory();
#if !NET40 && !NET45
if (_interceptor != null)
{
sessionFactory.WithOptions().Interceptor(_interceptor);
}
Session = sessionFactory.OpenSession();
#else
Session = _interceptor != null
? sessionFactory.OpenSession(_interceptor)
: sessionFactory.OpenSession();
#endif
if (_isInTransactionScope) return;
Session.BeginTransaction(isolationLevel);
}
Maybe support collection of intereceptors, and use the following CompositeInterceptor
:
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.SqlCommand;
using NHibernate.Type;
// taken from https://stackoverflow.com/a/33289268/379279 and modified for nullable
public class CompositeInterceptor(IEnumerable<IInterceptor> interceptors) : IInterceptor
{
private readonly IEnumerable<IInterceptor> _interceptors = interceptors.ToList();
public void AfterTransactionBegin(ITransaction tx)
{
foreach (var interceptor in _interceptors)
{
interceptor.AfterTransactionBegin(tx);
}
}
public void AfterTransactionCompletion(ITransaction tx)
{
foreach (var interceptor in _interceptors)
{
interceptor.AfterTransactionCompletion(tx);
}
}
public void BeforeTransactionCompletion(ITransaction tx)
{
foreach (var interceptor in _interceptors)
{
interceptor.BeforeTransactionCompletion(tx);
}
}
public int[]? FindDirty(
object entity,
object id,
object[] currentState,
object[] previousState,
string[] propertyNames,
IType[] types
)
{
var results = _interceptors
.Select(interceptor => interceptor.FindDirty(entity, id, currentState, previousState, propertyNames, types))
.Where(result => result != null)
.SelectMany(x => x)
.Distinct()
.ToArray();
return !results.Any() ? null : results;
}
public object? GetEntity(string entityName, object id)
{
return _interceptors
.Select(interceptor => interceptor.GetEntity(entityName, id))
.FirstOrDefault(result => result != null);
}
public string? GetEntityName(object entity)
{
return _interceptors
.Select(interceptor => interceptor.GetEntityName(entity))
.FirstOrDefault(result => result != null);
}
public object? Instantiate(string entityName, object id)
{
return _interceptors
.Select(interceptor => interceptor.Instantiate(entityName, id))
.FirstOrDefault(result => result != null);
}
public bool? IsTransient(object entity)
{
return _interceptors
.Select(interceptor => interceptor.IsTransient(entity))
.FirstOrDefault(result => result != null);
}
public void OnCollectionRecreate(object collection, object key)
{
foreach (var interceptor in _interceptors)
{
interceptor.OnCollectionRecreate(collection, key);
}
}
public void OnCollectionRemove(object collection, object key)
{
foreach (var interceptor in _interceptors)
{
interceptor.OnCollectionRemove(collection, key);
}
}
public void OnCollectionUpdate(object collection, object key)
{
foreach (var interceptor in _interceptors)
{
interceptor.OnCollectionUpdate(collection, key);
}
}
public void OnDelete(
object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types
)
{
foreach (var interceptor in _interceptors)
{
interceptor.OnDelete(entity, id, state, propertyNames, types);
}
}
public bool OnFlushDirty(
object entity,
object id,
object[] currentState,
object[] previousState,
string[] propertyNames,
IType[] types
)
{
return _interceptors.Any(interceptor => interceptor.OnFlushDirty(entity, id, currentState, previousState, propertyNames, types));
}
public bool OnLoad(
object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types
)
{
return _interceptors.Any(interceptor => interceptor.OnLoad(entity, id, state, propertyNames, types));
}
public SqlString OnPrepareStatement(SqlString sql)
{
return _interceptors.Aggregate(sql, (current, interceptor) => interceptor.OnPrepareStatement(current));
}
public bool OnSave(
object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types
)
{
return _interceptors.Any(interceptor => interceptor.OnSave(entity, id, state, propertyNames, types));
}
public void PostFlush(ICollection entities)
{
foreach (var interceptor in _interceptors)
{
interceptor.PostFlush(entities);
}
}
public void PreFlush(ICollection entities)
{
foreach (var interceptor in _interceptors)
{
interceptor.PreFlush(entities);
}
}
public void SetSession(ISession session)
{
foreach (var interceptor in _interceptors)
{
interceptor.SetSession(session);
}
}
}
More info about interceptors:
NhibernateRepository does not support fetching aggregate root entities using a lock.
Please add a comment which dependency injection frameworks are worth supporting. Based on this SO question, I could add a support for Autofac, Structure Map, Spring.NET or others if there is demand for it.
Experienced int ids exhaustion, and my opinion now is to support ids of type long instead of int by default.
Consider bumping up the major version to version 4.
NhibernateUnitOfWork rollback flushes the session before rolling back the transaction. When there is an entity in the session which cannot be persisted (e.g. due to some sql error), flushing the session would re-attempt to persist the entity and would result in another exception.
Could not define another convention derived from ForeignKeyConvention
as it clashes with ForeignKeyNameConvention
.
Allow to disable some of default CoreDdd.Nhibernate conventions.
Currently one needs to override methods in NhibernateConfigurator
to customize NHibernate mapping.
Examples:
BaseNhibernateConfigurator.GetComponentTypes()
(code sample here)BaseNhibernateConfigurator.GetDiscriminatedTypes()
(code sample here)The same goes for other methods in BaseNhibernateConfigurator
(GetIncludeBaseTypes
, GetIgnoreBaseTypes
).
It would be handy to have a set of attributes to mark a class as NHibernate component, or mark it as a discriminated type, or mark a base class as "included" in mapping or "ignored" from mapping.
Error:
2022-04-26 13:06:35.722 +02:00 [ERR] An unhandled exception has occurred while executing the request.
System.InvalidCastException: Unable to cast object of type '<some entity>' to type 'NHibernate.Proxy.INHibernateProxy'.
at PointOfInterestProxy.NHibernate.Proxy.INHibernateProxy.get_HibernateLazyInitializer()
at NHibernate.Engine.StatefulPersistenceContext.Clear()
at NHibernate.Impl.SessionImpl.Close()
at NHibernate.Impl.SessionImpl.Dispose(Boolean isDisposing)
at NHibernate.Impl.SessionImpl.Dispose()
at CoreDdd.Nhibernate.UnitOfWorks.NhibernateUnitOfWork.RollbackAsync()
at CoreDdd.AspNetCore.Middlewares.BaseUnitOfWorkMiddleware.InvokeAsync(HttpContext context, RequestDelegate next, IUnitOfWork unitOfWork)
at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
How to replicate: Hot Reload a change, execute the change couple of times.
One has to manually map nullable component properties as nullable (componentPart.Map(x => x.Property, "ColumnName").Nullable();
). Try to make ColumnNullabilityConvention
work for components.
Change BaseNhibernateConfigurator
default implementation
protected virtual Func<Type, bool> GetIsTypeDtoFunc()
{
return type => type.Name.EndsWith("Dto");
}
not to map dtos into the database automatically.
The original exception it thus swallowed. This happens for instance with Npgsql when a sql command times out. Npgsql throws an exception on command timeout, and NhibernateUnitOfWork rollback throws "Transaction not connected, or was disconnected".
Currently one needs to completely override BaseNhibernateConfigurator.ConfigureNhibernate
to support NHibernate interceptors.
HasManyConvention
automapping supports only lists and sets, and not maps. IDictionary
collection is incorrectly marked AsSet
.
Use cases:
Unsupported netcoreapp2.1 is still in use for some projects. Nuget references use ~2018/2019 versions of packages.
Currently NHibernate components needs to be mapped manually (e.g. entity.Component(x => x.SomeComponent);
Make sure component classes can be easily identified as NHibernate components to avoid the manual mapping. Also make sure these kind of additional NHibernate configurations can be added without the CoreDdd libs code change.
Add to CommonNugetAssemblyInfo.props:
<WarningsAsErrors>NU1605;CS4014</WarningsAsErrors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Add JetBrains.Annotations source code into CoreUtils as per https://blog.jetbrains.com/dotnet/2015/08/12/how-to-use-jetbrains-annotations-to-improve-resharper-inspections/ , and annotate Guard.Hope
calls
When delayed domain event raising is enabled (DomainEvents.Initialize(..., isDelayedDomainEventHandlingEnabled: true);
), RebusUnitOfWork
does not support delayed domain event raising.
"Transaction not connected, or was disconnected" thrown by rollback call within a commit call. Rollback within commit should not throw anything and it's exception should be swallowed.
One has to explicitly add an automapping override:
mapping.HasMany(x => x.ChildEntities).AsList();
or
mapping.HasMany(x => x.ChildEntities).AsList(x => x.Column("IndexColumnName"));
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.