Weasel.Sqlite.CommandBuilder derives from CommandBuilderBase<SqliteCommand, SqliteParameter, SqliteType> but does not declare the non-generic Weasel.Core.ICommandBuilder, so nothing that targets the neutral contract can be handed a SQLite command builder.
That contract is what every Weasel.Storage closed-shape operation configures itself against:
public interface IStorageOperation : Core.IStorageOperation
{
void ConfigureCommand(ICommandBuilder builder, IStorageSession session);
}
The practical effect is that no Weasel.Storage document or event operation can be executed against SQLite at all — there is no way to construct the builder argument. Found while building Fisher (the SQLite event store) on Weasel.Storage:
error CS1503: Argument 1: cannot convert from 'Weasel.Sqlite.CommandBuilder'
to 'Weasel.Core.ICommandBuilder'
Confirmed against the published 9.23.1 assembly
Reflecting over Weasel.Sqlite 9.23.1+905a1f780bba4f07acabab560057a345ae1cf55b:
Sqlite CommandBuilder implements ICommandBuilder: False
interfaces: Weasel.Core.ICommandBuilder`1[Microsoft.Data.Sqlite.SqliteCommand]
Missing relative to the non-generic interface:
| Member |
Status on Weasel.Sqlite.CommandBuilder |
string TenantId { get; set; } |
absent |
void AppendParameters(params object[]) |
absent |
DbParameter AppendParameter(object) |
absent — the four inherited AppendParameter overloads all return void, so none satisfies the interface |
IGroupedParameterBuilder CreateGroupedParameterBuilder(char?) |
absent |
Everything else the interface needs (Append, AppendWithDbParameters, AddParameters, StartNewCommand, LastParameterName) is already inherited from CommandBuilderBase and needs no new code.
Scope
Weasel.Sqlite and Weasel.MySql are the two outliers; the rest picked this up, presumably in #327:
Weasel.Postgresql: ... , ICommandBuilder
Weasel.SqlServer: ... , Weasel.Core.ICommandBuilder
Weasel.Oracle: ... , ICommandBuilder
Weasel.MySql: ... (none)
Weasel.Sqlite: ... (none)
MySql presumably has the same blocker for any future Weasel.Storage consumer, though only SQLite is blocking anything today.
Suggested fix
Port Weasel.SqlServer.CommandBuilder's four members verbatim — they are dialect-neutral. StartNewCommand can stay the inherited no-op behaviour for the same reason as SqlClient: Microsoft.Data.Sqlite executes several semicolon-separated statements from one command.
public class CommandBuilder
: CommandBuilderBase<SqliteCommand, SqliteParameter, SqliteType>, Weasel.Core.ICommandBuilder
{
public string TenantId { get; set; } = StorageConstants.DefaultTenantId;
public void AppendParameters(params object[] parameters)
{
if (parameters.Length == 0)
throw new ArgumentOutOfRangeException(nameof(parameters),
"Must be at least one parameter value, but got " + parameters.Length);
AppendParameter(parameters[0]);
for (var i = 1; i < parameters.Length; i++)
{
Append(", ");
AppendParameter(parameters[i]);
}
}
public new DbParameter AppendParameter(object value)
{
base.AppendParameter(value);
return _command.Parameters[^1];
}
public Weasel.Core.IGroupedParameterBuilder CreateGroupedParameterBuilder(char? separator = null)
=> new Weasel.Core.GroupedParameterBuilder(this, separator);
public override void StartNewCommand()
{
// Microsoft.Data.Sqlite happily executes several statements from one command.
}
}
Fisher carries this today as a local FisherCommandBuilder shim and will delete it once a Weasel.Sqlite release includes the change.
A regression test asserting typeof(Weasel.Core.ICommandBuilder).IsAssignableFrom(typeof(CommandBuilder)) for each provider would keep the set from drifting again.
Weasel.Sqlite.CommandBuilderderives fromCommandBuilderBase<SqliteCommand, SqliteParameter, SqliteType>but does not declare the non-genericWeasel.Core.ICommandBuilder, so nothing that targets the neutral contract can be handed a SQLite command builder.That contract is what every
Weasel.Storageclosed-shape operation configures itself against:The practical effect is that no Weasel.Storage document or event operation can be executed against SQLite at all — there is no way to construct the builder argument. Found while building Fisher (the SQLite event store) on Weasel.Storage:
Confirmed against the published 9.23.1 assembly
Reflecting over
Weasel.Sqlite9.23.1+905a1f780bba4f07acabab560057a345ae1cf55b:Missing relative to the non-generic interface:
Weasel.Sqlite.CommandBuilderstring TenantId { get; set; }void AppendParameters(params object[])DbParameter AppendParameter(object)AppendParameteroverloads all returnvoid, so none satisfies the interfaceIGroupedParameterBuilder CreateGroupedParameterBuilder(char?)Everything else the interface needs (
Append,AppendWithDbParameters,AddParameters,StartNewCommand,LastParameterName) is already inherited fromCommandBuilderBaseand needs no new code.Scope
Weasel.SqliteandWeasel.MySqlare the two outliers; the rest picked this up, presumably in #327:MySql presumably has the same blocker for any future Weasel.Storage consumer, though only SQLite is blocking anything today.
Suggested fix
Port
Weasel.SqlServer.CommandBuilder's four members verbatim — they are dialect-neutral.StartNewCommandcan stay the inherited no-op behaviour for the same reason as SqlClient:Microsoft.Data.Sqliteexecutes several semicolon-separated statements from one command.Fisher carries this today as a local
FisherCommandBuildershim and will delete it once a Weasel.Sqlite release includes the change.A regression test asserting
typeof(Weasel.Core.ICommandBuilder).IsAssignableFrom(typeof(CommandBuilder))for each provider would keep the set from drifting again.