Database source generator
Add a reference to the Credfeto.Enumeration.Source.Generation package in each project you need the code generation
to run.
<ItemGroup>
<PackageReference
Include="Credfeto.Database.Source.Generation"
Version="1.1.1.1"
PrivateAssets="All"
ExcludeAssets="runtime" />
</ItemGroup>Reference the following package in the project that contains the attributes and interfaces that are used by the generator.
<ItemGroup>
<PackageReference
Include="Credfeto.Database.Interfaces"
Version="1.1.1.1"
PrivateAssets="All"
ExcludeAssets="runtime" />
</ItemGroup>Create a record (or class) whose property names match the database column names. Use [SqlFieldMap<TMapper, TType>]
on any property that requires custom type conversion:
using Credfeto.Database.Interfaces;
public sealed record Accounts(
int Id,
string Name,
[SqlFieldMap<AccountAddressMapper, AccountAddress>] AccountAddress Address,
DateTime LastModified
);Implement IMapper<T> to control how a custom type is read from / written to the database:
using System.Data;
using System.Data.Common;
using Credfeto.Database.Interfaces;
internal sealed class AccountAddressMapper : IMapper<AccountAddress>
{
public static AccountAddress MapFromDb(object value) =>
new() { Value = (string)value };
public static void MapToDb(AccountAddress value, DbParameter parameter)
{
parameter.Value = value.Value;
parameter.DbType = DbType.String;
parameter.Size = 100;
}
}Create an internal static partial class and annotate each method with [SqlObjectMap]. The generator fills
in the implementation at compile time:
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Credfeto.Database.Interfaces;
internal static partial class DatabaseWrapper
{
// Table-valued function — returns multiple rows
[SqlObjectMap(name: "schema.account_getall",
sqlObjectType: SqlObjectType.TABLE_FUNCTION,
sqlDialect: SqlDialect.GENERIC)]
public static partial ValueTask<IReadOnlyList<Accounts>> GetAllAsync(
DbConnection connection,
[SqlFieldMap<AccountAddressMapper, AccountAddress>] AccountAddress address,
CancellationToken cancellationToken);
// Stored procedure — no return value
[SqlObjectMap(name: "schema.account_insert",
sqlObjectType: SqlObjectType.STORED_PROCEDURE,
sqlDialect: SqlDialect.GENERIC)]
public static partial ValueTask InsertAsync(
DbConnection connection,
string name,
[SqlFieldMap<AccountAddressMapper, AccountAddress>] AccountAddress address,
CancellationToken cancellationToken);
// Scalar function — returns a single value
[SqlObjectMap(name: "schema.get_meaning_of_life",
sqlObjectType: SqlObjectType.SCALAR_FUNCTION,
sqlDialect: SqlDialect.GENERIC)]
public static partial ValueTask<int> GetMeaningOfLifeAsync(
DbConnection connection,
CancellationToken cancellationToken);
}Supported SqlObjectType values: TABLE_FUNCTION, STORED_PROCEDURE, SCALAR_FUNCTION.
Supported SqlDialect values: GENERIC, MICROSOFT_SQL_SERVER.
For each annotated method the generator emits a complete implementation in a *.generated.cs file. For example,
GetAllAsync above produces code that:
- builds a
DbCommandwith the appropriate SQL (SELECT … FROM schema.account_getall(@address)), - maps each input parameter (calling
AccountAddressMapper.MapToDbfor custom types), - executes the reader and maps each result row back to an
Accountsrecord (callingAccountAddressMapper.MapFromDbfor custom types), - returns the results as
IReadOnlyList<Accounts>.
You never write this boilerplate by hand — the generator keeps it in sync with your method signatures automatically.
Add the following to the csproj file:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<!-- Don't include the output from a previous source generator execution into future runs; the */** trick here ensures that there's
at least one subdirectory, which is our key that it's coming from a source generator as opposed to something that is coming from
some other tool. -->
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/*/**/*.cs" />
</ItemGroup>| Branch | Status |
|---|---|
| main | |
| release |
View changelog