diff --git a/.build/definitions.cake b/.build/definitions.cake
index b0cca17e7..e8f1c7d8a 100644
--- a/.build/definitions.cake
+++ b/.build/definitions.cake
@@ -1,21 +1,35 @@
// ADDINS
-#addin nuget:?package=Cake.Coveralls&version=4.0.0
-#addin nuget:?package=Cake.FileHelpers&version=7.0.0
-#addin nuget:?package=Cake.AppVeyor&version=6.0.0
+#addin nuget:?package=Cake.Coverlet&version=6.0.1
+#addin nuget:?package=Cake.FileHelpers&version=9.0.0
+#addin nuget:?package=Cake.AppVeyor&version=10.0.0
// TOOLS
-#tool nuget:?package=GitReleaseManager&version=0.20.0
-#tool nuget:?package=GitVersion.CommandLine&version=5.12.0
-#tool nuget:?package=OpenCover&version=4.7.1221
+#tool nuget:?package=GitReleaseManager.Tool&version=0.20.0
+#tool "dotnet:?package=GitVersion.Tool&version=6.0.0"
#tool nuget:?package=ReportGenerator&version=5.4.8
public class CodeCoverageSettings
{
- public string ExcludeByFile { get; set; } = "*/*Designer.cs";
- public string ExcludeByAttribute { get; set; } = "*.ExcludeFromCodeCoverage*";
- public string ExcludeFilter { get; set; } = "-[Tests*]*;-[*]Microsoft.CodeAnalysis*;-[*]System.Runtime.CompilerServices.*";
- public string IncludeFilter { get; set; }
+ ///
+ /// Glob pattern to exclude source files from coverage (Coverlet format, e.g. "**/*Designer.cs").
+ ///
+ public string ExcludeByFile { get; set; } = "**/*Designer.cs";
+
+ ///
+ /// Attribute short name used to exclude members from coverage (e.g. "ExcludeFromCodeCoverage").
+ ///
+ public string ExcludeByAttribute { get; set; } = "ExcludeFromCodeCoverage";
+
+ ///
+ /// Coverlet exclude filters in [Assembly]Type format, e.g. "[Tests*]*".
+ ///
+ public List ExcludeFilter { get; set; } = new List { "[Tests*]*", "[*]Microsoft.CodeAnalysis*", "[*]System.Runtime.CompilerServices.*" };
+
+ ///
+ /// Coverlet include filters in [Assembly]Type format, e.g. "[SharpArch.*]*".
+ ///
+ public List IncludeFilter { get; set; } = new List();
}
// params
@@ -40,7 +54,7 @@ public class ProjectSettings {
SolutionName = solutionName;
CodeCoverage = new CodeCoverageSettings {
- IncludeFilter = $"+[solutionName*]*"
+ IncludeFilter = new List { $"[{solutionName}*]*" }
};
}
}
@@ -101,25 +115,26 @@ public class Paths {
public DirectoryPath RootDir { get; }
public string SrcDir { get; set; }
public string ArtifactsDir { get; set; }
- public string TestCoverageOutputFile { get; set; }
+ /// Glob matching the per-test-project, per-TargetFramework Cobertura XML files Coverlet writes
+ /// (e.g. coverage.SharpArch.XunitTests.net10.0.cobertura.xml). Each test project is run separately with a
+ /// unique CoverletOutputName so projects/TFMs don't overwrite each other. Consumed both by ReportGenerator
+ /// (local HTML report) and by the Coveralls upload loop.
+ public string TestCoverageGlobPattern { get; set; }
public string TestCoverageReportDir { get; set; }
public string PackagesDir { get; set; }
public string BuildPropsFile { get; set; }
- public string TestsRootDir { get; set; }
- public string SamplesRootDir { get; set; }
public string CommonAssemblyVersionFile { get; set; }
public Paths(ICakeContext context)
{
RootDir = context.MakeAbsolute(context.Directory("./"));
- SrcDir = RootDir.Combine("src").ToString();
+ SrcDir = RootDir.Combine("Src").ToString();
ArtifactsDir = RootDir.Combine("artifacts").ToString();
- TestCoverageOutputFile = ArtifactsDir + "/OpenCover.xml";
+ TestCoverageGlobPattern = ArtifactsDir + "/coverage.*.cobertura.*.xml";
TestCoverageReportDir = ArtifactsDir + "/CodeCoverageReport";
PackagesDir = ArtifactsDir + "/packages";
BuildPropsFile = SrcDir + "/Directory.Build.props";
- TestsRootDir = SrcDir + "/tests";
- CommonAssemblyVersionFile = SrcDir + "/common/AssemblyVersion.cs";
+ CommonAssemblyVersionFile = SrcDir + "/Common/AssemblyVersion.cs";
}
}
@@ -133,6 +148,9 @@ public class BuildInfo {
public bool IsRelease {get; protected set;}
public bool IsLocal { get; protected set; }
+
+ public bool IsPullRequest { get; protected set; }
+
public string AppVeyorJobId { get; protected set; }
public BuildVersion Version { get; protected set; }
@@ -151,18 +169,39 @@ public class BuildInfo {
throw new ArgumentNullException(nameof(context));
var target = context.Argument("target", "Default");
var config = context.Argument("buildConfig", "Release");
- var buildSystem = context.BuildSystem();
- // Calculate version and commit hash
- GitVersion semVersion = context.GitVersion();
- var version = new BuildVersion(
- semVersion.NuGetVersion,
- semVersion.FullBuildMetaData,
- semVersion.InformationalVersion,
- $"{semVersion.Major+1}.0.0",
- semVersion.Sha,
- semVersion.MajorMinorPatch
- );
+ var buildSystem = context.BuildSystem();
+ var repositoryInfo = RepositoryInfo.Get(buildSystem, settings);
+ BuildVersion version;
+
+ if (repositoryInfo.IsPullRequest) {
+ // GitVersion fails on PR builds, use 0.PullRequestId.BuildNumber as a version number
+ var buildVersion = $"0.{buildSystem.AppVeyor.Environment.PullRequest.Number}.{buildSystem.AppVeyor.Environment.Build.Number}";
+ var commitHash = buildSystem.AppVeyor.Environment.Repository.Commit.Id;
+
+ version = new BuildVersion(
+ buildVersion,
+ $"{buildVersion}/{commitHash}-PR-{buildSystem.AppVeyor.Environment.PullRequest.Title}",
+ buildVersion,
+ $"{buildVersion}.0",
+ commitHash,
+ buildVersion
+ );
+ }
+ else {
+ // Calculate version and commit hash
+ // GitVersion 6.0 removed the NuGetVersion variable; SemVer is its SemVer2-compatible
+ // replacement and is accepted by modern NuGet (v3+).
+ GitVersion semVersion = context.GitVersion();
+ version = new BuildVersion(
+ semVersion.SemVer,
+ semVersion.FullBuildMetaData,
+ semVersion.InformationalVersion,
+ $"{semVersion.Major+1}.0.0",
+ semVersion.Sha,
+ semVersion.MajorMinorPatch
+ );
+ }
var gitHubToken = context.EnvironmentVariable("GITHUB_TOKEN");
@@ -172,6 +211,7 @@ public class BuildInfo {
IsDebug = string.Equals(config, "Debug", StringComparison.OrdinalIgnoreCase),
IsRelease = string.Equals(config, "Release", StringComparison.OrdinalIgnoreCase),
IsLocal = buildSystem.IsLocalBuild,
+ IsPullRequest = repositoryInfo.IsPullRequest,
AppVeyorJobId = buildSystem.AppVeyor.Environment.JobId,
Version = version,
Repository = RepositoryInfo.Get(buildSystem, settings),
@@ -181,5 +221,3 @@ public class BuildInfo {
};
}
}
-
-
diff --git a/.build/tasks.cake b/.build/tasks.cake
index b151fd209..e4be9e7fd 100644
--- a/.build/tasks.cake
+++ b/.build/tasks.cake
@@ -39,62 +39,69 @@ Task("Restore")
Task("RunXunitTests")
.Does(build =>
{
- var projectPath = build.Paths.SrcDir;
- var projectFilename = build.Settings.SolutionName;
- // keep in sync with src/Directory.Build.props
var solutionFullPath = new DirectoryPath(build.Paths.SrcDir).Combine(build.Settings.SolutionName) + ".sln";
+
+ // Build DotNetTestSettings for a given configuration and log-file name.
+ DotNetTestSettings BuildTestSettings(string buildCfg, string logFilename)
{
- Func buildProcessArgs = (buildCfg) => {
- var pb = new ProcessArgumentBuilder()
- .AppendSwitch("--configuration", buildCfg)
- .AppendSwitch("--filter", "Category!=ManualTests")
- .AppendSwitch("--results-directory", build.Paths.ArtifactsDir)
- .Append("--no-restore")
- .Append("--no-build");
- if (!build.IsLocal) {
- pb.AppendSwitch("--test-adapter-path", ".")
- .AppendSwitch("--logger", "AppVeyor");
- }
- else {
- pb.AppendSwitch("--logger", $"trx;LogFileName={projectFilename}.trx");
+ var ts = new DotNetTestSettings
+ {
+ Configuration = buildCfg,
+ Filter = "Category!=ManualTests",
+ ResultsDirectory = new DirectoryPath(build.Paths.ArtifactsDir),
+ NoRestore = true,
+ NoBuild = true,
+ ArgumentCustomization = args =>
+ {
+ if (!build.IsLocal)
+ return args.AppendSwitch("--test-adapter-path", ".")
+ .AppendSwitch("--logger", "AppVeyor");
+ return args.AppendSwitch("--logger", $"trx;LogFileName={logFilename}.trx");
}
- return pb;
};
+ return ts;
+ }
- Information("Calculating code coverage for {0} ...", projectFilename);
+ // Coverlet's MSBuild integration runs per test project. Testing the whole solution in one call makes
+ // every project write to the same CoverletOutputDirectory with the same CoverletOutputName, so each TFM
+ // slot is overwritten by the *last* project that runs and only that project's coverage survives. Instead,
+ // test each project individually with a unique CoverletOutputName; Coverlet still appends the TFM for
+ // multi-targeted projects, producing files like coverage...cobertura.xml. UploadCoverage
+ // then sends each file to Coveralls as a distinct parallel job so the full SharpArch.* surface is covered
+ // instead of just the last project's slice.
+ var testProjects = GetFiles($"{build.Paths.SrcDir}/**/*.csproj")
+ .Where(f => f.GetFilenameWithoutExtension().ToString().Contains("Tests"))
+ .OrderBy(f => f.GetFilenameWithoutExtension().ToString())
+ .ToList();
- var openCoverSettings = new OpenCoverSettings
+ foreach (var project in testProjects)
+ {
+ var projectName = project.GetFilenameWithoutExtension().ToString();
+ var coverletSettings = new CoverletSettings
{
- OldStyle = true,
- ReturnTargetCodeOffset = 0,
- ArgumentCustomization = args => args.Append("-mergeoutput").Append("-hideskipped:File;Filter;Attribute"),
- WorkingDirectory = projectPath,
- }
- .WithFilter($"{build.Settings.CodeCoverage.IncludeFilter} {build.Settings.CodeCoverage.ExcludeFilter}")
- .ExcludeByAttribute(build.Settings.CodeCoverage.ExcludeByAttribute)
- .ExcludeByFile(build.Settings.CodeCoverage.ExcludeByFile);
-
- // run open cover for debug build configuration
- OpenCover(
- tool => tool.DotNetTool(
- projectPath.ToString(),
- "test",
- buildProcessArgs("Debug")
- ),
- build.Paths.TestCoverageOutputFile,
- openCoverSettings
+ CollectCoverage = true,
+ CoverletOutputFormat = CoverletOutputFormat.cobertura,
+ CoverletOutputDirectory = new DirectoryPath(build.Paths.ArtifactsDir),
+ CoverletOutputName = $"coverage.{projectName}.cobertura.xml",
+ ExcludeByFile = new List { build.Settings.CodeCoverage.ExcludeByFile },
+ ExcludeByAttribute = new List { build.Settings.CodeCoverage.ExcludeByAttribute },
+ Include = build.Settings.CodeCoverage.IncludeFilter,
+ Exclude = build.Settings.CodeCoverage.ExcludeFilter,
+ };
+
+ Information("Calculating code coverage for {0} ...", projectName);
+ DotNetTest(
+ project.FullPath,
+ BuildTestSettings("Debug", projectName),
+ coverletSettings
);
+ }
- // run tests again if Release mode was requested
- if (build.IsRelease)
- {
- Information("Running Release mode tests for {0} ...", projectFilename);
- DotNetTool(
- solutionFullPath,
- "test",
- buildProcessArgs("Release")
- );
- }
+ // Run Release-mode tests (no coverage) when a Release build was requested.
+ if (build.IsRelease)
+ {
+ Information("Running Release mode tests for {0} ...", build.Settings.SolutionName);
+ DotNetTest(solutionFullPath, BuildTestSettings("Release", build.Settings.SolutionName));
}
})
.DeferOnError();
@@ -102,8 +109,7 @@ Task("RunXunitTests")
Task("CleanPreviousTestResults")
.Does(build =>
{
- if (FileExists(build.Paths.TestCoverageOutputFile))
- DeleteFile(build.Paths.TestCoverageOutputFile);
+ DeleteFiles(build.Paths.ArtifactsDir + "/coverage.*.cobertura.xml");
DeleteFiles(build.Paths.ArtifactsDir + "/*.trx");
if (DirectoryExists(build.Paths.TestCoverageReportDir))
DeleteDirectory(build.Paths.TestCoverageReportDir, new DeleteDirectorySettings
@@ -117,17 +123,90 @@ Task("GenerateCoverageReport")
.WithCriteria((ctx, build) => build.IsLocal)
.Does(build =>
{
- ReportGenerator((FilePath)build.Paths.TestCoverageOutputFile, build.Paths.TestCoverageReportDir);
+ ReportGenerator(new GlobPattern(build.Paths.TestCoverageGlobPattern), build.Paths.TestCoverageReportDir);
});
Task("UploadCoverage")
- .WithCriteria((ctx, build) => !build.IsLocal)
+ .WithCriteria((ctx, build) => build.IsPullRequest == false && build.IsLocal == false)
.Does(build =>
{
- CoverallsNet(build.Paths.TestCoverageOutputFile, CoverallsNetReportType.OpenCover, new CoverallsNetSettings()
+ // Uses the self-contained coveralls-windows.exe "coverage-reporter" binary (no .NET runtime
+ // dependency) instead of the abandoned, .NET-6-only coveralls.net/Cake.Coveralls tool.
+ //
+ // Each per-project/per-TFM Cobertura file is uploaded as a DISTINCT parallel job. The
+ // coverage-reporter does NOT merge overlapping coverage within a single job (its
+ // SourceFiles.add just concatenates source-file entries), so a single multi-file `report` —
+ // or several `report` calls sharing the auto-detected AppVeyor service_job_id — would
+ // concatenate and lose data. Distinct --job-id per upload makes Coveralls treat each file as
+ // a separate parallel job; the final `coveralls done` fires the completion webhook that
+ // aggregates all jobs for this build (service_number, auto-detected from AppVeyor) with
+ // server-side per-line-max merging. `done` MUST run even when individual uploads fail,
+ // otherwise Coveralls never finalizes the merge — hence the try/finally.
+ var reporterExe = EnvironmentVariable("COVERALLS_REPORTER_EXE") ?? "coveralls-windows.exe";
+ var buildNumber = EnvironmentVariable("APPVEYOR_BUILD_NUMBER");
+
+ var coverageFiles = GetFiles(build.Paths.TestCoverageGlobPattern)
+ .OrderBy(f => f.GetFilename().ToString())
+ .ToList();
+
+ if (coverageFiles.Count == 0)
{
- RepoTokenVariable = "COVERALLS_REPO_TOKEN"
- });
+ Warning("No coverage files found matching {0}; skipping Coveralls upload.", build.Paths.TestCoverageGlobPattern);
+ return;
+ }
+
+ var failures = new List();
+ var uploaded = 0;
+ try
+ {
+ foreach (var file in coverageFiles)
+ {
+ // Distinct job id per project+TFM (prefixed with the build number for cross-build
+ // uniqueness) so each upload is a separate parallel job rather than overwriting the
+ // auto-detected AppVeyor service_job_id that all calls in this job share.
+ var jobId = string.IsNullOrEmpty(buildNumber)
+ ? file.GetFilenameWithoutExtension().ToString()
+ : $"{buildNumber}-{file.GetFilenameWithoutExtension()}";
+
+ Information("Uploading coverage {0} to Coveralls (parallel job {1}) ...", file.GetFilename(), jobId);
+
+ var exitCode = StartProcess(reporterExe, new ProcessSettings
+ {
+ Arguments = new ProcessArgumentBuilder()
+ .Append("report")
+ .AppendQuoted(file.FullPath)
+ .Append("--format=cobertura")
+ .Append("--parallel")
+ .Append($"--job-id={jobId}")
+ .Append("--no-logo")
+ });
+
+ if (exitCode != 0)
+ failures.Add($"{file.GetFilename()} (exit code {exitCode})");
+ else
+ uploaded++;
+ }
+ }
+ finally
+ {
+ // Fire the completion webhook so Coveralls aggregates the parallel jobs for this build,
+ // regardless of whether some uploads failed. Only finalize if at least one job was sent.
+ if (uploaded > 0)
+ {
+ Information("Finalizing Coveralls parallel build ...");
+ var doneExitCode = StartProcess(reporterExe, new ProcessSettings
+ {
+ Arguments = new ProcessArgumentBuilder()
+ .Append("done")
+ .Append("--no-logo")
+ });
+ if (doneExitCode != 0)
+ failures.Add($"coveralls done (exit code {doneExitCode})");
+ }
+ }
+
+ if (failures.Count > 0)
+ throw new Exception("Coveralls upload reported failures:\n" + string.Join("\n", failures));
});
Task("RunUnitTests")
diff --git a/.clinerules b/.clinerules
new file mode 100644
index 000000000..794f1e7d6
--- /dev/null
+++ b/.clinerules
@@ -0,0 +1,116 @@
+# .clinerules
+
+This file provides guidance to Cline when working with code in this repository.
+
+## Project Settings
+
+- **Stack:** dotnet
+
+## What this is
+
+S#arp Architecture is a framework (a set of NuGet libraries, not an application) for building
+maintainable ASP.NET Core web applications using Domain-Driven Design on top of NHibernate.
+The shipped product is the set of `SharpArch.*` packages under `Src/Lib/`; everything else
+(`Src/Tests/`, `Src/Samples/`) exists to test and demonstrate them.
+
+**Ignore the `Old/` folder** — it holds legacy (pre-v5, ASP.NET MVC / Castle Windsor) samples kept
+for reference only. Do not modify, build, or use it as a pattern for new work.
+
+## Commands
+
+All commands run from the repository root unless noted. There is a single solution: `Src/SharpArch.sln`.
+
+```bash
+# Restore + build (Debug or Release)
+cd Src && dotnet build -c Release
+
+# Run the full unit-test suite (excludes DB integration & functional tests)
+cd Src && dotnet test -c Release --filter "Category!=IntegrationTests&Category!=Functional"
+
+# Run a single test project
+dotnet test Src/Tests/SharpArch.XunitTests/SharpArch.XunitTests.csproj
+
+# Run a single test by name (substring match on fully-qualified name)
+dotnet test Src/SharpArch.sln --filter "FullyQualifiedName~EntityTests"
+
+# Full CI pipeline locally (build + tests + coverage + pack) via Cake
+dotnet tool install Cake.Tool --global # once
+dotnet cake # runs the "Default" target from build.cake
+dotnet cake --target=RunUnitTests # build + tests + coverage only
+```
+
+### Test categories & the database
+
+- Unit tests use an **in-memory SQLite** database and need no setup.
+- Tests tagged `[Trait("Category","IntegrationTests")]` / `Functional` / `ManualTests` hit a **real SQL
+ Server** and are excluded from the default run. To run them locally, start SQL Server first:
+ `pwsh Docker/start-mssql.ps1` (SQL Server 2019 container on port **2433**, sa password `Password12!`).
+
+## Target frameworks
+
+Defined centrally in `Src/Directory.Build.props`:
+- **Libraries** (`Src/Lib/`): `netstandard2.1;net8.0;net9.0`
+- **Apps & test projects**: `net8.0;net9.0`
+
+## Architecture
+
+Strict DDD layering enforced by project references — the dependency direction is the point of the framework.
+
+- **`SharpArch.Domain`** — the core, with **no infrastructure dependencies**. Contains the domain
+ building blocks and, crucially, the *persistence-support interfaces* (`IRepository<,>`,
+ `ILinqRepository<,>`, `ITransactionManager`, `IEntityDuplicateChecker`) that the domain depends on
+ but does not implement. This inversion lets domain and application code stay ORM-agnostic.
+ - `Entity` / `IEntity` — identity-based equality. Two entities are equal if they share a
+ non-default `Id`; transient entities fall back to comparing *domain signature* properties.
+ - `[DomainSignature]` — marks the properties that define business identity; consumed by
+ `Entity.GetHashCode`/`Equals` and by `HasUniqueDomainSignatureAttribute` validation.
+ - `ValueObject`, `BaseObject`, `ValidatableObject` — value-equality and validation base types.
+ - `Specifications/` — `ILinqSpecification` / `QuerySpecification` (Specification pattern) used
+ by the LINQ repository.
+- **`SharpArch.NHibernate`** — the NHibernate implementation of the domain's persistence interfaces:
+ `NHibernateRepository`/`LinqRepository`, `TransactionManager`, and `NHibernateSessionFactoryBuilder`
+ (fluent builder for `ISessionFactory`, supporting external `hibernate.cfg.xml`, Fluent
+ auto-persistence models, 2nd-level cache, and data-annotation validators).
+- **`SharpArch.NHibernate.DependencyInjection`** — `AddNHibernateWithSingleDatabase(...)` wires the
+ above into `IServiceCollection`. Lifetimes matter: **`ISessionFactory` = Singleton**,
+ **`ISession` = Scoped** (per HTTP request), **`IStatelessSession` = Transient**, `TransactionManager`
+ = Scoped.
+- **`SharpArch.Web.AspNetCore`** — MVC integration. `[Transaction]` is a *marker* attribute
+ (class/method/global); it does nothing unless `AutoTransactionHandler` is registered as an MVC
+ filter. The handler opens a transaction per action and commits/rolls back based on the outcome
+ (rollback on unhandled exception, and optionally on model-validation errors).
+- **`SharpArch.Infrastructure`** — cross-cutting helpers (`CodeBaseLocator`, logging wrappers).
+- **`SharpArch.Testing[.Xunit|.NUnit][.NHibernate]`** — base classes for consumers' tests, e.g.
+ `RepositoryTestsBase` / `TransientDatabaseTests` that spin up a throwaway DB per test.
+
+Typical consumer flow: define entities in a Domain layer → depend only on `IRepository`/`ILinqRepository`
+→ compose an ASP.NET Core app that calls `AddNHibernateWithSingleDatabase` and registers
+`AutoTransactionHandler`. The `Src/Samples/TardisBank` project is the end-to-end reference.
+
+## Conventions
+
+- **File-scoped namespaces**, with `using` directives placed **inside** the namespace block.
+- `Nullable` and `ImplicitUsings` are **enabled**; `LangVersion` is `preview`. Global usings include
+ `JetBrains.Annotations` and `System.Diagnostics.CodeAnalysis`.
+- **Public API is annotated**: `[PublicAPI]` marks exported surface, and `GenerateDocumentationFile`
+ is on — public members need XML doc comments (missing-doc warning 1591 is *not* suppressed for
+ libraries).
+- **Test frameworks**: xUnit is primary (`Shouldly` for assertions, `Moq` for mocking). One legacy
+ project, `SharpArch.Tests.NHibernate`, still uses NUnit — the framework is selected by project name
+ in `Src/Tests/Directory.Build.props`, so match the surrounding project when adding tests.
+- **Versioning/branching**: GitFlow (`develop` = pre-release, `master` = stable) with GitVersion;
+ package version is derived from git, not hardcoded.
+
+## Code exploration
+
+A CodeGraph index (`.codegraph/`) is present — prefer structural exploration of the codebase
+(searching symbols, call graphs, where a symbol is defined, blast radius) for structural questions
+over broad grep/read loops.
+
+## Auto-approve / safe commands
+
+The following read-only commands are pre-approved as safe to run without confirmation
+(migrated from the previous Claude `settings.local.json` permissions):
+
+- `dotnet --list-sdks`
+- `dotnet --version`
diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore
index 9de0f1690..d20c0fe4b 100644
--- a/.codegraph/.gitignore
+++ b/.codegraph/.gitignore
@@ -1,16 +1,5 @@
-# CodeGraph data files
-# These are local to each machine and should not be committed
-
-# Database
-*.db
-*.db-wal
-*.db-shm
-
-# Cache
-cache/
-
-# Logs
-*.log
-
-# Hook markers
-.dirty
+# CodeGraph data files — local to each machine, not for committing.
+# Ignore everything in .codegraph/ except this file itself, so transient
+# files (the database, daemon.pid, sockets, logs) never show up in git.
+*
+!.gitignore
diff --git a/.gitignore b/.gitignore
index 0f008e753..047847196 100644
--- a/.gitignore
+++ b/.gitignore
@@ -290,4 +290,5 @@ Artefacts/Documentation/build/
/DropsTestOutput.xml
/Src/Common/AssemblyVersion.cs
/Docker/mssql-data
+/Docker/postgres-data
/tmp/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..49466de3e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.preferCSharpExtension": true
+}
diff --git a/Artefacts/Documentation/ARCHITECTURE.md b/Artefacts/Documentation/ARCHITECTURE.md
new file mode 100644
index 000000000..eaa31a1a1
--- /dev/null
+++ b/Artefacts/Documentation/ARCHITECTURE.md
@@ -0,0 +1,925 @@
+# Sharp-Architecture — Component Architecture
+
+> Generated from source analysis of `Src/Lib` and `Src/Samples`.
+> Last updated: June 2026
+
+---
+
+## Table of Contents
+
+- [Sharp-Architecture — Component Architecture](#sharp-architecture--component-architecture)
+ - [Table of Contents](#table-of-contents)
+ - [Overview](#overview)
+ - [Package Structure](#package-structure)
+ - [Domain Model Layer](#domain-model-layer)
+ - [Class Hierarchy](#class-hierarchy)
+ - [Key Interfaces — Domain Model](#key-interfaces--domain-model)
+ - [Domain Signature](#domain-signature)
+ - [Equality \& Comparison Utilities](#equality--comparison-utilities)
+ - [Reflection Cache](#reflection-cache)
+ - [Persistence Abstractions](#persistence-abstractions)
+ - [Repository Contracts](#repository-contracts)
+ - [Transaction Contracts](#transaction-contracts)
+ - [Duplicate Detection Contract](#duplicate-detection-contract)
+ - [Validation Attributes](#validation-attributes)
+ - [`[HasUniqueDomainSignature]`](#hasuniquedomainsignature)
+ - [Specification Pattern](#specification-pattern)
+ - [Repository Extensions](#repository-extensions)
+ - [NHibernate Implementations](#nhibernate-implementations)
+ - [Interface Inheritance](#interface-inheritance)
+ - [Class Implementations](#class-implementations)
+ - [`TransactionManager`](#transactionmanager)
+ - [`NHibernateRepository`](#nhibernaterepositorytentity-tid)
+ - [`LinqRepository`](#linqrepositorytentity-tid)
+ - [`EntityDuplicateChecker`](#entityduplicatechecker)
+ - [Session \& Query Utilities](#session--query-utilities)
+ - [`Enums.LockMode`](#enumslockmode)
+ - [Session Factory Setup](#session-factory-setup)
+ - [FluentNHibernate Integration](#fluentnhibernate-integration)
+ - [Data Annotation Validation Hook](#data-annotation-validation-hook)
+ - [Infrastructure Utilities](#infrastructure-utilities)
+ - [`LogWrapper` (struct, in `SharpArch.Infrastructure.Logging`)](#logwrapper-struct-in-sharparchinfrastructurelogging)
+ - [`CodeBaseLocator`](#codebaselocator)
+ - [ASP.NET Core Web Layer](#aspnet-core-web-layer)
+ - [Declarative Transaction Management](#declarative-transaction-management)
+ - [Filter Pipeline](#filter-pipeline)
+ - [Dependency Injection Wiring](#dependency-injection-wiring)
+ - [End-to-End Request Flow](#end-to-end-request-flow)
+ - [Testing Infrastructure](#testing-infrastructure)
+ - [Base Test Classes (xUnit)](#base-test-classes-xunit)
+ - [`TransientDatabaseTests` (`SharpArch.Testing.Xunit.NHibernate`)](#transientdatabaseteststdatabaseinitializer-sharparchtestingxunitnhibernate)
+ - [`LiveDatabaseTests` (`SharpArch.Testing.Xunit.NHibernate`)](#livedatabaseteststdatabasesetup-sharparchtestingxunitnhibernate)
+ - [Base Test Classes (NUnit)](#base-test-classes-nunit)
+ - [General Test Helpers](#general-test-helpers)
+ - [`SetCultureAttribute` (`SharpArch.Testing.Xunit`)](#setcultureattribute-sharparchtestingxunit)
+ - [Sample Application — TardisBank](#sample-application--tardisbank)
+ - [Project Structure](#project-structure)
+ - [Layer → Framework Dependency Mapping](#layer--framework-dependency-mapping)
+ - [Component Dependency Graph](#component-dependency-graph)
+ - [NuGet Package Dependencies](#nuget-package-dependencies)
+ - [`SharpArch.Domain.csproj`](#sharparchdomaincsproj)
+ - [`SharpArch.NHibernate.csproj`](#sharparchnhibernatecsproj)
+ - [`SharpArch.NHibernate.Extensions.DependencyInjection.csproj`](#sharparchnhibernateextensionsdependencyinjectioncsproj)
+ - [`SharpArch.Web.AspNetCore.csproj`](#sharparchwebaspnetcorecsproj)
+
+---
+
+## Overview
+
+Sharp-Architecture is a .NET framework built around **Domain-Driven Design (DDD)** principles. It provides:
+
+- A clean **domain model base** (entities, value objects, specifications) with **zero external dependencies**.
+- **Repository and Transaction Manager abstractions** defined in the domain layer and implemented against NHibernate.
+- **ASP.NET Core MVC filter** support for declarative, attribute-driven transaction management.
+- **Dependency Injection extensions** to wire everything together with minimal boilerplate.
+- **Testing base classes** for both xUnit and NUnit, supporting in-memory and live-database scenarios.
+
+The architecture enforces a strict **dependency direction**: only outer layers depend on inner layers. The domain layer has no knowledge of any ORM, web framework, or infrastructure library.
+
+```
+┌────────────────────────────────────────────────────────────────┐
+│ Web / Application │
+│ SharpArch.Web.AspNetCore │
+├────────────────────────────────────────────────────────────────┤
+│ ORM / Infrastructure │
+│ SharpArch.NHibernate │
+│ SharpArch.NHibernate.DependencyInjection │
+├────────────────────────────────────────────────────────────────┤
+│ Domain (Core — no external deps) │
+│ SharpArch.Domain │
+├────────────────────────────────────────────────────────────────┤
+│ Cross-cutting Utilities │
+│ SharpArch.Infrastructure │
+└────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Package Structure
+
+| Package | Responsibility |
+|---|---|
+| `SharpArch.Domain` | Core DDD building blocks: entities, value objects, repository contracts, specifications, validation. **Zero external dependencies.** |
+| `SharpArch.Infrastructure` | Cross-cutting utilities: allocation-free logging wrapper (`LogWrapper`), code-base locator. Depends only on `Microsoft.Extensions.Logging`. |
+| `SharpArch.NHibernate` | NHibernate implementations of domain repository/transaction contracts. Includes FluentNHibernate automapping support and Data Annotation validation hooks. |
+| `SharpArch.NHibernate.DependencyInjection` | ASP.NET Core `IServiceCollection` extension `AddNHibernateWithSingleDatabase` that registers session factory, sessions, and transaction manager with correct lifetimes. |
+| `SharpArch.Web.AspNetCore` | `[Transaction]` attribute and `AutoTransactionHandler` MVC action filter for declarative transaction management. |
+| `SharpArch.Testing` | Framework-agnostic test base types and helper utilities shared between NUnit and xUnit packages. |
+| `SharpArch.Testing.NUnit` | NUnit base classes: `RepositoryTestsBase` (transient DB) and `DatabaseRepositoryTestsBase` (live DB). |
+| `SharpArch.Testing.Xunit` | xUnit attribute `SetCultureAttribute` for per-test/per-class culture control. |
+| `SharpArch.Testing.Xunit.NHibernate` | xUnit base classes: `TransientDatabaseTests` (in-memory/rollback) and `LiveDatabaseTests` (live DB, rollback per test). |
+
+---
+
+## Domain Model Layer
+
+### Class Hierarchy
+
+```
+System.Object
+ └── BaseObject (abstract)
+ │ ▸ Signature-based Equals / GetHashCode
+ │ ▸ HasSameObjectSignatureAs(BaseObject)
+ │ ▸ GetTypeUnproxied() — NHibernate proxy-safe type resolution
+ │ ▸ Uses static ITypePropertyDescriptorCache to cache PropertyInfo[]
+ │
+ ├── ValueObject (abstract)
+ │ ▸ Signature = ALL properties (by convention)
+ │ ▸ [DomainSignature] MUST NOT be used on value object properties
+ │ ▸ Throws InvalidOperationException if [DomainSignature] detected
+ │ ▸ == / != operators
+ │
+ └── ValidatableObject (abstract)
+ ▸ DataAnnotations validation via Validator.TryValidateObject
+ ▸ IsValid(ValidationContext) → bool
+ ▸ ValidationResults(ValidationContext) → ICollection
+ │
+ └── Entity (abstract, generic)
+ where TId : IEquatable
+ ▸ Id : TId (virtual, protected setter, [XmlIgnore])
+ ▸ IsTransient() — true when Id is null or equals default(TId)
+ ▸ GetId() — untyped object? accessor (avoids boxing where possible)
+ ▸ GetTypeUnproxied() — virtual, overridable for proxy detection
+ ▸ IEquatable>, == / != operators
+ ▸ Implements IEntity and IEntity
+ ▸ Hash code cached after first computation
+```
+
+### Key Interfaces — Domain Model
+
+| Interface | Purpose |
+|---|---|
+| `IEntity` | Non-generic entity contract: `GetId()`, `GetSignatureProperties()`, `IsTransient()`, `GetTypeUnproxied()` |
+| `IEntity` | Generic entity contract: typed `Id` property (covariant `out TId`) |
+| `IHasAssignedId` | Marker for entities with **manually assigned** IDs. Use when IDs are not auto-generated by the database (e.g., natural keys). `SetAssignedIdTo(TId)` must be implemented. |
+
+### Domain Signature
+
+`[DomainSignature]` is a property-level attribute that marks properties forming the **business identity** of an entity — the set of properties that determine whether two instances represent the same real-world concept (e.g., a `User`'s `Email`, a `Product`'s `Sku`).
+
+**How it works:**
+
+1. `BaseObject.GetSignatureProperties()` uses reflection to find all properties decorated with `[DomainSignature]` on the concrete type.
+2. The result is cached in `TypePropertyDescriptorCache` (static, thread-safe) to avoid repeated reflection.
+3. `HasSameObjectSignatureAs(BaseObject)` compares all signature property values.
+4. `Entity.Equals()` first tries ID comparison, then falls back to signature comparison for transient objects.
+5. `EntityDuplicateChecker.DoesDuplicateExistWithTypedIdOf(IEntity)` queries the database for entities with matching signature property values.
+
+```csharp
+// Example usage:
+public class User : Entity
+{
+ [DomainSignature]
+ public virtual string Email { get; set; }
+
+ public virtual string Name { get; set; } // Not part of domain signature
+}
+```
+
+**Rules:**
+- `ValueObject` subclasses use **all** properties as signature — `[DomainSignature]` must not be applied.
+- `Entity` subclasses should have at least one `[DomainSignature]` property.
+
+### Equality & Comparison Utilities
+
+| Type | Purpose |
+|---|---|
+| `BaseObjectEqualityComparer` | Implements `IEqualityComparer` for use with LINQ set operators (`Intersect`, `Union`, `Distinct`). Delegates to `BaseObject.Equals` and `GetHashCode`. Necessary because LINQ set operators use `IEqualityComparer.GetHashCode` rather than `object.GetHashCode`. |
+| `DomainSignatureAttribute` | Attribute applied to entity properties to include them in domain signature comparison. |
+
+```csharp
+// Example: using BaseObjectEqualityComparer with LINQ
+var distinct = users.Distinct(new BaseObjectEqualityComparer());
+var intersection = listA.Intersect(listB, new BaseObjectEqualityComparer());
+```
+
+### Reflection Cache
+
+```
+ITypePropertyDescriptorCache
+ └── TypePropertyDescriptorCache (thread-safe, static instance in BaseObject)
+ └── TypePropertyDescriptor (wraps Type + PropertyInfo[])
+```
+
+- `TypePropertyDescriptorCache` is a concurrent dictionary keyed by `Type`.
+- `BaseObject` holds a single static instance shared across all object instances.
+- `GetOrAdd(type, factory)` is used to prevent redundant allocations on the first access.
+
+---
+
+## Persistence Abstractions
+
+All persistence contracts live in `SharpArch.Domain.PersistenceSupport`. They carry **no ORM dependency** — they reference only `SharpArch.Domain.DomainModel` types.
+
+### Repository Contracts
+
+```
+IRepository
+ where TEntity : class, IEntity
+ where TId : IEquatable
+ │
+ ├── TransactionManager : ITransactionManager
+ ├── GetAsync(TId, CancellationToken) → Task
+ ├── GetAllAsync(CancellationToken) → Task>
+ ├── SaveAsync(entity, CancellationToken) → Task
+ ├── SaveOrUpdateAsync(entity, CancellationToken) → Task
+ ├── EvictAsync(entity, CancellationToken) → Task
+ ├── DeleteAsync(entity, CancellationToken) → Task
+ └── DeleteAsync(TId, CancellationToken) → Task
+
+ILinqRepository extends IRepository
+ ├── FindOneAsync(TId, CancellationToken) → Task
+ ├── FindOneAsync(ILinqSpecification, CancellationToken) → Task
+ ├── FindAll() → IQueryable
+ └── FindAll(ILinqSpecification) → IQueryable
+```
+
+### Transaction Contracts
+
+```
+ITransactionManager
+ ├── BeginTransaction(IsolationLevel) → IDisposable
+ ├── CommitTransactionAsync(CancellationToken) → Task
+ └── RollbackTransactionAsync(CancellationToken) → Task
+
+ISupportsTransactionStatus
+ └── IsActive : bool
+```
+
+`ISupportsTransactionStatus` is an **optional extension interface**. `AutoTransactionHandler` checks whether the resolved `ITransactionManager` also implements this interface, and if so, verifies `IsActive` before attempting commit/rollback. This prevents double-commit errors if the action manually managed the transaction.
+
+### Duplicate Detection Contract
+
+```
+IEntityDuplicateChecker
+ └── DoesDuplicateExistWithTypedIdOf(IEntity) : bool
+```
+
+Implemented by `EntityDuplicateChecker` in `SharpArch.NHibernate`. Used internally by the `[HasUniqueDomainSignature]` validator attribute.
+
+### Validation Attributes
+
+The `SharpArch.Domain.Validation` namespace provides Data Annotation validators that integrate with the entity lifecycle:
+
+#### `[HasUniqueDomainSignature]`
+
+Applied to an entity **class** to validate that no other persisted entity has the same domain signature.
+
+```
+HasUniqueDomainSignatureAttributeBase (abstract base)
+ └── HasUniqueDomainSignatureAttribute (concrete, resolves IEntityDuplicateChecker from ValidationContext)
+```
+
+**Flow:**
+1. Applied to an entity class: `[HasUniqueDomainSignature]`
+2. When `entity.IsValid(validationContext)` or `entity.ValidationResults(...)` is called:
+3. `HasUniqueDomainSignatureAttribute.IsValid(entity, context)` is invoked.
+4. It retrieves `IEntityDuplicateChecker` from `validationContext.GetService()`.
+5. Calls `IEntityDuplicateChecker.DoesDuplicateExistWithTypedIdOf(entity)`.
+6. Returns a validation error if a duplicate exists.
+
+```csharp
+[HasUniqueDomainSignature]
+public class User : Entity
+{
+ [DomainSignature]
+ public virtual string Email { get; set; }
+}
+```
+
+### Specification Pattern
+
+Specifications encapsulate and name query logic, enabling reuse and composition.
+
+```
+ISpecification
+ └── IsSatisfiedBy(T) : bool ← in-memory evaluation
+
+ILinqSpecification
+ └── SatisfyingElementsFrom(IQueryable) → IQueryable ← DB query
+
+QuerySpecification (abstract base — implements ILinqSpecification)
+ └── override SatisfyingElementsFrom(candidates)
+
+AdHoc (extends QuerySpecification)
+ └── Created via lambda: new AdHoc(q => q.Where(u => u.IsActive))
+```
+
+`QuerySpecificationExtensions` provides LINQ-style extension methods for composing specifications.
+
+**Usage pattern:**
+
+```csharp
+// Named specification
+public class ActiveUsersSpec : QuerySpecification
+{
+ public override IQueryable SatisfyingElementsFrom(IQueryable candidates)
+ => candidates.Where(u => u.IsActive);
+}
+
+// Inline ad-hoc specification
+var spec = new AdHoc(q => q.Where(u => u.Email.EndsWith("@example.com")));
+
+// Usage with repository
+IQueryable activeUsers = linqRepo.FindAll(new ActiveUsersSpec());
+User? user = await linqRepo.FindOneAsync(spec, cancellationToken);
+```
+
+### Repository Extensions
+
+`RepositoryExtensions` provides convenience extension methods on `IRepository`:
+
+- Helpers for common patterns such as checking existence or combining operations.
+- Reduces boilerplate in application code that uses `IRepository`.
+
+---
+
+## NHibernate Implementations
+
+### Interface Inheritance
+
+```
+Domain Layer NHibernate Layer
+───────────────── ────────────────────────────────────────
+ITransactionManager ◄───── INHibernateTransactionManager
+ └── + Session : ISession
+ └── + FlushChangesAsync()
+
+IRepository ◄───── INHibernateRepository
+ └── + FindAllAsync(propertyValuePairs)
+ └── + FindAllAsync(exampleInstance)
+ └── + FindOneAsync(exampleInstance)
+ └── + FindOneAsync(propertyValuePairs)
+ └── + GetAsync(id, lockMode)
+ └── + LoadAsync(id)
+ └── + LoadAsync(id, lockMode)
+ └── + MergeAsync(entity)
+ └── + UpdateAsync(entity)
+
+ILinqRepository ◄───── LinqRepository (class)
+```
+
+### Class Implementations
+
+#### `TransactionManager`
+
+```
+TransactionManager
+ ├── implements INHibernateTransactionManager
+ ├── implements ISupportsTransactionStatus
+ ├── constructor: TransactionManager(ISession session)
+ ├── Session : ISession → exposes raw NHibernate session
+ ├── BeginTransaction(IsolationLevel) → Session.BeginTransaction(...)
+ ├── CommitTransactionAsync(ct) → Session.GetCurrentTransaction().CommitAsync(ct)
+ ├── RollbackTransactionAsync(ct) → Session.GetCurrentTransaction().RollbackAsync(ct)
+ ├── FlushChangesAsync(ct) → Session.FlushAsync(ct)
+ └── IsActive → Session.GetCurrentTransaction()?.IsActive ?? false
+```
+
+#### `NHibernateRepository`
+
+```
+NHibernateRepository
+ ├── implements INHibernateRepository
+ ├── constructor: NHibernateRepository(INHibernateTransactionManager transactionManager)
+ ├── Session : ISession → TransactionManager.Session
+ ├── GetAsync(id, ct) → Session.GetAsync(id, ct)
+ ├── GetAllAsync(ct) → Session.CreateCriteria(...).ListAsync(ct)
+ ├── SaveAsync(entity, ct) → Session.SaveAsync(entity, ct)
+ ├── SaveOrUpdateAsync(entity, ct) → Session.SaveOrUpdateAsync(entity, ct)
+ ├── EvictAsync(entity, ct) → Session.EvictAsync(entity, ct)
+ ├── DeleteAsync(entity, ct) → Session.DeleteAsync(entity, ct)
+ ├── DeleteAsync(id, ct) → GetAsync then DeleteAsync
+ ├── FindAllAsync(propertyValuePairs, max, ct) → Criteria with Restrictions.Eq / IsNull
+ ├── FindAllAsync(example, exclusions, max, ct) → Criteria with Example.Create(...)
+ ├── FindOneAsync(example, ct, exclusions) → FindAllAsync (maxResults=2) + uniqueness check
+ ├── FindOneAsync(propertyValuePairs, ct) → FindAllAsync (maxResults=2) + uniqueness check
+ ├── GetAsync(id, lockMode, ct) → Session.GetAsync(id, lockMode, ct)
+ ├── LoadAsync(id, ct) → Session.LoadAsync(id, ct)
+ ├── LoadAsync(id, lockMode, ct) → Session.LoadAsync(id, lockMode, ct)
+ ├── MergeAsync(entity, ct) → Session.MergeAsync(entity, ct)
+ └── UpdateAsync(entity, ct) → Session.UpdateAsync(entity, ct)
+```
+
+#### `LinqRepository`
+
+```
+LinqRepository
+ ├── extends NHibernateRepository
+ ├── implements ILinqRepository
+ ├── FindOneAsync(id, ct) → Session.GetAsync(id, ct)
+ ├── FindOneAsync(ILinqSpecification, ct) → spec.SatisfyingElementsFrom(Session.Query()).SingleOrDefaultAsync()
+ ├── FindAll() → Session.Query()
+ └── FindAll(ILinqSpecification) → spec.SatisfyingElementsFrom(Session.Query())
+```
+
+#### `EntityDuplicateChecker`
+
+```
+EntityDuplicateChecker
+ ├── implements IEntityDuplicateChecker
+ ├── constructor: EntityDuplicateChecker(ISession session)
+ ├── Temporarily sets FlushMode to Manual during check (avoids flushing pending changes)
+ ├── Builds ICriteria excluding the current entity's ID
+ ├── Appends criteria for each [DomainSignature] property:
+ │ ├── IEntity<> properties → compared by .id
+ │ ├── ValueObject properties → recursively expanded to scalar comparisons
+ │ ├── DateTime properties → Eq or IsNull (skips uninitialized dates)
+ │ ├── string properties → InsensitiveLike (case-insensitive exact match)
+ │ ├── enum properties → Eq (as int)
+ │ └── value types → Eq or IsNull
+ └── Returns true if at least one duplicate found (SetMaxResults(1))
+```
+
+### Session & Query Utilities
+
+| Type | Purpose |
+|---|---|
+| `NHibernateQuery` | Base class / helpers for constructing HQL or Criteria queries; provides common query patterns. |
+| `SessionExtensions` | Extension methods on `ISession`, e.g., helpers for common session operations. |
+| `LockModeConvertExtensions` | Converts `SharpArch.Domain.Enums.LockMode` to the NHibernate `LockMode` enum, keeping the domain layer free of NHibernate references. |
+| `ISessionFactoryKeyProvider` | Contract for providing a named key for multi-database session factory scenarios. |
+
+#### `Enums.LockMode`
+
+Defined in `SharpArch.Domain` (no NHibernate dependency), maps to NHibernate lock modes via `LockModeConvertExtensions`:
+
+| SharpArch LockMode | NHibernate LockMode |
+|---|---|
+| `None` | `LockMode.None` |
+| `Read` | `LockMode.Read` |
+| `Upgrade` | `LockMode.Upgrade` |
+| `UpgradeNoWait` | `LockMode.UpgradeNoWait` |
+| `Force` | `LockMode.Force` |
+| `Write` | `LockMode.Write` |
+
+### Session Factory Setup
+
+`NHibernateSessionFactoryBuilder` is a **fluent builder** (instantiated as transient) that produces an `ISessionFactory` (registered as singleton in DI).
+
+```
+NHibernateSessionFactoryBuilder
+ │
+ ├── UseConfigFile(string path)
+ │ Loads hibernate.cfg.xml from specified path.
+ │ Default: looks for "hibernate.cfg.xml" in working directory.
+ │
+ ├── UseProperties(IEnumerable> props)
+ │ Sets NHibernate configuration properties programmatically.
+ │
+ ├── UsePersistenceConfigurer(IPersistenceConfigurer configurer)
+ │ FluentNHibernate database configuration
+ │ e.g., SQLiteConfiguration.Standard.InMemory()
+ │ or MsSqlConfiguration.MsSql2012.ConnectionString(...)
+ │
+ ├── AddMappingAssemblies(IEnumerable assemblies)
+ │ Scans assemblies for:
+ │ - HBM XML mappings (AddFromAssembly)
+ │ - FluentNHibernate class maps (AddFromAssembly + Conventions)
+ │
+ ├── UseAutoPersistenceModel(AutoPersistenceModel model)
+ │ Adds FluentNHibernate auto-persistence model for convention-based mapping.
+ │
+ ├── UseCache(Action cacheConfig)
+ │ Configures NHibernate second-level cache.
+ │
+ ├── UseDataAnnotationValidators(bool enable)
+ │ When true, registers DataAnnotationsEventListener as pre-insert
+ │ and pre-update event listener on the Configuration.
+ │
+ ├── ExposeConfiguration(Action config)
+ │ Callback invoked after all settings applied; allows direct NHibernate
+ │ Configuration manipulation (e.g., schema export, schema update).
+ │
+ ├── BuildConfiguration() → NHibernate.Cfg.Configuration
+ └── BuildSessionFactory() → ISessionFactory
+```
+
+### FluentNHibernate Integration
+
+Located in `SharpArch.NHibernate/FluentNHibernate/`:
+
+| Type | Role |
+|---|---|
+| `IAutoPersistenceModelGenerator` | Contract: implement to provide a customised `AutoPersistenceModel` for auto-mapping. Pass result to `NHibernateSessionFactoryBuilder.UseAutoPersistenceModel(...)`. |
+| `IMapGenerator` | Contract: implement to generate `ClassMap` instances for explicit FluentNHibernate mappings. |
+| `AutomappingConfiguration` | Base `DefaultAutomappingConfiguration` subclass pre-configured to map only `Entity` subtypes, ignoring value objects and non-entity types. |
+| `FluentNHibernateExtensions` | Extension methods simplifying common FluentNHibernate configuration tasks. |
+| `GeneratorHelper` | Helpers for configuring ID generation strategies (identity, assigned, hilo, etc.) in FluentNHibernate convention-based mappings. |
+| `Conventions/` | Pre-built FluentNHibernate conventions (e.g., table naming, foreign key naming, cascade defaults). |
+
+### Data Annotation Validation Hook
+
+`DataAnnotationsEventListener` implements NHibernate's `IPreInsertEventListener` and `IPreUpdateEventListener`:
+
+- Invoked automatically by NHibernate **before** every `INSERT` and `UPDATE`.
+- Uses `Validator.TryValidateObject` to run all Data Annotation validators on the entity.
+- Throws a `ValidationException` if validation fails, preventing the database write.
+- Registered via `NHibernateSessionFactoryBuilder.UseDataAnnotationValidators(true)`.
+
+---
+
+## Infrastructure Utilities
+
+### `LogWrapper` (struct, in `SharpArch.Infrastructure.Logging`)
+
+An allocation-free logging helper that skips the `ILogger.Log` call (and avoids lambda captures / string interpolation allocations) when a log level is disabled:
+
+```csharp
+public readonly struct LogWrapper
+{
+ // Returns EnabledLogger? — non-null only when the level is enabled
+ public EnabledLogger? Trace { get; }
+ public EnabledLogger? Debug { get; }
+ public EnabledLogger? Information { get; }
+ public EnabledLogger? Warning { get; }
+ public EnabledLogger? Error { get; }
+ public EnabledLogger? Critical { get; }
+}
+
+// Usage:
+var logger = new LogWrapper(loggerFactory.CreateLogger("..."));
+logger.Debug?.Log("Session created: {Id}", session.GetSessionImplementation().SessionId);
+// ^^^^ short-circuits if Debug is disabled — no string formatting occurs
+```
+
+`EnabledLogger` wraps the actual `ILogger` and exposes a `Log(string message, params object[] args)` method.
+
+### `CodeBaseLocator`
+
+Provides utilities for locating the application code base directory at runtime — useful for finding configuration files (e.g., `hibernate.cfg.xml`) relative to the assembly location rather than the working directory.
+
+---
+
+## ASP.NET Core Web Layer
+
+### Declarative Transaction Management
+
+The `[Transaction]` attribute is a **marker** — it carries configuration but does not apply any behaviour itself. Behaviour is provided by `AutoTransactionHandler`.
+
+```csharp
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public sealed class TransactionAttribute : Attribute, IFilterMetadata
+{
+ IsolationLevel IsolationLevel { get; } // default: ReadCommitted
+ bool RollbackOnModelValidationError { get; } // default: true
+}
+```
+
+Apply at **controller class** level (all actions use it) or override per **action method**:
+
+```csharp
+[Transaction] // all actions: ReadCommitted, rollback on validation error
+public class OrdersController : ControllerBase
+{
+ [Transaction(IsolationLevel.Serializable)] // this action: Serializable
+ public Task Create([FromBody] CreateOrderRequest request) { ... }
+
+ [Transaction(rollbackOnModelValidationError: false)] // this action: don't rollback on bad model
+ public Task Update(...) { ... }
+}
+```
+
+### Filter Pipeline
+
+```
+ApplyTransactionFilterBase (abstract base)
+ └── Thread-safe action-descriptor-ID → TransactionAttribute? cache
+ Uses copy-on-write dictionary pattern (lock + new dict copy) for thread safety
+
+AutoTransactionHandler
+ ├── extends ApplyTransactionFilterBase
+ ├── implements IAsyncActionFilter
+ │
+ ├── BEFORE action execution:
+ │ 1. GetTransactionAttribute(context) — looks up cache, reads effective policy
+ │ 2. If [Transaction] present:
+ │ → context.HttpContext.RequestServices.GetRequiredService()
+ │ → transactionManager.BeginTransaction(attribute.IsolationLevel)
+ │
+ └── AFTER action execution:
+ 1. If ITransactionManager also implements ISupportsTransactionStatus:
+ → if !IsActive: log "Transaction already closed" and return (no double-commit)
+ 2. if (executedContext.Exception != null
+ || attribute.RollbackOnModelValidationError && !context.ModelState.IsValid):
+ → RollbackTransactionAsync() (no cancellation token on error path)
+ 3. else:
+ → CommitTransactionAsync(context.HttpContext.RequestAborted)
+```
+
+**Registration:**
+
+```csharp
+// In Program.cs / Startup.cs
+builder.Services.AddControllers(options =>
+{
+ options.Filters.Add();
+});
+```
+
+---
+
+## Dependency Injection Wiring
+
+`NHibernateRegistrationExtensions.AddNHibernateWithSingleDatabase` (in `SharpArch.NHibernate.DependencyInjection`) registers the complete NHibernate infrastructure stack:
+
+| Service | Lifetime | Notes |
+|---|---|---|
+| `ISessionFactory` | **Singleton** | Built once at startup by `NHibernateSessionFactoryBuilder`. Hash code logged on creation. |
+| `ISession` | **Scoped** (per HTTP request) | Opened from `ISessionFactory.OpenSession()` or via optional `sessionConfigurator` callback. Session ID logged on creation. |
+| `IStatelessSession` | **Scoped** | Opened from `ISessionFactory.OpenStatelessSession()`. Stateless — no identity map or change tracking. Caller is responsible for disposal. |
+| `TransactionManager` | **Scoped** | Wraps the scoped `ISession`. One per request. |
+| `INHibernateTransactionManager` | Transient (→ `TransactionManager`) | |
+| `ITransactionManager` | Transient (→ `TransactionManager`) | |
+
+**Signature:**
+
+```csharp
+public static IServiceCollection AddNHibernateWithSingleDatabase(
+ this IServiceCollection services,
+ Func configureSessionFactory,
+ Func? sessionConfigurator = null,
+ Func? statelessSessionConfigurator = null
+)
+```
+
+**Full setup example:**
+
+```csharp
+// Program.cs
+builder.Services.AddNHibernateWithSingleDatabase(sp =>
+ new NHibernateSessionFactoryBuilder()
+ .UsePersistenceConfigurer(
+ MsSqlConfiguration.MsSql2012.ConnectionString(
+ builder.Configuration.GetConnectionString("Default")))
+ .AddMappingAssemblies(new[] { typeof(ProductMap).Assembly })
+ .UseDataAnnotationValidators(true)
+ .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
+);
+
+builder.Services.AddControllers(options =>
+{
+ options.Filters.Add();
+});
+
+// Repository registrations (not auto-registered)
+builder.Services.AddTransient, NHibernateRepository>();
+builder.Services.AddTransient, LinqRepository>();
+builder.Services.AddTransient();
+```
+
+---
+
+## End-to-End Request Flow
+
+```
+HTTP Request
+ │
+ ▼
+┌──────────────────────────────────────────────────────┐
+│ AutoTransactionHandler — BEFORE │
+│ │
+│ 1. Find [Transaction] attribute on action/class │
+│ 2. Resolve ITransactionManager from DI │
+│ → TransactionManager (scoped to this request) │
+│ 3. TransactionManager.BeginTransaction(isolation) │
+│ → ISession.BeginTransaction(isolation) │
+└──────────────────────────────────────────────────────┘
+ │
+ ▼
+┌──────────────────────────────────────────────────────┐
+│ Controller Action │
+│ │
+│ Constructor-injects ILinqRepository │
+│ → LinqRepository │
+│ → NHibernateRepository (base) │
+│ → INHibernateTransactionManager │
+│ → TransactionManager (scoped) │
+│ → ISession (scoped) │
+│ → ISessionFactory (singleton) │
+│ │
+│ repo.GetAsync(id) → Session.GetAsync(id) │
+│ repo.SaveAsync(e) → Session.SaveAsync(e) │
+│ repo.FindAll(spec) → Session.Query()... │
+└──────────────────────────────────────────────────────┘
+ │
+ ▼
+┌──────────────────────────────────────────────────────┐
+│ AutoTransactionHandler — AFTER │
+│ │
+│ Check ISupportsTransactionStatus.IsActive │
+│ ├── false → already closed, skip (log debug) │
+│ └── true → │
+│ ├── exception || invalid model │
+│ │ → RollbackTransactionAsync() │
+│ └── success │
+│ → CommitTransactionAsync(ct) │
+└──────────────────────────────────────────────────────┘
+ │
+ ▼
+HTTP Response
+```
+
+---
+
+## Testing Infrastructure
+
+### Base Test Classes (xUnit)
+
+#### `TransientDatabaseTests` (`SharpArch.Testing.Xunit.NHibernate`)
+
+- Designed for **in-memory or transient databases** (e.g., SQLite in-memory).
+- Creates the database schema before the test class runs (`TDatabaseInitializer`).
+- Wraps **each test** in a transaction that is **rolled back** after the test completes.
+- Ensures complete test isolation — no test leaves data behind.
+- Inherits from xUnit `IAsyncLifetime` for setup/teardown.
+
+#### `LiveDatabaseTests` (`SharpArch.Testing.Xunit.NHibernate`)
+
+- Designed for tests against a **real, pre-existing development database**.
+- Does **not** run schema export — assumes schema is already up-to-date.
+- Wraps each test in a transaction that is **rolled back** after the test.
+- Useful for integration tests against a real SQL Server / PostgreSQL instance.
+- Provided primarily for **backward compatibility**.
+
+**Usage pattern:**
+
+```csharp
+public class OrderRepositoryTests : TransientDatabaseTests
+{
+ readonly ISession _session;
+
+ public OrderRepositoryTests(SqliteTestDatabaseInitializer dbInit)
+ : base(dbInit) { }
+
+ [Fact]
+ public async Task Can_save_and_retrieve_order()
+ {
+ var repo = new NHibernateRepository(TransactionManager);
+ var order = new Order { /* ... */ };
+ await repo.SaveAsync(order);
+ Session.Flush();
+
+ var loaded = await repo.GetAsync(order.Id);
+ Assert.NotNull(loaded);
+ }
+}
+```
+
+### Base Test Classes (NUnit)
+
+Located in `SharpArch.Testing.NUnit/NHibernate/`:
+
+| Class | Purpose |
+|---|---|
+| `RepositoryTestsBase` | NUnit equivalent of `TransientDatabaseTests`. Runs schema export before tests; each test rolls back. For in-memory / SQLite databases. |
+| `DatabaseRepositoryTestsBase` | NUnit equivalent of `LiveDatabaseTests`. Tests against a live database; each test rolls back. |
+
+Both inherit from NUnit's `[TestFixture]` pattern and use `[SetUp]` / `[TearDown]` for transaction management.
+
+### General Test Helpers
+
+Located in `SharpArch.Testing/Helpers/`:
+
+- Framework-agnostic base types and utility helpers shared between NUnit and xUnit packages.
+- Provides the core NHibernate session/transaction management logic that both `SharpArch.Testing.NUnit` and `SharpArch.Testing.Xunit.NHibernate` build upon.
+
+#### `SetCultureAttribute` (`SharpArch.Testing.Xunit`)
+
+An xUnit `BeforeAfterTestAttribute` that sets and restores `Thread.CurrentCulture` and `Thread.CurrentUICulture` for a specific test or test class:
+
+```csharp
+[SetCulture("fr-FR")]
+public class PriceFormattingTests
+{
+ [Fact]
+ public void Price_formats_with_french_locale() { /* ... */ }
+}
+```
+
+---
+
+## Sample Application — TardisBank
+
+TardisBank is a pocket-money / savings application that demonstrates all Sharp-Architecture patterns in a complete end-to-end scenario.
+
+### Project Structure
+
+```
+Samples/TardisBank/Src/
+ ├── Suteki.TardisBank.Domain/ ← Domain layer
+ │ ├── User.cs, Account.cs, ... ← Entities extending Entity
+ │ ├── Money.cs, ... ← Value objects extending ValueObject
+ │ └── [DomainSignature] properties ← Business identity attributes
+ │
+ ├── Suteki.TardisBank.Infrastructure/ ← ORM / persistence layer
+ │ ├── NHibernate mappings ← FluentNHibernate ClassMap or HBM
+ │ └── Repository implementations ← Concrete repos using NHibernateRepository
+ │
+ ├── Suteki.TardisBank.Tasks/ ← Application services layer
+ │ └── Use-case orchestration ← Commands / handlers that coordinate
+ │ classes calling repositories domain objects and repositories
+ │
+ ├── Suteki.TardisBank.Api/ ← Shared API contracts
+ │ └── DTOs / request-response types (not MVC controllers)
+ │
+ ├── Suteki.TardisBank.WebApi/ ← ASP.NET Core Web API
+ │ ├── Controllers/ ← [Transaction] attributed controllers
+ │ └── Program.cs / Startup ← AddNHibernateWithSingleDatabase wiring
+ │
+ └── Suteki.TardisBank.Tests/ ← Integration & unit tests
+ └── Uses TransientDatabaseTests ← In-memory DB, rollback per test
+```
+
+### Layer → Framework Dependency Mapping
+
+| Sample Layer | Sharp-Architecture Package Used |
+|---|---|
+| Domain entities | `SharpArch.Domain` (`Entity`, `ValueObject`, `[DomainSignature]`) |
+| Infrastructure / ORM | `SharpArch.NHibernate` (`NHibernateRepository`, `LinqRepository`, `EntityDuplicateChecker`) |
+| Web API | `SharpArch.Web.AspNetCore` (`[Transaction]`, `AutoTransactionHandler`) |
+| DI wiring | `SharpArch.NHibernate.DependencyInjection` (`AddNHibernateWithSingleDatabase`) |
+| Tests | `SharpArch.Testing.Xunit.NHibernate` (`TransientDatabaseTests`) |
+
+---
+
+## Component Dependency Graph
+
+```
+SharpArch.Web.AspNetCore
+ └─→ SharpArch.Domain
+ (ITransactionManager, ISupportsTransactionStatus)
+
+SharpArch.NHibernate.DependencyInjection
+ ├─→ SharpArch.NHibernate
+ │ (TransactionManager, NHibernateSessionFactoryBuilder)
+ ├─→ SharpArch.Domain
+ │ (ITransactionManager)
+ └─→ SharpArch.Infrastructure
+ (LogWrapper)
+
+SharpArch.NHibernate
+ ├─→ SharpArch.Domain
+ │ (IEntity, IRepository, ILinqRepository,
+ │ ITransactionManager, IEntityDuplicateChecker,
+ │ ILinqSpecification, DomainSignatureAttribute, ValueObject, Enums)
+ └─→ NHibernate + FluentNHibernate (external NuGet)
+
+SharpArch.Infrastructure
+ └─→ Microsoft.Extensions.Logging (external NuGet)
+
+SharpArch.Domain
+ └─→ (none — pure .NET BCL only)
+
+SharpArch.Testing.Xunit.NHibernate
+ ├─→ SharpArch.Testing
+ ├─→ SharpArch.NHibernate
+ └─→ xUnit (external NuGet)
+
+SharpArch.Testing.NUnit
+ ├─→ SharpArch.Testing
+ ├─→ SharpArch.NHibernate
+ └─→ NUnit (external NuGet)
+```
+
+---
+
+## NuGet Package Dependencies
+
+### `SharpArch.Domain.csproj`
+
+```xml
+
+
+```
+
+### `SharpArch.NHibernate.csproj`
+
+```xml
+
+
+
+
+```
+
+### `SharpArch.NHibernate.Extensions.DependencyInjection.csproj`
+
+```xml
+
+
+
+
+```
+
+### `SharpArch.Web.AspNetCore.csproj`
+
+```xml
+
+
+
+
+```
+
+---
+
+*This document was generated from source-code analysis of the Sharp-Architecture repository (`Src/Lib` and `Src/Samples`). Keep this document in sync when adding new packages or significant API changes.*
diff --git a/Docker/create_database_postgres.sql b/Docker/create_database_postgres.sql
new file mode 100644
index 000000000..62a9b9206
--- /dev/null
+++ b/Docker/create_database_postgres.sql
@@ -0,0 +1,12 @@
+-- PostgreSQL equivalent of create_database.sql
+-- Compatible with the official postgres Docker Linux image
+--
+-- Usage options:
+-- 1. Place in /docker-entrypoint-initdb.d/ for automatic execution on first container start
+-- 2. Run manually: psql -U postgres -f create_database_postgres.sql
+
+-- Create TardisBank database if it does not already exist
+SELECT 'CREATE DATABASE "TardisBank"'
+WHERE NOT EXISTS (
+ SELECT FROM pg_database WHERE datname = 'TardisBank'
+)\gexec
diff --git a/Docker/start-mssql.ps1 b/Docker/start-mssql.ps1
index c226cf8a5..53b5d5aa1 100644
--- a/Docker/start-mssql.ps1
+++ b/Docker/start-mssql.ps1
@@ -1 +1,8 @@
-docker.exe run --name sharparch-sql --user root -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password12!' -p 2433:1433 -d -v./mssql-data:/var/opt/mssql/data mcr.microsoft.com/mssql/server:2019-latest
\ No newline at end of file
+docker.exe run `
+ --name sharparch-sql `
+ --user root `
+ -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password12!' `
+ -p 2433:1433 `
+ -d `
+ -v./mssql-data:/var/opt/mssql/data `
+ mcr.microsoft.com/mssql/server:2019-latest
diff --git a/Docker/start-postgres.ps1 b/Docker/start-postgres.ps1
new file mode 100755
index 000000000..d68f57000
--- /dev/null
+++ b/Docker/start-postgres.ps1
@@ -0,0 +1,7 @@
+docker run --name sharparch-postgres `
+ -e POSTGRES_USER=postgres `
+ -e POSTGRES_PASSWORD=Password12! `
+ -p 5432:5432 `
+ -v ${PWD}/postgres-data:/var/lib/postgresql `
+ -v ${PWD}/create_database_postgres.sql:/docker-entrypoint-initdb.d/create_database_postgres.sql `
+ -d postgres:latest
diff --git a/GitVersion.yml b/GitVersion.yml
index a02cbd49e..ede654002 100644
--- a/GitVersion.yml
+++ b/GitVersion.yml
@@ -2,10 +2,10 @@
branches:
release:
mode: ContinuousDeployment
- tag: rc
+ label: rc
develop:
mode: ContinuousDeployment
- tag: alpha
+ label: alpha
ignore:
sha: []
diff --git a/SharpArchitecture.code-workspace b/SharpArchitecture.code-workspace
index 362d7c25b..e68d1d58c 100644
--- a/SharpArchitecture.code-workspace
+++ b/SharpArchitecture.code-workspace
@@ -3,5 +3,8 @@
{
"path": "."
}
- ]
-}
\ No newline at end of file
+ ],
+ "settings": {
+ "dotnet.preferCSharpExtension": true
+ }
+}
diff --git a/Src/Common/NHibernate/TestDatabaseSetup.cs b/Src/Common/NHibernate/TestDatabaseSetup.cs
index f05357cf4..ccf7f304b 100644
--- a/Src/Common/NHibernate/TestDatabaseSetup.cs
+++ b/Src/Common/NHibernate/TestDatabaseSetup.cs
@@ -69,7 +69,8 @@ public TestDatabaseSetup(
if (!typeof(IAutoPersistenceModelGenerator).IsAssignableFrom(persistenceModelGenerator))
throw new ArgumentException($"Type {persistenceModelGenerator.FullName} must implement {nameof(IAutoPersistenceModelGenerator)}.");
- if (mappingAssemblies == null) throw new ArgumentNullException(nameof(mappingAssemblies));
+ if (mappingAssemblies == null)
+ throw new ArgumentNullException(nameof(mappingAssemblies));
_mappingAssemblies = mappingAssemblies.Distinct().ToArray();
}
@@ -116,7 +117,8 @@ public virtual void Dispose()
///
public Configuration GetConfiguration()
{
- if (_configuration != null) return _configuration;
+ if (_configuration != null)
+ return _configuration;
var autoPersistenceModel = GenerateAutoPersistenceModel();
@@ -167,7 +169,8 @@ protected virtual void Customize(NHibernateSessionFactoryBuilder builder)
/// Session builder.
protected virtual ISessionBuilder ConfigureSession(ISessionBuilder sessionBuilder)
{
- if (sessionBuilder == null) throw new ArgumentNullException(nameof(sessionBuilder));
+ if (sessionBuilder == null)
+ throw new ArgumentNullException(nameof(sessionBuilder));
SessionConfigurator?.Invoke(sessionBuilder);
return sessionBuilder;
}
@@ -178,7 +181,8 @@ protected virtual ISessionBuilder ConfigureSession(ISessionBuilder sessionBuilde
/// Session builder.
protected virtual IStatelessSessionBuilder ConfigureStatelessSession(IStatelessSessionBuilder statelessSessionBuilder)
{
- if (statelessSessionBuilder == null) throw new ArgumentNullException(nameof(statelessSessionBuilder));
+ if (statelessSessionBuilder == null)
+ throw new ArgumentNullException(nameof(statelessSessionBuilder));
StatelessSessionConfigurator?.Invoke(statelessSessionBuilder);
return statelessSessionBuilder;
}
@@ -189,7 +193,8 @@ protected virtual IStatelessSessionBuilder ConfigureStatelessSession(IStatelessS
///
public ISessionFactory GetSessionFactory()
{
- if (_sessionFactory != null) return _sessionFactory;
+ if (_sessionFactory != null)
+ return _sessionFactory;
_sessionFactory = GetConfiguration().BuildSessionFactory();
return _sessionFactory!;
}
@@ -217,7 +222,8 @@ public ISession InitializeSession()
/// NHibernate Session
public IStatelessSession CreateStatelessSessionForConnection(DbConnection connection)
{
- if (connection == null) throw new ArgumentNullException(nameof(connection));
+ if (connection == null)
+ throw new ArgumentNullException(nameof(connection));
var sessionBuilder = GetSessionFactory().WithStatelessOptions();
sessionBuilder.Connection(connection);
var statelessSession = ConfigureStatelessSession(sessionBuilder).OpenStatelessSession();
diff --git a/Src/Directory.Build.props b/Src/Directory.Build.props
index 07448a0e3..7bb5d264e 100644
--- a/Src/Directory.Build.props
+++ b/Src/Directory.Build.props
@@ -1,7 +1,7 @@
- net8.0;net9.0
+ net8.0;net9.0;net10.0
netstandard2.1;$(AppTargetFrameworks)
@@ -38,10 +38,6 @@
-
- full
-
-
True
@@ -68,6 +64,23 @@
true
false
$(NoWarn);0618;1591
+
+ false
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+ $(NoWarn);xunit1051
@@ -91,7 +104,7 @@
-
+
diff --git a/Src/Lib/Directory.Build.props b/Src/Lib/Directory.Build.props
index 4b9686446..19d2e1a8e 100644
--- a/Src/Lib/Directory.Build.props
+++ b/Src/Lib/Directory.Build.props
@@ -4,7 +4,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/BaseObject.cs b/Src/Lib/SharpArch.Domain/DomainModel/BaseObject.cs
index 4084d2811..e991e6844 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/BaseObject.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/BaseObject.cs
@@ -153,7 +153,7 @@ public virtual bool HasSameObjectSignatureAs(BaseObject compareTo)
}
if (valueOfThisObject == null ^ valueToCompareTo == null ||
- !(valueOfThisObject!.Equals(valueToCompareTo)))
+ !valueOfThisObject!.Equals(valueToCompareTo))
{
return false;
}
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/BaseObjectEqualityComparer.cs b/Src/Lib/SharpArch.Domain/DomainModel/BaseObjectEqualityComparer.cs
index 44468262d..60aed7cc1 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/BaseObjectEqualityComparer.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/BaseObjectEqualityComparer.cs
@@ -49,7 +49,8 @@ public bool Equals(T? firstObject, T? secondObject)
///
public int GetHashCode(T obj)
{
- if (obj is null) throw new ArgumentNullException(nameof(obj));
+ if (obj is null)
+ throw new ArgumentNullException(nameof(obj));
return obj.GetHashCode();
}
}
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/Entity.cs b/Src/Lib/SharpArch.Domain/DomainModel/Entity.cs
index 8d1221d52..e9d97fbb3 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/Entity.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/Entity.cs
@@ -1,6 +1,5 @@
namespace SharpArch.Domain.DomainModel;
-using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Xml.Serialization;
@@ -30,7 +29,8 @@ public abstract class Entity : ValidatableObject, IEntity, IEntity,
public virtual object? GetId()
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
- if (Id is null) return null;
+ if (Id is null)
+ return null;
return Id.Equals(default!)
// ReSharper disable once RedundantCast
? (object?)null
@@ -180,9 +180,7 @@ protected override PropertyInfo[] GetTypeSpecificSignatureProperties()
/// default ID value; otherwise; false.
///
bool HasSameNonDefaultIdAs(Entity compareTo)
- {
- return !IsTransient() && !compareTo.IsTransient() && Id!.Equals(compareTo.Id!);
- }
+ => !IsTransient() && !compareTo.IsTransient() && Id!.Equals(compareTo.Id!);
///
/// Check whether entities are equal.
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/IEntity.cs b/Src/Lib/SharpArch.Domain/DomainModel/IEntity.cs
index 84a7aa25f..2c72ff800 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/IEntity.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/IEntity.cs
@@ -67,6 +67,7 @@ public interface IEntity
///
/// Gets the ID which uniquely identifies the entity instance within its type's bounds.
///
- [AllowNull] [MaybeNull]
+ [AllowNull]
+ [MaybeNull]
TId Id { get; }
}
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/ValidatableObject.cs b/Src/Lib/SharpArch.Domain/DomainModel/ValidatableObject.cs
index 85e2abc84..e86d9133e 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/ValidatableObject.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/ValidatableObject.cs
@@ -1,7 +1,6 @@
namespace SharpArch.Domain.DomainModel;
using System.ComponentModel.DataAnnotations;
-using System.Diagnostics.CodeAnalysis;
///
diff --git a/Src/Lib/SharpArch.Domain/DomainModel/ValueObject.cs b/Src/Lib/SharpArch.Domain/DomainModel/ValueObject.cs
index 3cc75a6a6..45e6f8664 100644
--- a/Src/Lib/SharpArch.Domain/DomainModel/ValueObject.cs
+++ b/Src/Lib/SharpArch.Domain/DomainModel/ValueObject.cs
@@ -1,6 +1,5 @@
namespace SharpArch.Domain.DomainModel;
-using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@@ -27,7 +26,8 @@ public abstract class ValueObject : BaseObject
/// The result of the operator.
public static bool operator ==(ValueObject? valueObject1, ValueObject? valueObject2)
{
- if (ReferenceEquals(valueObject1, null)) return ReferenceEquals(valueObject2, null);
+ if (ReferenceEquals(valueObject1, null))
+ return ReferenceEquals(valueObject2, null);
return valueObject1.Equals(valueObject2);
}
diff --git a/Src/Lib/SharpArch.Domain/PersistenceSupport/RepositoryExtensions.cs b/Src/Lib/SharpArch.Domain/PersistenceSupport/RepositoryExtensions.cs
index 61d8d57bd..1e6becf4b 100644
--- a/Src/Lib/SharpArch.Domain/PersistenceSupport/RepositoryExtensions.cs
+++ b/Src/Lib/SharpArch.Domain/PersistenceSupport/RepositoryExtensions.cs
@@ -21,8 +21,10 @@ public static async Task SaveAndEvictAsync(
where TEntity : class, IEntity
where TId : IEquatable
{
- if (repository == null) throw new ArgumentNullException(nameof(repository));
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (repository == null)
+ throw new ArgumentNullException(nameof(repository));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
var saved = await repository.SaveAsync(entity, CancellationToken.None).ConfigureAwait(false);
await repository.EvictAsync(saved, cancellationToken).ConfigureAwait(false);
}
diff --git a/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptor.cs b/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptor.cs
index b42891761..ac1465a10 100644
--- a/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptor.cs
+++ b/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptor.cs
@@ -35,7 +35,8 @@ public class TypePropertyDescriptor : IEquatable
///
public TypePropertyDescriptor(Type ownerType, PropertyInfo[]? properties)
{
- if (ownerType == null) throw new ArgumentNullException(nameof(ownerType));
+ if (ownerType == null)
+ throw new ArgumentNullException(nameof(ownerType));
_ownerType = ownerType;
if (properties != null && properties.Length > 0)
@@ -53,8 +54,10 @@ public TypePropertyDescriptor(Type ownerType, PropertyInfo[]? properties)
///
public bool Equals(TypePropertyDescriptor? other)
{
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
+ if (ReferenceEquals(null, other))
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
return OwnerType == other.OwnerType;
}
@@ -76,8 +79,10 @@ public bool HasProperties()
///
public override bool Equals(object? obj)
{
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
+ if (ReferenceEquals(null, obj))
+ return false;
+ if (ReferenceEquals(this, obj))
+ return true;
return obj is TypePropertyDescriptor other && Equals(other);
}
diff --git a/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptorCache.cs b/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptorCache.cs
index 91f92ef13..348a2cfda 100644
--- a/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptorCache.cs
+++ b/Src/Lib/SharpArch.Domain/Reflection/TypePropertyDescriptorCache.cs
@@ -38,8 +38,10 @@ public class TypePropertyDescriptorCache : ITypePropertyDescriptorCache
///
public TypePropertyDescriptor GetOrAdd(Type type, Func factory)
{
- if (type == null) throw new ArgumentNullException(nameof(type));
- if (factory == null) throw new ArgumentNullException(nameof(factory));
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+ if (factory == null)
+ throw new ArgumentNullException(nameof(factory));
return _cache.GetOrAdd(type, factory);
}
diff --git a/Src/Lib/SharpArch.Domain/Specifications/QuerySpecification.cs b/Src/Lib/SharpArch.Domain/Specifications/QuerySpecification.cs
index 79687a030..6cb029683 100644
--- a/Src/Lib/SharpArch.Domain/Specifications/QuerySpecification.cs
+++ b/Src/Lib/SharpArch.Domain/Specifications/QuerySpecification.cs
@@ -24,7 +24,8 @@ public abstract class QuerySpecification : ILinqSpecification
/// is .
public virtual IQueryable SatisfyingElementsFrom(IQueryable candidates)
{
- if (candidates == null) throw new ArgumentNullException(nameof(candidates));
+ if (candidates == null)
+ throw new ArgumentNullException(nameof(candidates));
if (MatchingCriteria != null)
{
return candidates.Where(MatchingCriteria);
diff --git a/Src/Lib/SharpArch.Domain/Specifications/QuerySpecificationExtensions.cs b/Src/Lib/SharpArch.Domain/Specifications/QuerySpecificationExtensions.cs
index 5b10f5f60..f4dc78dec 100644
--- a/Src/Lib/SharpArch.Domain/Specifications/QuerySpecificationExtensions.cs
+++ b/Src/Lib/SharpArch.Domain/Specifications/QuerySpecificationExtensions.cs
@@ -21,8 +21,10 @@ public static QuerySpecification And(
this QuerySpecification specification1,
QuerySpecification specification2)
{
- if (specification1 == null) throw new ArgumentNullException(nameof(specification1));
- if (specification2 == null) throw new ArgumentNullException(nameof(specification2));
+ if (specification1 == null)
+ throw new ArgumentNullException(nameof(specification1));
+ if (specification2 == null)
+ throw new ArgumentNullException(nameof(specification2));
InvocationExpression invokedExpr =
Expression.Invoke(specification2.MatchingCriteria!, specification1.MatchingCriteria!.Parameters);
Expression> dynamicClause = Expression.Lambda>(
diff --git a/Src/Lib/SharpArch.Domain/Validation/HasUniqueDomainSignatureAttributeBase.cs b/Src/Lib/SharpArch.Domain/Validation/HasUniqueDomainSignatureAttributeBase.cs
index ed80bec22..4247907fb 100644
--- a/Src/Lib/SharpArch.Domain/Validation/HasUniqueDomainSignatureAttributeBase.cs
+++ b/Src/Lib/SharpArch.Domain/Validation/HasUniqueDomainSignatureAttributeBase.cs
@@ -48,7 +48,7 @@ protected HasUniqueDomainSignatureAttributeBase()
if (entityToValidate == null)
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"This validator must be used at the class level of an " + nameof(IEntity) + ". The type you provided was '{0}'.",
- (value?.GetType() as object) ?? "null"));
+ value?.GetType() as object ?? "null"));
var duplicateChecker =
(IEntityDuplicateChecker?)validationContext.GetService(typeof(IEntityDuplicateChecker));
diff --git a/Src/Lib/SharpArch.Infrastructure/CodeBaseLocator.cs b/Src/Lib/SharpArch.Infrastructure/CodeBaseLocator.cs
index 5bfe82d98..ce06f6bc4 100644
--- a/Src/Lib/SharpArch.Infrastructure/CodeBaseLocator.cs
+++ b/Src/Lib/SharpArch.Infrastructure/CodeBaseLocator.cs
@@ -17,11 +17,12 @@ public class CodeBaseLocator
/// is
public static string GetAssemblyCodeBasePath(Assembly assembly)
{
- if (assembly == null) throw new ArgumentNullException(nameof(assembly));
+ if (assembly == null)
+ throw new ArgumentNullException(nameof(assembly));
#if NET6_0_OR_GREATER
- return Path.GetDirectoryName(assembly.Location)
- ?? Directory.GetCurrentDirectory();
+ return Path.GetDirectoryName(assembly.Location)
+ ?? Directory.GetCurrentDirectory();
#else
var uri = new UriBuilder(assembly.CodeBase);
diff --git a/Src/Lib/SharpArch.Infrastructure/SharpArch.Infrastructure.csproj b/Src/Lib/SharpArch.Infrastructure/SharpArch.Infrastructure.csproj
index e87f26938..f13d56257 100644
--- a/Src/Lib/SharpArch.Infrastructure/SharpArch.Infrastructure.csproj
+++ b/Src/Lib/SharpArch.Infrastructure/SharpArch.Infrastructure.csproj
@@ -27,4 +27,8 @@
+
+
+
+
diff --git a/Src/Lib/SharpArch.NHibernate.DependencyInjection/NHibernateRegistrationExtensions.cs b/Src/Lib/SharpArch.NHibernate.DependencyInjection/NHibernateRegistrationExtensions.cs
index 1199219f1..2c3c4097d 100644
--- a/Src/Lib/SharpArch.NHibernate.DependencyInjection/NHibernateRegistrationExtensions.cs
+++ b/Src/Lib/SharpArch.NHibernate.DependencyInjection/NHibernateRegistrationExtensions.cs
@@ -48,8 +48,10 @@ public static IServiceCollection AddNHibernateWithSingleDatabase(
Func? statelessSessionConfigurator = null
)
{
- if (services == null) throw new ArgumentNullException(nameof(services));
- if (configureSessionFactory == null) throw new ArgumentNullException(nameof(configureSessionFactory));
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+ if (configureSessionFactory == null)
+ throw new ArgumentNullException(nameof(configureSessionFactory));
services.AddSingleton(sp =>
{
diff --git a/Src/Lib/SharpArch.NHibernate.DependencyInjection/SharpArch.NHibernate.Extensions.DependencyInjection.csproj b/Src/Lib/SharpArch.NHibernate.DependencyInjection/SharpArch.NHibernate.Extensions.DependencyInjection.csproj
index deb8d78f8..194bb6de3 100644
--- a/Src/Lib/SharpArch.NHibernate.DependencyInjection/SharpArch.NHibernate.Extensions.DependencyInjection.csproj
+++ b/Src/Lib/SharpArch.NHibernate.DependencyInjection/SharpArch.NHibernate.Extensions.DependencyInjection.csproj
@@ -27,6 +27,10 @@
+
+
+
+
diff --git a/Src/Lib/SharpArch.NHibernate/EntityDuplicateChecker.cs b/Src/Lib/SharpArch.NHibernate/EntityDuplicateChecker.cs
index b149f99a7..3390604c1 100644
--- a/Src/Lib/SharpArch.NHibernate/EntityDuplicateChecker.cs
+++ b/Src/Lib/SharpArch.NHibernate/EntityDuplicateChecker.cs
@@ -16,7 +16,7 @@
[PublicAPI]
public class EntityDuplicateChecker : IEntityDuplicateChecker
{
- static readonly DateTime _uninitializedDatetime = default;
+ static readonly DateTime _uninitializedDatetime;
readonly ISession _session;
///
@@ -39,7 +39,8 @@ public EntityDuplicateChecker(ISession session)
/// entity is null.
public bool DoesDuplicateExistWithTypedIdOf(IEntity entity)
{
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
var sessionForEntity = GetSessionFor(entity);
@@ -102,7 +103,7 @@ static string GetPropertyName(string parentPropertyName, PropertyInfo signatureP
return signatureProperty.Name;
}
- if (parentPropertyName.EndsWith(".") == false)
+ if (!parentPropertyName.EndsWith("."))
{
parentPropertyName += ".";
}
@@ -113,7 +114,7 @@ static string GetPropertyName(string parentPropertyName, PropertyInfo signatureP
static void AppendDateTimePropertyCriteriaTo(ICriteria criteria, string propertyName, object? propertyValue)
{
criteria.Add(
- (propertyValue is null) || (DateTime)propertyValue > _uninitializedDatetime
+ propertyValue is null || (DateTime)propertyValue > _uninitializedDatetime
? Restrictions.Eq(propertyName, propertyValue)
: Restrictions.IsNull(propertyName));
}
@@ -126,8 +127,7 @@ static void AppendSignaturePropertyCriteriaTo(ICriteria criteria, IEntity entity
var propertyValue = signatureProperty.GetValue(entity, null);
var propertyName = signatureProperty.Name;
- if (propertyType.GetInterfaces().Any(
- x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>)))
+ if (propertyType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>)))
{
AppendEntityIdCriteriaTo(criteria, propertyName, propertyValue);
}
diff --git a/Src/Lib/SharpArch.NHibernate/FluentNHibernate/FluentNHibernateExtensions.cs b/Src/Lib/SharpArch.NHibernate/FluentNHibernate/FluentNHibernateExtensions.cs
index f060c34a2..57cabac13 100644
--- a/Src/Lib/SharpArch.NHibernate/FluentNHibernate/FluentNHibernateExtensions.cs
+++ b/Src/Lib/SharpArch.NHibernate/FluentNHibernate/FluentNHibernateExtensions.cs
@@ -26,7 +26,8 @@ public static class FluentNHibernateExtensions
/// is null.
public static FluentMappingsContainer AddFromNamespaceOf(this FluentMappingsContainer mappingsContainer)
{
- if (mappingsContainer == null) throw new ArgumentNullException(nameof(mappingsContainer));
+ if (mappingsContainer == null)
+ throw new ArgumentNullException(nameof(mappingsContainer));
string ns = typeof(T).Namespace!;
var types = typeof(T).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.Namespace == ns)
diff --git a/Src/Lib/SharpArch.NHibernate/LockModeConvertExtensions.cs b/Src/Lib/SharpArch.NHibernate/LockModeConvertExtensions.cs
index fda31e9c4..70d561157 100644
--- a/Src/Lib/SharpArch.NHibernate/LockModeConvertExtensions.cs
+++ b/Src/Lib/SharpArch.NHibernate/LockModeConvertExtensions.cs
@@ -45,7 +45,8 @@ public static LockMode ToNHibernate(this Enums.LockMode lockMode)
///
public static Enums.LockMode ToNHibernate(this LockMode lockMode)
{
- if (_nHibernateToSharpArchMap.TryGetValue(lockMode, out var convertedMode)) return convertedMode;
+ if (_nHibernateToSharpArchMap.TryGetValue(lockMode, out var convertedMode))
+ return convertedMode;
throw new ArgumentOutOfRangeException(nameof(lockMode), lockMode,
$"The provided lock mode , '{lockMode}', could not be translated into an SharpArch.LockMode. "
+ "This is probably because NHibernate was updated and now has different lock modes "
diff --git a/Src/Lib/SharpArch.NHibernate/NHibernateQuery.cs b/Src/Lib/SharpArch.NHibernate/NHibernateQuery.cs
index f142419b4..7c03ee463 100644
--- a/Src/Lib/SharpArch.NHibernate/NHibernateQuery.cs
+++ b/Src/Lib/SharpArch.NHibernate/NHibernateQuery.cs
@@ -22,7 +22,8 @@ public abstract class NHibernateQuery
/// is null.
protected NHibernateQuery(ISession session)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
Session = session;
}
diff --git a/Src/Lib/SharpArch.NHibernate/NHibernateRepositoryBase.cs b/Src/Lib/SharpArch.NHibernate/NHibernateRepositoryBase.cs
index b74feca38..cf52474ba 100644
--- a/Src/Lib/SharpArch.NHibernate/NHibernateRepositoryBase.cs
+++ b/Src/Lib/SharpArch.NHibernate/NHibernateRepositoryBase.cs
@@ -80,7 +80,8 @@ public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = de
public async Task DeleteAsync(TId id, CancellationToken cancellationToken = default)
{
var entity = await GetAsync(id, cancellationToken).ConfigureAwait(false);
- if (entity != null) await DeleteAsync(entity, cancellationToken).ConfigureAwait(false);
+ if (entity != null)
+ await DeleteAsync(entity, cancellationToken).ConfigureAwait(false);
}
ITransactionManager IRepository.TransactionManager => TransactionManager;
@@ -91,7 +92,8 @@ public virtual Task> FindAllAsync(
int? maxResults = null,
CancellationToken cancellationToken = default)
{
- if (propertyValuePairs == null) throw new ArgumentNullException(nameof(propertyValuePairs));
+ if (propertyValuePairs == null)
+ throw new ArgumentNullException(nameof(propertyValuePairs));
if (propertyValuePairs.Count == 0)
throw new ArgumentException("No properties specified. Please specify at least one property/value pair.",
nameof(propertyValuePairs));
@@ -104,7 +106,8 @@ public virtual Task> FindAllAsync(
: Restrictions.IsNull(key));
}
- if (maxResults.HasValue) criteria.SetMaxResults(maxResults.Value);
+ if (maxResults.HasValue)
+ criteria.SetMaxResults(maxResults.Value);
return criteria.ListAsync(cancellationToken);
}
@@ -115,11 +118,13 @@ public Task> FindAllAsync(
ICriteria criteria = Session.CreateCriteria(typeof(TEntity));
Example example = Example.Create(exampleInstance);
- foreach (string propertyToExclude in propertiesToExclude) example.ExcludeProperty(propertyToExclude);
+ foreach (string propertyToExclude in propertiesToExclude)
+ example.ExcludeProperty(propertyToExclude);
criteria.Add(example);
- if (maxResults.HasValue) criteria.SetMaxResults(maxResults.Value);
+ if (maxResults.HasValue)
+ criteria.SetMaxResults(maxResults.Value);
return criteria.ListAsync(cancellationToken);
}
@@ -128,7 +133,8 @@ public Task> FindAllAsync(
public virtual async Task FindOneAsync(TEntity exampleInstance, CancellationToken ct, params string[] propertiesToExclude)
{
IList foundList = await FindAllAsync(exampleInstance, propertiesToExclude, 2, ct).ConfigureAwait(false);
- if (foundList.Count > 1) throw new NonUniqueResultException(foundList.Count);
+ if (foundList.Count > 1)
+ throw new NonUniqueResultException(foundList.Count);
return foundList.Count == 1 ? foundList[0] : default;
}
@@ -139,7 +145,8 @@ public Task> FindAllAsync(
CancellationToken cancellationToken = default)
{
var foundList = await FindAllAsync(propertyValuePairs, 2, cancellationToken).ConfigureAwait(false);
- if (foundList.Count > 1) throw new NonUniqueResultException(foundList.Count);
+ if (foundList.Count > 1)
+ throw new NonUniqueResultException(foundList.Count);
return foundList.Count == 1 ? foundList[0] : default;
}
diff --git a/Src/Lib/SharpArch.NHibernate/NHibernateSessionFactoryBuilder.cs b/Src/Lib/SharpArch.NHibernate/NHibernateSessionFactoryBuilder.cs
index 872163562..9621db12d 100644
--- a/Src/Lib/SharpArch.NHibernate/NHibernateSessionFactoryBuilder.cs
+++ b/Src/Lib/SharpArch.NHibernate/NHibernateSessionFactoryBuilder.cs
@@ -94,7 +94,8 @@ bool ShouldExposeConfiguration()
/// Mapping assemblies are not specified.
public NHibernateSessionFactoryBuilder AddMappingAssemblies(IEnumerable mappingAssemblies)
{
- if (mappingAssemblies == null) throw new ArgumentNullException(nameof(mappingAssemblies), "Mapping assemblies are not specified.");
+ if (mappingAssemblies == null)
+ throw new ArgumentNullException(nameof(mappingAssemblies), "Mapping assemblies are not specified.");
_mappingAssemblies.AddRange(mappingAssemblies);
return this;
@@ -122,9 +123,11 @@ public NHibernateSessionFactoryBuilder UseAutoPersistenceModel(
/// is null.
public NHibernateSessionFactoryBuilder UseProperties(IEnumerable> properties)
{
- if (properties == null) throw new ArgumentNullException(nameof(properties));
+ if (properties == null)
+ throw new ArgumentNullException(nameof(properties));
- if (_properties == null) _properties = new Dictionary(64);
+ if (_properties == null)
+ _properties = new Dictionary(64);
foreach (var pair in properties)
{
_properties[pair.Key] = pair.Value;
diff --git a/Src/Lib/SharpArch.NHibernate/SessionExtensions.cs b/Src/Lib/SharpArch.NHibernate/SessionExtensions.cs
index 64db28650..44ab303c6 100644
--- a/Src/Lib/SharpArch.NHibernate/SessionExtensions.cs
+++ b/Src/Lib/SharpArch.NHibernate/SessionExtensions.cs
@@ -21,8 +21,10 @@ public static class SessionExtensions
///
public static void FlushAndEvict(this ISession session, object entity)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
// Commits any changes up to this point to the database
session.Flush();
@@ -42,8 +44,10 @@ public static void FlushAndEvict(this ISession session, object entity)
///
public static async Task FlushAndEvictAsync(this ISession session, object entity, CancellationToken cancellationToken)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
// Commits any changes up to this point to the database
await session.FlushAsync(cancellationToken).ConfigureAwait(false);
@@ -63,8 +67,10 @@ public static async Task FlushAndEvictAsync(this ISession session, object entity
///
public static async Task FlushAndEvictAsync(this ISession session, CancellationToken cancellationToken, params object[] entities)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entities == null) throw new ArgumentNullException(nameof(entities));
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entities == null)
+ throw new ArgumentNullException(nameof(entities));
// Commits any changes up to this point to the database
await session.FlushAsync(cancellationToken).ConfigureAwait(false);
@@ -73,7 +79,8 @@ public static async Task FlushAndEvictAsync(this ISession session, CancellationT
{
var entity = entities[i];
- if (entity == null) throw new ArgumentNullException(nameof(entities), $"Item at index {i} it null.");
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entities), $"Item at index {i} it null.");
await session.EvictAsync(entity, cancellationToken).ConfigureAwait(false);
}
}
@@ -89,12 +96,15 @@ public static async Task FlushAndEvictAsync(this ISession session, CancellationT
/// is null.
public static Task IncrementVersionAsync(this ISession session, object? entity, CancellationToken cancellationToken)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entity == null) return Task.CompletedTask;
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entity == null)
+ return Task.CompletedTask;
// don't process deleted entity.
var entry = session.GetSessionImplementation().PersistenceContext.GetEntry(entity);
- if (entry == null || entry.Status is Status.Deleted or Status.Gone) return Task.CompletedTask;
+ if (entry == null || entry.Status is Status.Deleted or Status.Gone)
+ return Task.CompletedTask;
return session.LockAsync(entity, LockMode.Force, cancellationToken);
}
@@ -108,12 +118,15 @@ public static Task IncrementVersionAsync(this ISession session, object? entity,
/// is null.
public static void IncrementVersion(this ISession session, object? entity)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entity == null) return;
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entity == null)
+ return;
// ignore deleted entity
var entry = session.GetSessionImplementation().PersistenceContext.GetEntry(entity);
- if (entry == null || entry.Status is Status.Deleted or Status.Gone) return;
+ if (entry == null || entry.Status is Status.Deleted or Status.Gone)
+ return;
session.Lock(entity, LockMode.Force);
}
@@ -130,15 +143,20 @@ public static void IncrementVersion(this ISession session, object? entity)
///
public static bool IsModified(this ISession session, object entity)
{
- if (session == null) throw new ArgumentNullException(nameof(session));
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (session == null)
+ throw new ArgumentNullException(nameof(session));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
var sessionImplementation = session.GetSessionImplementation();
var entry = sessionImplementation.PersistenceContext.GetEntry(entity);
- if (entry == null) return false;
+ if (entry == null)
+ return false;
var persister = entry.Persister;
- if (entry.Status == Status.Deleted) return true;
- if (!entry.RequiresDirtyCheck(entity)) return false;
+ if (entry.Status == Status.Deleted)
+ return true;
+ if (!entry.RequiresDirtyCheck(entity))
+ return false;
var currentValue = persister.GetPropertyValues(entity);
var dirtyPropertyIndexes = persister.FindDirty(currentValue, entry.LoadedState, entity, sessionImplementation);
diff --git a/Src/Lib/SharpArch.NHibernate/SharpArch.NHibernate.csproj b/Src/Lib/SharpArch.NHibernate/SharpArch.NHibernate.csproj
index 3afc7f4f0..70710347e 100644
--- a/Src/Lib/SharpArch.NHibernate/SharpArch.NHibernate.csproj
+++ b/Src/Lib/SharpArch.NHibernate/SharpArch.NHibernate.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/Src/Lib/SharpArch.Testing.NUnit/NHibernate/DatabaseRepositoryTestsBase.cs b/Src/Lib/SharpArch.Testing.NUnit/NHibernate/DatabaseRepositoryTestsBase.cs
index 949d16065..8148c049b 100644
--- a/Src/Lib/SharpArch.Testing.NUnit/NHibernate/DatabaseRepositoryTestsBase.cs
+++ b/Src/Lib/SharpArch.Testing.NUnit/NHibernate/DatabaseRepositoryTestsBase.cs
@@ -5,6 +5,7 @@
using global::NUnit.Framework;
using Testing.NHibernate;
+
///
///
/// Initiates a transaction before each test is run and rolls back the transaction after
@@ -67,7 +68,8 @@ protected DatabaseRepositoryTestsBase(TestDatabaseSetup initializer)
[OneTimeSetUp]
public void OneTimeSetUp()
{
- if (Initializer == null) throw new InvalidOperationException($"{nameof(Initializer)} is not set.");
+ if (Initializer == null)
+ throw new InvalidOperationException($"{nameof(Initializer)} is not set.");
UpdateConfiguration(Initializer.GetConfiguration());
}
@@ -85,7 +87,8 @@ protected virtual void UpdateConfiguration(Configuration configuration)
[SetUp]
public virtual void SetUp()
{
- if (Initializer == null) throw new InvalidOperationException($"{nameof(Initializer)} is not set.");
+ if (Initializer == null)
+ throw new InvalidOperationException($"{nameof(Initializer)} is not set.");
Session = Initializer.GetSessionFactory().OpenSession();
Session.BeginTransaction();
}
@@ -99,7 +102,8 @@ public virtual void TearDown()
if (Session != null)
{
var currentTransaction = Session.GetCurrentTransaction();
- if (currentTransaction is { IsActive: true }) currentTransaction.Rollback();
+ if (currentTransaction is { IsActive: true })
+ currentTransaction.Rollback();
Session.Dispose();
Session = null;
}
diff --git a/Src/Lib/SharpArch.Testing.NUnit/NHibernate/RepositoryTestsBase.cs b/Src/Lib/SharpArch.Testing.NUnit/NHibernate/RepositoryTestsBase.cs
index de3a7bce0..219918329 100644
--- a/Src/Lib/SharpArch.Testing.NUnit/NHibernate/RepositoryTestsBase.cs
+++ b/Src/Lib/SharpArch.Testing.NUnit/NHibernate/RepositoryTestsBase.cs
@@ -71,7 +71,8 @@ public virtual void TearDown()
/// is
protected void FlushSessionAndEvict(object instance)
{
- if (instance == null) throw new ArgumentNullException(nameof(instance));
+ if (instance == null)
+ throw new ArgumentNullException(nameof(instance));
Session.FlushAndEvict(instance);
}
@@ -82,7 +83,8 @@ protected void FlushSessionAndEvict(object instance)
/// is
protected void SaveAndEvict(object instance)
{
- if (instance == null) throw new ArgumentNullException(nameof(instance));
+ if (instance == null)
+ throw new ArgumentNullException(nameof(instance));
Session.Save(instance);
FlushSessionAndEvict(instance);
}
diff --git a/Src/Lib/SharpArch.Testing.NUnit/SharpArch.Testing.NUnit.csproj b/Src/Lib/SharpArch.Testing.NUnit/SharpArch.Testing.NUnit.csproj
index 4bc5f10b0..0a0a00a2f 100644
--- a/Src/Lib/SharpArch.Testing.NUnit/SharpArch.Testing.NUnit.csproj
+++ b/Src/Lib/SharpArch.Testing.NUnit/SharpArch.Testing.NUnit.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/LiveDatabaseTests.cs b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/LiveDatabaseTests.cs
index ec01c115d..368a4dbe1 100644
--- a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/LiveDatabaseTests.cs
+++ b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/LiveDatabaseTests.cs
@@ -53,7 +53,8 @@ public virtual void Dispose()
if (Session != null)
{
var currentTransaction = Session.GetCurrentTransaction();
- if (currentTransaction != null && currentTransaction.IsActive) currentTransaction.Rollback();
+ if (currentTransaction != null && currentTransaction.IsActive)
+ currentTransaction.Rollback();
Session.Dispose();
Session = null;
}
diff --git a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/SharpArch.Testing.Xunit.NHibernate.csproj b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/SharpArch.Testing.Xunit.NHibernate.csproj
index cb6ee70f4..7a791ca40 100644
--- a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/SharpArch.Testing.Xunit.NHibernate.csproj
+++ b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/SharpArch.Testing.Xunit.NHibernate.csproj
@@ -16,10 +16,14 @@
-
+
+
+
+
+
diff --git a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/TransientDatabaseTests.cs b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/TransientDatabaseTests.cs
index 766ef4b4f..d17e3b6ae 100644
--- a/Src/Lib/SharpArch.Testing.Xunit.NHibernate/TransientDatabaseTests.cs
+++ b/Src/Lib/SharpArch.Testing.Xunit.NHibernate/TransientDatabaseTests.cs
@@ -51,12 +51,21 @@ protected TransientDatabaseTests(TestDatabaseSetup dbSetup)
}
///
- public Task InitializeAsync()
- => LoadTestData(CancellationToken.None);
+#if NET8_0_OR_GREATER
+ public async ValueTask InitializeAsync()
+#else
+ public async Task InitializeAsync()
+#endif
+ => await LoadTestData(CancellationToken.None).ConfigureAwait(false);
///
+#if NET8_0_OR_GREATER
+ public ValueTask DisposeAsync()
+ => new ValueTask(Task.CompletedTask);
+#else
public Task DisposeAsync()
- => Task.CompletedTask;
+ => Task.CompletedTask;
+#endif
///
public virtual void Dispose()
@@ -81,7 +90,8 @@ protected Task FlushSessionAndEvict(object instance, CancellationToken cancellat
/// is
protected async Task SaveAndEvict(object instance, CancellationToken cancellationToken = default)
{
- if (instance == null) throw new ArgumentNullException(nameof(instance));
+ if (instance == null)
+ throw new ArgumentNullException(nameof(instance));
await Session.SaveAsync(instance, cancellationToken).ConfigureAwait(false);
await FlushSessionAndEvict(instance, cancellationToken).ConfigureAwait(false);
}
diff --git a/Src/Lib/SharpArch.Testing.Xunit/SetCultureAttribute.cs b/Src/Lib/SharpArch.Testing.Xunit/SetCultureAttribute.cs
index 0e4ed3251..abd864d60 100644
--- a/Src/Lib/SharpArch.Testing.Xunit/SetCultureAttribute.cs
+++ b/Src/Lib/SharpArch.Testing.Xunit/SetCultureAttribute.cs
@@ -2,7 +2,12 @@
using System.Globalization;
using System.Reflection;
+#if NET8_0_OR_GREATER
+using global::Xunit.v3;
+
+#else
using global::Xunit.Sdk;
+#endif
///
@@ -36,8 +41,10 @@ public SetCultureAttribute(string culture)
/// The name of the UI culture.
public SetCultureAttribute(string culture, string uiCulture)
{
- if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
- if (string.IsNullOrWhiteSpace(uiCulture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(uiCulture));
+ if (string.IsNullOrWhiteSpace(culture))
+ throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
+ if (string.IsNullOrWhiteSpace(uiCulture))
+ throw new ArgumentException("Value cannot be null or whitespace.", nameof(uiCulture));
_culture = new Lazy(() => new CultureInfo(culture, false));
_uiCulture = new Lazy(() => new CultureInfo(uiCulture, false));
}
@@ -47,7 +54,11 @@ public SetCultureAttribute(string culture, string uiCulture)
/// and
/// and replaces them with the new cultures defined in the constructor.
///
+#if NET8_0_OR_GREATER
+ public override void Before(MethodInfo methodUnderTest, IXunitTest test)
+#else
public override void Before(MethodInfo methodUnderTest)
+#endif
{
_originalCulture = CultureInfo.CurrentCulture;
_originalUiCulture = CultureInfo.CurrentUICulture;
@@ -66,10 +77,16 @@ static void ClearCachedData()
/// Restores the original and
///
///
+#if NET8_0_OR_GREATER
+ public override void After(MethodInfo methodUnderTest, IXunitTest test)
+#else
public override void After(MethodInfo methodUnderTest)
+#endif
{
- if (_originalCulture != null) CultureInfo.CurrentCulture = _originalCulture;
- if (_originalUiCulture != null) CultureInfo.CurrentUICulture = _originalUiCulture;
+ if (_originalCulture != null)
+ CultureInfo.CurrentCulture = _originalCulture;
+ if (_originalUiCulture != null)
+ CultureInfo.CurrentUICulture = _originalUiCulture;
ClearCachedData();
}
}
diff --git a/Src/Lib/SharpArch.Testing.Xunit/SharpArch.Testing.Xunit.csproj b/Src/Lib/SharpArch.Testing.Xunit/SharpArch.Testing.Xunit.csproj
index 419666c95..868a5554d 100644
--- a/Src/Lib/SharpArch.Testing.Xunit/SharpArch.Testing.Xunit.csproj
+++ b/Src/Lib/SharpArch.Testing.Xunit/SharpArch.Testing.Xunit.csproj
@@ -14,10 +14,14 @@
-
+
+
+
+
+
false
diff --git a/Src/Lib/SharpArch.Testing/Helpers/EntityIdSetter.cs b/Src/Lib/SharpArch.Testing/Helpers/EntityIdSetter.cs
index a5e0f3c17..555c54b47 100644
--- a/Src/Lib/SharpArch.Testing/Helpers/EntityIdSetter.cs
+++ b/Src/Lib/SharpArch.Testing/Helpers/EntityIdSetter.cs
@@ -22,11 +22,13 @@ public static class EntityIdSetter
public static void SetIdOf(IEntity entity, TId id)
where TId : IEquatable
{
- if (entity == null) throw new ArgumentNullException(nameof(entity));
+ if (entity == null)
+ throw new ArgumentNullException(nameof(entity));
// Set the data property reflectively
PropertyInfo? idProperty = entity.GetType().GetProperty("Id", BindingFlags.Public | BindingFlags.Instance);
- if (idProperty == null) throw new InvalidOperationException("Property with name 'Id' could not be found.");
+ if (idProperty == null)
+ throw new InvalidOperationException("Property with name 'Id' could not be found.");
idProperty.SetValue(entity, id, null);
}
diff --git a/Src/Lib/SharpArch.Web.AspNetCore/Transaction/AutoTransactionHandler.cs b/Src/Lib/SharpArch.Web.AspNetCore/Transaction/AutoTransactionHandler.cs
index fa2240d9a..e02d99b88 100644
--- a/Src/Lib/SharpArch.Web.AspNetCore/Transaction/AutoTransactionHandler.cs
+++ b/Src/Lib/SharpArch.Web.AspNetCore/Transaction/AutoTransactionHandler.cs
@@ -39,7 +39,7 @@ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionE
}
if (executedContext.Exception != null ||
- transactionAttribute!.RollbackOnModelValidationError && context.ModelState.IsValid == false)
+ transactionAttribute!.RollbackOnModelValidationError && !context.ModelState.IsValid)
{
// don't use cancellation token to ensure transaction is rolled back on error
await transactionManager.RollbackTransactionAsync().ConfigureAwait(false);
diff --git a/Src/Lib/SharpArch.Web.AspNetCore/Transaction/TransactionAttribute.cs b/Src/Lib/SharpArch.Web.AspNetCore/Transaction/TransactionAttribute.cs
index 1169bec88..6ceed07a7 100644
--- a/Src/Lib/SharpArch.Web.AspNetCore/Transaction/TransactionAttribute.cs
+++ b/Src/Lib/SharpArch.Web.AspNetCore/Transaction/TransactionAttribute.cs
@@ -62,8 +62,10 @@ public TransactionAttribute(IsolationLevel isolationLevel = IsolationLevel.ReadC
///
public bool Equals(TransactionAttribute? other)
{
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
+ if (ReferenceEquals(null, other))
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
return RollbackOnModelValidationError == other.RollbackOnModelValidationError && IsolationLevel == other.IsolationLevel;
}
diff --git a/Src/Samples/Directory.Build.props b/Src/Samples/Directory.Build.props
index 6d4e9699c..13afd9181 100644
--- a/Src/Samples/Directory.Build.props
+++ b/Src/Samples/Directory.Build.props
@@ -4,6 +4,7 @@
$(AppTargetFrameworks)
+ $(NoWarn);NU1903
diff --git a/Src/Samples/TardisBank/Src/Directory.Build.props b/Src/Samples/TardisBank/Src/Directory.Build.props
index 7cbfe1490..9e6165c05 100644
--- a/Src/Samples/TardisBank/Src/Directory.Build.props
+++ b/Src/Samples/TardisBank/Src/Directory.Build.props
@@ -3,7 +3,7 @@
- net9.0
+ net9.0;net10.0
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/AnnouncementModel.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/AnnouncementModel.cs
index a2cee8635..c81f43094 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/AnnouncementModel.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/AnnouncementModel.cs
@@ -1,7 +1,7 @@
namespace Suteki.TardisBank.Api.Announcements;
using System.ComponentModel.DataAnnotations;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
///
@@ -9,15 +9,15 @@
///
public class AnnouncementSummary
{
- [JsonProperty("id")]
+ [JsonPropertyName("id")]
public int Id { get; set; }
- [JsonProperty("date")]
+ [JsonPropertyName("date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Required]
- [JsonProperty("title")]
+ [JsonPropertyName("title")]
public string Title { get; set; } = null!;
}
@@ -27,7 +27,7 @@ public class AnnouncementSummary
///
public class AnnouncementModel : AnnouncementSummary
{
- [JsonProperty("content")]
+ [JsonPropertyName("content")]
[Required]
public string Content { get; set; } = null!;
}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/NewAnnouncement.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/NewAnnouncement.cs
index 207a9feba..9dd955242 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/NewAnnouncement.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Announcements/NewAnnouncement.cs
@@ -1,20 +1,20 @@
namespace Suteki.TardisBank.Api.Announcements;
using System.ComponentModel.DataAnnotations;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
public class NewAnnouncement
{
- [JsonProperty("date")]
+ [JsonPropertyName("date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Required]
- [JsonProperty("title")]
+ [JsonPropertyName("title")]
public string Title { get; set; } = null!;
- [JsonProperty("content")]
+ [JsonPropertyName("content")]
[Required]
public string Content { get; set; } = null!;
}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Suteki.TardisBank.Api.csproj b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Suteki.TardisBank.Api.csproj
index 3e03f86ed..61718a1f8 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Suteki.TardisBank.Api.csproj
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Api/Suteki.TardisBank.Api.csproj
@@ -1,7 +1,3 @@
-
-
-
-
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Account.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Account.cs
index 8176fe7a8..8f7f79c99 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Account.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Account.cs
@@ -1,4 +1,5 @@
// ReSharper disable MissingXmlDoc
+
namespace Suteki.TardisBank.Domain;
using SharpArch.Domain.DomainModel;
@@ -34,7 +35,8 @@ public virtual void AddTransaction(string? description, decimal amount)
void RemoveOldTransactions()
{
- if (Transactions.Count <= MaxTransactions) return;
+ if (Transactions.Count <= MaxTransactions)
+ return;
var oldestTransaction = Transactions.First();
Transactions.Remove(oldestTransaction);
@@ -59,7 +61,8 @@ public virtual void TriggerScheduledPayments(DateTime now)
public virtual void RemovePaymentSchedule(int paymentScheduleId)
{
var scheduleToRemove = PaymentSchedules.SingleOrDefault(x => x.Id == paymentScheduleId);
- if (scheduleToRemove == null) return;
+ if (scheduleToRemove == null)
+ return;
PaymentSchedules.Remove(scheduleToRemove);
}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Announcement.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Announcement.cs
index 9848b874a..a2c8b8271 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Announcement.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Announcement.cs
@@ -1,4 +1,5 @@
// ReSharper disable MissingXmlDoc
+
namespace Suteki.TardisBank.Domain;
using System.ComponentModel.DataAnnotations;
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Parent.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Parent.cs
index a64c01e30..1ea0fd1bf 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Parent.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Parent.cs
@@ -1,4 +1,5 @@
// ReSharper disable MissingXmlDoc
+
namespace Suteki.TardisBank.Domain;
using Events;
@@ -24,7 +25,8 @@ protected Parent()
// should be called when parent is first created.
public virtual Parent Initialise(IMediator mediator)
{
- if (mediator == null) throw new ArgumentNullException(nameof(mediator));
+ if (mediator == null)
+ throw new ArgumentNullException(nameof(mediator));
ActivationKey = Guid.NewGuid().ToString();
mediator.Publish(new NewParentCreatedEvent(this));
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Suteki.TardisBank.Domain.csproj b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Suteki.TardisBank.Domain.csproj
index c2902264c..8dd7604b2 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Suteki.TardisBank.Domain.csproj
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/Suteki.TardisBank.Domain.csproj
@@ -2,7 +2,6 @@
-
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/User.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/User.cs
index 4b039c72c..b832fb8b6 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/User.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Domain/User.cs
@@ -1,4 +1,5 @@
// ReSharper disable MissingXmlDoc
+
namespace Suteki.TardisBank.Domain;
using Events;
@@ -48,7 +49,8 @@ protected User(string name, string userName, string password)
public virtual void SendMessage(string text, IMediator mediator)
{
- if (mediator == null) throw new ArgumentNullException(nameof(mediator));
+ if (mediator == null)
+ throw new ArgumentNullException(nameof(mediator));
Messages.Add(new Message(DateTime.Now.Date, text, this));
RemoveOldMessages();
@@ -58,7 +60,8 @@ public virtual void SendMessage(string text, IMediator mediator)
void RemoveOldMessages()
{
- if (Messages.Count <= MaxMessages) return;
+ if (Messages.Count <= MaxMessages)
+ return;
var oldestMessage = Messages.First();
Messages.Remove(oldestMessage);
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Configuration/AnnouncementMappingProfile.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Configuration/AnnouncementMappingProfile.cs
index 404d500b3..7757af3d1 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Configuration/AnnouncementMappingProfile.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Configuration/AnnouncementMappingProfile.cs
@@ -2,7 +2,6 @@
using Api.Announcements;
using AutoMapper;
-using AutoMapper.Internal;
using Domain;
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Suteki.TardisBank.Infrastructure.csproj b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Suteki.TardisBank.Infrastructure.csproj
index 596e15c17..5a553623c 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Suteki.TardisBank.Infrastructure.csproj
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Infrastructure/Suteki.TardisBank.Infrastructure.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/EmailService.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/EmailService.cs
index 719cc28bd..ad807fb71 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/EmailService.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/EmailService.cs
@@ -36,7 +36,8 @@ public void SendEmail(string toAddress, string subject, string body)
throw new ArgumentNullException(nameof(body));
}
- if (string.IsNullOrWhiteSpace(_configuration.EmailSmtpServer)) return;
+ if (string.IsNullOrWhiteSpace(_configuration.EmailSmtpServer))
+ return;
var message = new MailMessage(
_configuration.EmailFromAddress,
@@ -44,11 +45,7 @@ public void SendEmail(string toAddress, string subject, string body)
subject,
body);
- var client = new SmtpClient(_configuration.EmailSmtpServer)
- {
- EnableSsl = _configuration.EmailEnableSsl,
- Port = _configuration.EmailPort,
- };
+ var client = new SmtpClient(_configuration.EmailSmtpServer) { EnableSsl = _configuration.EmailEnableSsl, Port = _configuration.EmailPort, };
if (!string.IsNullOrWhiteSpace(_configuration.EmailCredentialsUserName) &&
!string.IsNullOrWhiteSpace(_configuration.EmailCredentialsPassword))
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/UserService.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/UserService.cs
index 0cc1dec39..b20848996 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/UserService.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tasks/UserService.cs
@@ -22,7 +22,8 @@ public UserService(IHttpContextService context, ILinqRepository par
public Task GetCurrentUser(CancellationToken cancellationToken)
{
- if (!_context.UserIsAuthenticated) return Task.FromResult(null);
+ if (!_context.UserIsAuthenticated)
+ return Task.FromResult(null);
return GetUserByUserName(_context.UserName, cancellationToken);
}
@@ -54,14 +55,16 @@ public UserService(IHttpContextService context, ILinqRepository par
public Task SaveUser(User user, CancellationToken cancellationToken)
{
- if (user == null) throw new ArgumentNullException(nameof(user));
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
return _userRepository.SaveAsync(user, cancellationToken);
}
public bool AreNullOrNotRelated(Parent? parent, Child? child)
{
- if (parent == null || child == null) return true;
+ if (parent == null || child == null)
+ return true;
if (!parent.HasChild(child))
{
@@ -73,8 +76,8 @@ public bool AreNullOrNotRelated(Parent? parent, Child? child)
public async Task IsNotChildOfCurrentUser(Child? child, CancellationToken cancellationToken)
{
- var parent = (await GetCurrentUser(cancellationToken).ConfigureAwait(false)) as Parent;
- return (child == null) || (parent == null) || (!parent.HasChild(child));
+ var parent = await GetCurrentUser(cancellationToken).ConfigureAwait(false) as Parent;
+ return child == null || parent == null || !parent.HasChild(child);
}
public Task DeleteUser(int userId, CancellationToken cancellationToken)
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Functional/AnnouncementControllerTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Functional/AnnouncementControllerTests.cs
index 746fed1fd..a9553044c 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Functional/AnnouncementControllerTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Functional/AnnouncementControllerTests.cs
@@ -1,10 +1,11 @@
namespace Suteki.TardisBank.Tests.Functional;
using Api.Announcements;
-using Shouldly;
using Setup;
+using Shouldly;
using Xunit;
+
[Trait("Category", "Functional")]
public class AnnouncementControllerTests : IClassFixture, IDisposable
{
@@ -33,12 +34,7 @@ public async Task CanSaveAnnouncement()
{
var today = DateTime.Now.Date;
var uid = Guid.NewGuid().ToString("N");
- var newAnnouncement = new NewAnnouncement
- {
- Date = today,
- Content = "New announcement " + today,
- Title = uid
- };
+ var newAnnouncement = new NewAnnouncement { Date = today, Content = "New announcement " + today, Title = uid };
var response = await _setup.Client.PostAsJsonAsync("announcements", newAnnouncement);
response.EnsureSuccessStatusCode();
@@ -52,4 +48,4 @@ public async Task CanSaveAnnouncement()
await DeleteAnnouncement(_newAnnouncementUri);
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Helpers/RunnableInDebugOnlyAttribute.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Helpers/RunnableInDebugOnlyAttribute.cs
index e52c4ae70..2b146f490 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Helpers/RunnableInDebugOnlyAttribute.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Helpers/RunnableInDebugOnlyAttribute.cs
@@ -1,6 +1,7 @@
namespace Suteki.TardisBank.Tests.Helpers;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using Xunit;
@@ -11,11 +12,16 @@
[AttributeUsage(AttributeTargets.Method)]
public sealed class RunnableInDebugOnlyAttribute : FactAttribute
{
+#if NET8_0_OR_GREATER
+ public RunnableInDebugOnlyAttribute(
+ [CallerFilePath] string? sourceFilePath = null,
+ [CallerLineNumber] int sourceLineNumber = -1)
+ : base(sourceFilePath, sourceLineNumber)
+#else
public RunnableInDebugOnlyAttribute()
+#endif
{
if (!Debugger.IsAttached)
- {
Skip = "Only running in interactive mode.";
- }
}
}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ChildTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ChildTests.cs
index ce7afd8a9..63481aeb3 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ChildTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ChildTests.cs
@@ -1,9 +1,9 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using SharpArch.NHibernate;
using SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Xunit;
@@ -68,4 +68,4 @@ public async Task Should_be_able_to_create_and_retrieve_a_child()
child.Password.ShouldBe("xxx");
child.Account.ShouldNotBeNull();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageCountLimitTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageCountLimitTests.cs
index 186dae839..00f233e0e 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageCountLimitTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageCountLimitTests.cs
@@ -1,9 +1,9 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using MediatR;
using Moq;
+using Shouldly;
using Xunit;
@@ -38,4 +38,4 @@ public void Number_of_messages_should_be_limited()
_user.Messages.First().Text.ShouldBe("Message2");
_user.Messages.Last().Text.ShouldBe("New2");
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageTests.cs
index a99ca940a..c99c34115 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/MessageTests.cs
@@ -1,11 +1,11 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using MediatR;
using Moq;
using SharpArch.NHibernate;
using SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Xunit;
@@ -42,4 +42,4 @@ public async Task Should_be_able_to_add_a_message_to_a_user()
Parent parent = (await parentRepository.GetAsync(_userId))!;
parent.Messages.Count.ShouldBe(1);
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ParentTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ParentTests.cs
index cd212f979..d56dc45a8 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ParentTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/ParentTests.cs
@@ -1,9 +1,9 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using SharpArch.NHibernate;
using SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Xunit;
@@ -53,4 +53,4 @@ public async Task Should_be_able_to_create_and_retrieve_Parent()
parent.UserName.ShouldBe("mike@yahoo.com");
parent.Children.ShouldNotBeNull();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingQueryTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingQueryTests.cs
index 7228712bf..a9a3d0090 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingQueryTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingQueryTests.cs
@@ -1,9 +1,9 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using NHibernate.Linq;
using SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Tasks;
using Xunit;
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingTests.cs
index 3df321b1b..b6aa5c7f4 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/PaymentSchedulingTests.cs
@@ -1,8 +1,8 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using Humanizer;
+using Shouldly;
using Xunit;
@@ -70,4 +70,4 @@ public void Triggering_payment_before_next_run_causes_nothing_to_happen()
_child.Account.Transactions.Count.ShouldBe(0);
_child.Account.PaymentSchedules[0].NextRun.ShouldBe(_startDate);
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransactionCountLimitTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransactionCountLimitTests.cs
index 2b3219a32..bb822cf31 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransactionCountLimitTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransactionCountLimitTests.cs
@@ -43,4 +43,4 @@ public void When_more_than_max_transactions_created_transactions_should_be_trunc
_child.Account.Transactions.First().Description.ShouldBe("payment2");
_child.Account.Transactions.Last().Description.ShouldBe("payment_new2");
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransientDatabaseSetup.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransientDatabaseSetup.cs
index 42c57fcd5..53d148a04 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransientDatabaseSetup.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/TransientDatabaseSetup.cs
@@ -8,10 +8,8 @@ public class TransientDatabaseSetup : TestDatabaseSetup
{
///
public TransientDatabaseSetup()
- : base(typeof(TransientDatabaseSetup).Assembly, typeof(AutoPersistenceModelGenerator), new[]
- {
- typeof(AutoPersistenceModelGenerator).Assembly
- })
+ : base(typeof(TransientDatabaseSetup).Assembly, typeof(AutoPersistenceModelGenerator),
+ new[] { typeof(AutoPersistenceModelGenerator).Assembly })
{
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserActivationTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserActivationTests.cs
index aa581a8b4..3cd5ede99 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserActivationTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserActivationTests.cs
@@ -2,9 +2,9 @@ namespace Suteki.TardisBank.Tests.Model;
using Domain;
using Domain.Events;
-using Shouldly;
using MediatR;
using Moq;
+using Shouldly;
using Xunit;
@@ -41,4 +41,4 @@ public void ParentShouldNotBeActiveWhenCreated()
User parent = new Parent("Dad", "mike@mike.com", "xxx");
parent.IsActive.ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserTests.cs
index 9b6800e8a..ffa2e6261 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/UserTests.cs
@@ -1,8 +1,8 @@
namespace Suteki.TardisBank.Tests.Model;
using Domain;
-using Shouldly;
using SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Xunit;
@@ -45,4 +45,4 @@ public void Should_be_able_to_treat_Parents_and_Children_Polymorphically()
users[3].GetType().Name.ShouldBe("Parent");
users[4].GetType().Name.ShouldBe("Child");
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/WithdrawlCashTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/WithdrawlCashTests.cs
index 3f56c5f71..647dbf392 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/WithdrawlCashTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Model/WithdrawlCashTests.cs
@@ -2,10 +2,10 @@ namespace Suteki.TardisBank.Tests.Model;
using Domain;
using Domain.Events;
-using Shouldly;
using MediatR;
using Moq;
using SharpArch.Testing.Xunit;
+using Shouldly;
using Xunit;
@@ -27,14 +27,6 @@ public WithdrawlCashTests()
_somebodyElsesParent = new Parent("Not Dad", "jon@jon.com", "zzz");
}
- [Fact]
- public void Child_should_not_be_able_to_withdraw_from_some_other_parent()
- {
- Action withdraw = () => _child.WithdrawCashFromParent(_somebodyElsesParent, 2.30M, "for toys", _mediator.Object);
- var exception = withdraw.ShouldThrow();
- exception.Message.ShouldBe("Not Your Parent");
- }
-
[Fact]
[SetCulture("en-GB")]
public void Child_should_be_able_to_withdraw_cash()
@@ -49,6 +41,14 @@ public void Child_should_be_able_to_withdraw_cash()
_parent.Messages[0].Text.ShouldBe("Leo would like to withdraw £2.30");
}
+ [Fact]
+ public void Child_should_not_be_able_to_withdraw_from_some_other_parent()
+ {
+ Action withdraw = () => _child.WithdrawCashFromParent(_somebodyElsesParent, 2.30M, "for toys", _mediator.Object);
+ var exception = withdraw.ShouldThrow();
+ exception.Message.ShouldBe("Not Your Parent");
+ }
+
[Fact]
[SetCulture("en-GB")]
public void Child_should_not_be_able_to_withdraw_more_than_their_balance()
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Services/EmailServiceTests.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Services/EmailServiceTests.cs
index 5d6c9fb3a..6fd9d4281 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Services/EmailServiceTests.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Services/EmailServiceTests.cs
@@ -37,4 +37,4 @@ public void Should_be_able_to_send_an_email()
_emailService.SendEmail(toAddress, subject, body);
}
-}
\ No newline at end of file
+}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Suteki.TardisBank.Tests.csproj b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Suteki.TardisBank.Tests.csproj
index a73db13fc..c3a3a51c1 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Suteki.TardisBank.Tests.csproj
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.Tests/Suteki.TardisBank.Tests.csproj
@@ -2,31 +2,31 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Controllers/AnnouncementsController.cs b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Controllers/AnnouncementsController.cs
index 3e5a8f852..453229888 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Controllers/AnnouncementsController.cs
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Controllers/AnnouncementsController.cs
@@ -1,7 +1,6 @@
#pragma warning disable 1573
namespace Suteki.TardisBank.WebApi.Controllers;
-using System.Data;
using Api.Announcements;
using AutoMapper;
using AutoMapper.QueryableExtensions;
@@ -14,7 +13,7 @@ namespace Suteki.TardisBank.WebApi.Controllers;
[ApiController]
[Route("[controller]")]
-[Transaction(IsolationLevel.ReadCommitted)]
+[Transaction()]
public class AnnouncementsController : ControllerBase
{
readonly ILinqRepository _announcementRepository;
@@ -61,7 +60,8 @@ public async Task>> Get()
public async Task> Get(int id)
{
var announcement = await _announcementRepository.GetAsync(id).ConfigureAwait(false);
- if (announcement != null) return _mapper.Map(announcement)!;
+ if (announcement != null)
+ return _mapper.Map(announcement)!;
return NotFound(new { id });
}
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/NHibernate-Postgres.config b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/NHibernate-Postgres.config
new file mode 100644
index 000000000..40d999fc7
--- /dev/null
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/NHibernate-Postgres.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ Host=localhost;Port=5432;Database=TardisBank;Username=postgres;Password=Password12!;
+
+
+
+ NHibernate.Dialect.PostgreSQLDialect
+
+
+ NHibernate.Connection.DriverConnectionProvider
+ NHibernate.Driver.NpgsqlDriver
+
+
+ true
+ auto
+ 500
+
+
+ update
+
+
+
diff --git a/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Suteki.TardisBank.WebApi.csproj b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Suteki.TardisBank.WebApi.csproj
index bf2292929..497d81b1b 100644
--- a/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Suteki.TardisBank.WebApi.csproj
+++ b/Src/Samples/TardisBank/Src/Suteki.TardisBank.WebApi/Suteki.TardisBank.WebApi.csproj
@@ -5,14 +5,14 @@
-
-
-
-
+
+
+
+
-
+
-
+
diff --git a/Src/Samples/TransactionAttribute/App/Controllers/OverridesController.cs b/Src/Samples/TransactionAttribute/App/Controllers/OverridesController.cs
index d4dfe1a68..395bb660a 100644
--- a/Src/Samples/TransactionAttribute/App/Controllers/OverridesController.cs
+++ b/Src/Samples/TransactionAttribute/App/Controllers/OverridesController.cs
@@ -8,7 +8,7 @@
[Route("api/[controller]")]
[ApiController]
-[Transaction(IsolationLevel.ReadCommitted)]
+[Transaction()]
public class OverridesController : ControllerBase
{
static readonly ILogger Log = Serilog.Log.ForContext();
diff --git a/Src/Samples/TransactionAttribute/App/Stubs/TransactionManagerStub.cs b/Src/Samples/TransactionAttribute/App/Stubs/TransactionManagerStub.cs
index 5d7f6e818..fc588191b 100644
--- a/Src/Samples/TransactionAttribute/App/Stubs/TransactionManagerStub.cs
+++ b/Src/Samples/TransactionAttribute/App/Stubs/TransactionManagerStub.cs
@@ -32,7 +32,8 @@ public IDisposable BeginTransaction(IsolationLevel isolationLevel = IsolationLev
public Task CommitTransactionAsync(CancellationToken cancellationToken)
{
- _httpContextAccessor.HttpContext!.Response.Headers.Append(TransactionIsolationLevel, _transaction?.IsolationLevel.ToString() ?? "unknown");
+ _httpContextAccessor.HttpContext!.Response.Headers.Append(TransactionIsolationLevel,
+ _transaction?.IsolationLevel.ToString() ?? "unknown");
_httpContextAccessor.HttpContext.Response.Headers.Append(TransactionState, "committed");
_transaction?.Commit();
return Task.CompletedTask;
@@ -40,7 +41,8 @@ public Task CommitTransactionAsync(CancellationToken cancellationToken)
public Task RollbackTransactionAsync(CancellationToken cancellationToken)
{
- _httpContextAccessor.HttpContext!.Response.Headers.Append(TransactionIsolationLevel, _transaction?.IsolationLevel.ToString() ?? "unknown");
+ _httpContextAccessor.HttpContext!.Response.Headers.Append(TransactionIsolationLevel,
+ _transaction?.IsolationLevel.ToString() ?? "unknown");
_httpContextAccessor.HttpContext.Response.Headers.Append(TransactionState, "rolled-back");
_transaction?.Rollback();
return Task.CompletedTask;
diff --git a/Src/Samples/TransactionAttribute/App/TransactionAttribute.WebApi.csproj b/Src/Samples/TransactionAttribute/App/TransactionAttribute.WebApi.csproj
index f67006db3..d7f9ed515 100644
--- a/Src/Samples/TransactionAttribute/App/TransactionAttribute.WebApi.csproj
+++ b/Src/Samples/TransactionAttribute/App/TransactionAttribute.WebApi.csproj
@@ -5,22 +5,26 @@
-
-
+
+
-
+
-
+
-
+
+
+
+
+
diff --git a/Src/Samples/TransactionAttribute/Tests/TransactionAttribute.Tests.csproj b/Src/Samples/TransactionAttribute/Tests/TransactionAttribute.Tests.csproj
index 9986dd4ac..9508a8020 100644
--- a/Src/Samples/TransactionAttribute/Tests/TransactionAttribute.Tests.csproj
+++ b/Src/Samples/TransactionAttribute/Tests/TransactionAttribute.Tests.csproj
@@ -9,6 +9,10 @@
+
+
+
+
all
@@ -16,6 +20,16 @@
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
@@ -24,6 +38,10 @@
+
+
+
+
diff --git a/Src/Samples/TransactionAttribute/Tests/UnitOfWorkAttributeOverrideTests.cs b/Src/Samples/TransactionAttribute/Tests/UnitOfWorkAttributeOverrideTests.cs
index ceadc1961..3fa849019 100644
--- a/Src/Samples/TransactionAttribute/Tests/UnitOfWorkAttributeOverrideTests.cs
+++ b/Src/Samples/TransactionAttribute/Tests/UnitOfWorkAttributeOverrideTests.cs
@@ -1,13 +1,17 @@
using Xunit;
-[assembly: AssemblyTrait("Category", "Samples")]
+#if NET8_0_OR_GREATER
+[assembly: Trait("Category", "Samples")]
+#else
+[assembly: Xunit.AssemblyTrait("Category", "Samples")]
+#endif
namespace TransactionAttribute.Tests;
using System.Data;
using System.Net;
-using Shouldly;
using Setup;
+using Shouldly;
using WebApi.Stubs;
using Xunit;
diff --git a/Src/SharpArch.Library.DotSettings.AutoLoad b/Src/SharpArch.Library.DotSettings.AutoLoad
deleted file mode 100644
index 89316e414..000000000
--- a/Src/SharpArch.Library.DotSettings.AutoLoad
+++ /dev/null
@@ -1,2 +0,0 @@
-
- Library
\ No newline at end of file
diff --git a/SharpArch.AutoLoad.DotSettings b/Src/SharpArch.sln.DotSettings
similarity index 100%
rename from SharpArch.AutoLoad.DotSettings
rename to Src/SharpArch.sln.DotSettings
diff --git a/Src/Tests/Directory.Build.props b/Src/Tests/Directory.Build.props
index 61302408e..de7e049a5 100644
--- a/Src/Tests/Directory.Build.props
+++ b/Src/Tests/Directory.Build.props
@@ -1,4 +1,5 @@
+
@@ -9,14 +10,16 @@
-
+
-
-
-
+
+
+
+
all
runtime; build; native; contentfiles; analyzers
-
\ No newline at end of file
+
diff --git a/Src/Tests/SharpArch.Tests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs b/Src/Tests/SharpArch.Tests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
index 4ad2c4fdb..84f2a33fd 100644
--- a/Src/Tests/SharpArch.Tests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
+++ b/Src/Tests/SharpArch.Tests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
@@ -1,8 +1,8 @@
namespace Tests.SharpArch.NHibernate;
using Domain;
-using Shouldly;
using NUnit.Framework;
+using Shouldly;
[TestFixture]
@@ -40,4 +40,4 @@ public void WhenEntityWithDuplicateStringIdExists_Should_MarkEntityAsInvalid()
duplicateUser.IsValid(ValidationContextFor(duplicateUser))
.ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.Tests.NHibernate/NHibernateSessionFactoryBuilderTests.cs b/Src/Tests/SharpArch.Tests.NHibernate/NHibernateSessionFactoryBuilderTests.cs
index fa708c41b..6915aba4d 100644
--- a/Src/Tests/SharpArch.Tests.NHibernate/NHibernateSessionFactoryBuilderTests.cs
+++ b/Src/Tests/SharpArch.Tests.NHibernate/NHibernateSessionFactoryBuilderTests.cs
@@ -1,10 +1,10 @@
namespace Tests.SharpArch.NHibernate;
-using Shouldly;
using FluentNHibernate.Cfg.Db;
using global::NHibernate.Cfg;
using global::SharpArch.NHibernate;
using NUnit.Framework;
+using Shouldly;
[TestFixture]
@@ -21,7 +21,8 @@ public void TearDown()
{
try
{
- if (File.Exists(_tempFileName)) File.Delete(_tempFileName);
+ if (File.Exists(_tempFileName))
+ File.Delete(_tempFileName);
}
// ReSharper disable once CatchAllClause
catch
@@ -130,4 +131,4 @@ public void WhenUsingDataAnnotationValidators_ShouldKeepRegisteredPreUpdateEvent
configuration.EventListeners.PreUpdateEventListeners.ShouldContain(l => l is PreUpdateListener);
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.Tests.NHibernate/RepositoryTests.cs b/Src/Tests/SharpArch.Tests.NHibernate/RepositoryTests.cs
index d8622a8de..b2891b20d 100644
--- a/Src/Tests/SharpArch.Tests.NHibernate/RepositoryTests.cs
+++ b/Src/Tests/SharpArch.Tests.NHibernate/RepositoryTests.cs
@@ -2,13 +2,13 @@
namespace Tests.SharpArch.NHibernate;
-using Shouldly;
using global::NHibernate;
using global::SharpArch.Domain.DomainModel;
using global::SharpArch.Domain.PersistenceSupport;
using global::SharpArch.NHibernate;
using Moq;
using NUnit.Framework;
+using Shouldly;
[TestFixture]
@@ -31,4 +31,4 @@ public void CanCastConcreteLinqRepositoryToInterfaceILinqRepository()
public class MyEntity : Entity
{
string? Name { get; set; }
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.Tests.NHibernate/SharpArch.Tests.NHibernate.csproj b/Src/Tests/SharpArch.Tests.NHibernate/SharpArch.Tests.NHibernate.csproj
index 78a7f6e14..9dd67297b 100644
--- a/Src/Tests/SharpArch.Tests.NHibernate/SharpArch.Tests.NHibernate.csproj
+++ b/Src/Tests/SharpArch.Tests.NHibernate/SharpArch.Tests.NHibernate.csproj
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/Src/Tests/SharpArch.XunitTests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs b/Src/Tests/SharpArch.XunitTests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
index 799520043..ab1838b96 100644
--- a/Src/Tests/SharpArch.XunitTests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
+++ b/Src/Tests/SharpArch.XunitTests.NHibernate/HasUniqueDomainSignatureValidatorTests.cs
@@ -42,4 +42,4 @@ public async Task WhenEntityWithDuplicateStringIdExists_Should_MarkEntityAsInval
duplicateUser.IsValid(ValidationContextFor(duplicateUser))
.ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateRepositoryTests.cs b/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateRepositoryTests.cs
index a9560e4d3..bb4a758a0 100644
--- a/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateRepositoryTests.cs
+++ b/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateRepositoryTests.cs
@@ -1,9 +1,9 @@
namespace Tests.SharpArch.NHibernate;
using Domain;
-using Shouldly;
using global::SharpArch.NHibernate;
using global::SharpArch.Testing.Xunit.NHibernate;
+using Shouldly;
using Xunit;
@@ -25,10 +25,7 @@ protected override Task LoadTestData(CancellationToken cancellationToken)
[Fact]
public async Task CanSaveAsync()
{
- var entity = new Contractor
- {
- Name = "John Doe"
- };
+ var entity = new Contractor { Name = "John Doe" };
var res = await _repo.SaveAsync(entity);
res.IsTransient().ShouldBeFalse();
@@ -38,10 +35,7 @@ public async Task CanSaveAsync()
[Fact]
public async Task CanSaveOrUpdate()
{
- var entity = new Contractor
- {
- Name = "John Doe"
- };
+ var entity = new Contractor { Name = "John Doe" };
var res = await _repo.SaveOrUpdateAsync(entity);
res.IsTransient().ShouldBeFalse();
@@ -49,4 +43,4 @@ public async Task CanSaveOrUpdate()
res = await _repo.SaveOrUpdateAsync(entity);
res.Name.ShouldBe("John Doe Jr");
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateTestsSetup.cs b/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateTestsSetup.cs
index 962e801f9..0a6519dc8 100644
--- a/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateTestsSetup.cs
+++ b/Src/Tests/SharpArch.XunitTests.NHibernate/NHibernateTestsSetup.cs
@@ -27,10 +27,6 @@ protected override void Customize(NHibernateSessionFactoryBuilder builder)
{
base.Customize(builder);
builder.UsePersistenceConfigurer(new SQLiteConfiguration().InMemory());
- builder.UseProperties(new SortedList
- {
- [Environment.ReleaseConnections] = "on_close",
- [Environment.Hbm2ddlAuto] = "create"
- });
+ builder.UseProperties(new SortedList { [Environment.ReleaseConnections] = "on_close", [Environment.Hbm2ddlAuto] = "create" });
}
}
diff --git a/Src/Tests/SharpArch.XunitTests.NHibernate/RepositoryTests.cs b/Src/Tests/SharpArch.XunitTests.NHibernate/RepositoryTests.cs
index f584b448e..85a03ca72 100644
--- a/Src/Tests/SharpArch.XunitTests.NHibernate/RepositoryTests.cs
+++ b/Src/Tests/SharpArch.XunitTests.NHibernate/RepositoryTests.cs
@@ -2,12 +2,12 @@
namespace Tests.SharpArch.NHibernate;
-using Shouldly;
using global::NHibernate;
using global::SharpArch.Domain.DomainModel;
using global::SharpArch.Domain.PersistenceSupport;
using global::SharpArch.NHibernate;
using Moq;
+using Shouldly;
using Xunit;
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DataAnnotationsValidator/HasUniqueEntitySignatureValidatorTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DataAnnotationsValidator/HasUniqueEntitySignatureValidatorTests.cs
index 893791284..1ac794ca2 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DataAnnotationsValidator/HasUniqueEntitySignatureValidatorTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DataAnnotationsValidator/HasUniqueEntitySignatureValidatorTests.cs
@@ -2,11 +2,11 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
-using Shouldly;
using global::SharpArch.Domain.DomainModel;
using global::SharpArch.Domain.PersistenceSupport;
using global::SharpArch.Domain.Validation;
using Moq;
+using Shouldly;
using Xunit;
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/BaseObjectEqualityComparerTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/BaseObjectEqualityComparerTests.cs
index 7de90e8c5..1bcd4e8db 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/BaseObjectEqualityComparerTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/BaseObjectEqualityComparerTests.cs
@@ -3,9 +3,9 @@
namespace Tests.SharpArch.Domain.DomainModel;
using System.Reflection;
-using Shouldly;
using global::SharpArch.Domain.DomainModel;
using global::SharpArch.Testing.Helpers;
+using Shouldly;
using Xunit;
@@ -151,4 +151,4 @@ public void CannotSuccessfullyCompareDifferentlyTypedObjectsThatDeriveFromBaseOb
comparer.Equals(obj1, obj2).ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/EntityTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/EntityTests.cs
index 25d87fa9c..287534ff3 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/EntityTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/EntityTests.cs
@@ -4,9 +4,9 @@
namespace Tests.SharpArch.Domain.DomainModel;
-using Shouldly;
using global::SharpArch.Domain.DomainModel;
using global::SharpArch.Testing.Helpers;
+using Shouldly;
using Xunit;
@@ -26,42 +26,12 @@ public class EntityTests
public EntityTests()
{
- _obj = new MockEntityObjectWithDefaultId
- {
- FirstName = "FName1",
- LastName = "LName1",
- Email = @"testus...@mail.com"
- };
- _sameObj = new MockEntityObjectWithDefaultId
- {
- FirstName = "FName1",
- LastName = "LName1",
- Email = @"testus...@mail.com"
- };
- _diffObj = new MockEntityObjectWithDefaultId
- {
- FirstName = "FName2",
- LastName = "LName2",
- Email = @"testuse...@mail.com"
- };
- _objWithId = new MockEntityObjectWithSetId
- {
- FirstName = "FName1",
- LastName = "LName1",
- Email = @"testus...@mail.com"
- };
- _sameObjWithId = new MockEntityObjectWithSetId
- {
- FirstName = "FName1",
- LastName = "LName1",
- Email = @"testus...@mail.com"
- };
- _diffObjWithId = new MockEntityObjectWithSetId
- {
- FirstName = "FName2",
- LastName = "LName2",
- Email = @"testuse...@mail.com"
- };
+ _obj = new MockEntityObjectWithDefaultId { FirstName = "FName1", LastName = "LName1", Email = @"testus...@mail.com" };
+ _sameObj = new MockEntityObjectWithDefaultId { FirstName = "FName1", LastName = "LName1", Email = @"testus...@mail.com" };
+ _diffObj = new MockEntityObjectWithDefaultId { FirstName = "FName2", LastName = "LName2", Email = @"testuse...@mail.com" };
+ _objWithId = new MockEntityObjectWithSetId { FirstName = "FName1", LastName = "LName1", Email = @"testus...@mail.com" };
+ _sameObjWithId = new MockEntityObjectWithSetId { FirstName = "FName1", LastName = "LName1", Email = @"testus...@mail.com" };
+ _diffObjWithId = new MockEntityObjectWithSetId { FirstName = "FName2", LastName = "LName2", Email = @"testuse...@mail.com" };
}
@@ -353,12 +323,7 @@ public void CanCompareObjectsWithComplexProperties()
obj1.Equals(obj2).ShouldBeTrue();
- obj1.Address = new AddressBeingDomainSignatureComparable
- {
- Address1 = "123 Smith Ln.",
- Address2 = "Suite 201",
- ZipCode = 12345
- };
+ obj1.Address = new AddressBeingDomainSignatureComparable { Address1 = "123 Smith Ln.", Address2 = "Suite 201", ZipCode = 12345 };
obj1.Equals(obj2).ShouldBeFalse();
// Set the address of the 2nd to be different to the address of the first
@@ -641,4 +606,4 @@ public void WontGetConfusedWithOutsideCases()
obj2.Name = @"Supercalifragilisticexpialidociouz";
obj1.Equals(obj2).ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/ValueObjectTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/ValueObjectTests.cs
index 85d185597..4da054356 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/ValueObjectTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/DomainModel/ValueObjectTests.cs
@@ -1,8 +1,7 @@
namespace Tests.SharpArch.Domain.DomainModel;
-using System.Diagnostics.CodeAnalysis;
-using Shouldly;
using global::SharpArch.Domain.DomainModel;
+using Shouldly;
using Xunit;
@@ -133,4 +132,4 @@ public void ShouldNotBeEqualWithDifferentReferencesAndDifferentIds()
var val2 = new DummyValueType { Id = 10, Name = "Luis" };
val2.Equals(val1).ShouldBeFalse();
}
-}
\ No newline at end of file
+}
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/Reflection/TypePropertyDescriptorCacheTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/Reflection/TypePropertyDescriptorCacheTests.cs
index aec0df1a4..6d2905c4d 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/Reflection/TypePropertyDescriptorCacheTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Domain/Reflection/TypePropertyDescriptorCacheTests.cs
@@ -1,7 +1,7 @@
namespace Tests.SharpArch.Domain.Reflection;
-using Shouldly;
using global::SharpArch.Domain.Reflection;
+using Shouldly;
using Xunit;
diff --git a/Src/Tests/SharpArch.XunitTests/SharpArch.Infrastructure/CodeBaseLocatorTests.cs b/Src/Tests/SharpArch.XunitTests/SharpArch.Infrastructure/CodeBaseLocatorTests.cs
index c5c2b14f0..e42d5cda1 100644
--- a/Src/Tests/SharpArch.XunitTests/SharpArch.Infrastructure/CodeBaseLocatorTests.cs
+++ b/Src/Tests/SharpArch.XunitTests/SharpArch.Infrastructure/CodeBaseLocatorTests.cs
@@ -1,10 +1,9 @@
namespace Tests.SharpArch.Infrastructure;
using System.Reflection;
-using Shouldly;
using global::SharpArch.Infrastructure;
+using Shouldly;
using Xunit;
-using Xunit.Abstractions;
public class CodeBaseLocatorTests
diff --git a/VersionHistory.txt b/VersionHistory.txt
index b74da78bd..71e24a121 100644
--- a/VersionHistory.txt
+++ b/VersionHistory.txt
@@ -1,8 +1,43 @@
+vNext
========================
-vNext
+
+
+9.1.0
========================
+ENHANCEMENTS:
+* Add .NET 10 support (net10.0 added to AppTargetFrameworks)
+* TardisBank sample now targets net9.0 and net10.0
+
+BUILD:
+* Upgrade xUnit to v3 for .NET 8+ targets (xunit.v3 3.2.2 / xunit.v3.extensibility.core 3.2.2);
+ keep xunit v2 (xunit.core 2.9.3) for netstandard2.1 and net6.0
+* Upgrade Microsoft.SourceLink.GitHub 8.0.0 -> 10.0.300
+
+DEPENDENCIES:
+* NHibernate upgraded 5.5.3 -> 5.6.1
+* NUnit upgraded 4.4.0 -> 4.6.1
+* NUnit3TestAdapter upgraded 5.1.0 -> 6.2.0
+* xunit.runner.visualstudio upgraded 3.1.4 -> 3.1.5
+* Microsoft.NET.Test.Sdk upgraded 17.14.1 -> 18.6.0
+* JetBrains.Annotations upgraded 2025.2.2 -> 2026.2.0
+* Microsoft.Extensions.Logging.Abstractions 10.0.0 added for net10.0 target
+* Microsoft.Extensions.DependencyInjection.Abstractions 10.0.0 added for net10.0 target
+* Autofac upgraded 8.4.0 -> 9.3.0 (samples)
+* Autofac.Extensions.DependencyInjection upgraded 10.0.0 -> 11.0.1 (samples)
+* Microsoft.Data.SqlClient upgraded 6.1.1 -> 7.0.1 (samples)
+* Humanizer.Core upgraded 2.14.1 -> 3.0.10 (TardisBank sample)
+* Newtonsoft.Json upgraded 13.0.3 -> 13.0.4 (TardisBank sample)
+* Serilog.AspNetCore now TFM-conditional per target (8.0.0/9.0.0/10.0.0)
+* Serilog.Sinks.Console upgraded 6.0.0 -> 6.1.1 (samples)
+* Serilog.Sinks.Seq upgraded 9.0.0 -> 9.1.0 (samples)
+* Serilog upgraded 4.3.0 -> 4.3.1 (TardisBank sample)
+* SQLitePCLRaw.bundle_e_sqlite3 upgraded 3.0.2 -> 3.0.3 (TardisBank sample)
+* NHibernate.Extensions.Sqlite now TFM-conditional (9.0.11 / net9.0, 10.0.2 / net10.0)
+* Microsoft.AspNetCore.TestHost now TFM-conditional for net9.0 and net10.0 (samples)
+
9.0.0
+========================
BREAKING:
* Drop .NET 5 support
@@ -26,7 +61,7 @@ DEPENDENCIES:
8.0.1
========================
* .NET 6 support (upgrade from RC to release)
-
+
8.0.0
========================
@@ -215,7 +250,7 @@ S#arp Architecture 2.0.3 for Visual Studio 2010
Issues:
* #42: Show the expected/requested TResult in the exception message (Thanks Joel Purra @joelpurra).
-* #41: Decorated the attribute classes with the AttributeUsageAttribute, to restrict usage of the attributes to their intended targets. (Thanks Sandor Drienhuizen @sandord)
+* #41: Decorated the attribute classes with the AttributeUsageAttribute, to restrict usage of the attributes to their intended targets. (Thanks Sandor Drieënhuizen @sandord)
-------
diff --git a/appveyor.yml b/appveyor.yml
index 239b6541d..f6f2c8982 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,8 +7,8 @@ nuget:
disable_publish_on_pr: true
image:
- - Ubuntu2204
- - Visual Studio 2022
+ - Ubuntu2404
+ - Visual Studio 2026
environment:
CAKE_SETTINGS_SKIPVERIFICATION: true
@@ -25,11 +25,11 @@ matrix:
for:
-# Linux build
+ # Linux build
-
matrix:
only:
- - image: Ubuntu2204
+ - image: Ubuntu2404
clone_folder: ~/work/sharp-arch
@@ -48,7 +48,7 @@ for:
-
matrix:
only:
- - image: Visual Studio 2022
+ - image: Visual Studio 2026
clone_folder: c:\work\sharp-arch
@@ -56,21 +56,23 @@ for:
- c:\tmp\cake\tools -> build\*.cake, appveyor.yml
services:
- - mssql2019
+ - mssql2025
install:
- ps: dotnet --info
- ps: ./mssql-setup.ps1
- - ps: dotnet tool install Cake.Tool --version 4.0.0 --global
- - ps: dotnet tool install coveralls.net --version 4.0.1 --global
+ - ps: dotnet tool install Cake.Tool --version 6.1.0 --global
+ - ps: New-Item -ItemType Directory -Force -Path C:\tools | Out-Null
+ - ps: Invoke-WebRequest -Uri "https://github.com/coverallsapp/coverage-reporter/releases/latest/download/coveralls-windows.exe" -OutFile "C:\tools\coveralls-windows.exe"
build_script:
- ps: dotnet cake
environment:
- coveralls_repo_token:
- secure: FgZlD1O2ilcGB6nF7cIgNcF6f8wWJk//ish6EG800QfuMd0y3BQWUXkl9u7TB4yG
- appveyor_cache_entry_zip_args: -t7z -m0=lzma -mx=5 -ms=on
+ COVERALLS_REPO_TOKEN:
+ secure: Yf2HHc4ObFerbXits621royUrbahaAPNsUHdvJVnl7y1tTG/chj5wXKDnk0V71aP
+ COVERALLS_REPORTER_EXE: C:\tools\coveralls-windows.exe
+ APPVEYOR_CACHE_ENTRY_ZIP_ARGS: -t7z -m0=lzma -mx=5 -ms=on
CAKE_PATHS_TOOLS: c:\tmp\cake\tools\
GITHUB_TOKEN:
secure: 21bpvob1W59q2H3tgdQ4c6pkZVzTTZTTWUNkkLJ/1bo2xY4K160RtHw08cBRsQOd
diff --git a/build.cake b/build.cake
index 77bf2a8fc..d9c5ca551 100644
--- a/build.cake
+++ b/build.cake
@@ -11,10 +11,11 @@ ProjectSettings settings = new ProjectSettings("sharparchitecture", "Sharp-Archi
{
CodeCoverage =
{
- IncludeFilter = "+[SharpArch.*]*"
+ // Coverlet filter syntax: [Assembly]TypeName
+ IncludeFilter = new List { "[SharpArch.*]*" }
}
};
-settings.CodeCoverage.ExcludeFilter += ";-[Suteki*]*;-[TransactionAttribute*]*";
+settings.CodeCoverage.ExcludeFilter.AddRange(new[] { "[Suteki*]*", "[TransactionAttribute*]*" });
// SETUP / TEARDOWN
diff --git a/global.json b/global.json
new file mode 100644
index 000000000..512142d2b
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/mssql-setup.ps1 b/mssql-setup.ps1
index abd225390..e7224af99 100644
--- a/mssql-setup.ps1
+++ b/mssql-setup.ps1
@@ -5,7 +5,7 @@
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
$serverName = $env:COMPUTERNAME
-$instanceName = 'SQL2019'
+$instanceName = 'SQL2025'
$smo = 'Microsoft.SqlServer.Management.Smo.'
$wmi = new-object ($smo + 'Wmi.ManagedComputer')