Hi! I did some more digging on this issue, and the
UnpreparedExecute is not being set for commands involving migrations.
Here is the EFCore code for the
MigrateAsync method of the
Migrator class.
Code: Select all
public virtual async Task MigrateAsync(
string? targetMigration = null,
CancellationToken cancellationToken = default)
{
_logger.MigrateUsingConnection(this, _connection);
if (!await _historyRepository.ExistsAsync(cancellationToken).ConfigureAwait(false))
{
if (!await _databaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false))
{
await _databaseCreator.CreateAsync(cancellationToken).ConfigureAwait(false);
}
var command = _rawSqlCommandBuilder.Build(
_historyRepository.GetCreateScript());
await command.ExecuteNonQueryAsync(
new RelationalCommandParameterObject(
_connection,
null,
null,
_currentContext.Context,
_commandLogger, CommandSource.Migrations),
cancellationToken)
.ConfigureAwait(false);
}
var commandLists = GetMigrationCommandLists(
await _historyRepository.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false),
targetMigration);
foreach (var commandList in commandLists)
{
await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection, cancellationToken)
.ConfigureAwait(false);
}
}
Of our interest are the variables
_rawSqlCommandBuilder,
_historyRepository and
command .
The
_rawSqlCommandBuilder is the default EFCore implementation.
_historyRepository is the de Devart implementation, and
command is also the Devart implementation.
The call
_historyRepository.GetCreateScript() returns the following scritp:
Code: Select all
-- Script
CREATE SCHEMA IF NOT EXISTS "cbfb002c-2b85-4453-882a-40157fa6e11c";
CREATE TABLE "cbfb002c-2b85-4453-882a-40157fa6e11c"."__EFMigrationsHistory" (
"MigrationId" varchar(150) NOT NULL,
"ProductVersion" varchar(32) NOT NULL,
PRIMARY KEY ("MigrationId")
)
This is used as text for the command that will return from the call
_rawSqlCommandBuilder.Build, and when the command is executed it will generate an error because the script has two commands.
Now, using ILSpy to look the Devart implementation of the ReleationCommand (yes, I went that far):
Code: Select all
internal class v : cz
{
[CompilerGenerated]
private bool m_a;
public v(RelationalCommandBuilderDependencies A_0, string A_1, IReadOnlyList<IRelationalParameter> A_2)
: base(A_0, A_1, A_2)
{
}
public override DbCommand CreateDbCommand(RelationalCommandParameterObject A_0, Guid A_1, DbCommandMethod A_2)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
DbCommand dbCommand = base.CreateDbCommand(A_0, A_1, A_2);
((Devart.Data.PostgreSql.Entity.q)dbCommand).b(a());
return dbCommand;
}
[SpecialName]
[CompilerGenerated]
public bool a()
{
return this.m_a;
}
[SpecialName]
[CompilerGenerated]
public void a(bool A_0)
{
this.m_a = A_0;
}
}
This line
((Devart.Data.PostgreSql.Entity.q)dbCommand).b(a()); is suposed to set the
UnpreparedExecute, but the variable
m_a is never set, so when the command is executed the value is false.
To test if this was really the issue, I replaced the default implementation of the
IRawSqlCommandBuilder interface with my own implementation that just inherits from the default implemention and overrides the method
IRelationalCommand Build(string sql).
Here is my implementation:
Code: Select all
public class TnfRawSqlCommandBuilder : RawSqlCommandBuilder
{
public TnfRawSqlCommandBuilder(
IRelationalCommandBuilderFactory relationalCommandBuilderFactory,
ISqlGenerationHelper sqlGenerationHelper,
IParameterNameGeneratorFactory parameterNameGeneratorFactory)
: base(relationalCommandBuilderFactory, sqlGenerationHelper, parameterNameGeneratorFactory)
{
}
public override IRelationalCommand Build(string sql)
{
var command = base.Build(sql);
var commandType = command.GetType();
commandType.GetMethod("a", new[] { typeof(bool) }).Invoke(command, new object[] { true });
return command;
}
}
Here, through reflection, I call the method
a(bool A_0) passing
true to it. This in turn will set
UnpreparedExecute to
true when the actual command is created.
This made issue go away.
While this works, it's not a reliable solution. The source code of the class
Devart.Data.PostgreSql.Entity.v can change and the method
a can change its name.
I believe ither class
Devart.Data.PostgreSql.Entity.v or
Devart.Common.Entity.cz could get the
UnpreparedExecute parameter from the connection that is available on the
RelationalCommandParameterObject parameter of the method
CreateDbCommand.