Some updates to the migration/update sql. Main change is if we are doing an INSERT, if there is no values to be read back we don't have to follow it up with a 'SELECT @@ROWCOUNT'

pull/175/head
Christopher Jolly 2 years ago
parent c73f4a3013
commit 559cdc6d3c

@ -16,6 +16,9 @@ using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Storage;
using EntityFrameworkCore.Jet.Utilities;
using Microsoft.Extensions.DependencyInjection;
using System.Text;
using EntityFrameworkCore.Jet.Update.Internal;
using Microsoft.EntityFrameworkCore.Update;
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Migrations
@ -38,22 +41,25 @@ namespace Microsoft.EntityFrameworkCore.Migrations
private IReadOnlyList<MigrationOperation> _operations = null!;
private RelationalTypeMapping _stringTypeMapping;
private readonly ICommandBatchPreparer _commandBatchPreparer;
/// <summary>
/// Creates a new <see cref="JetMigrationsSqlGenerator" /> instance.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this service. </param>
/// <param name="migrationsAnnotations"> Provider-specific Migrations annotations to use. </param>
/// <param name="options"> Provider-specific options. </param>
/// <param name="commandBatchPreparer">The command batch preparer.</param>
public JetMigrationsSqlGenerator(
[NotNull] MigrationsSqlGeneratorDependencies dependencies,
[NotNull] IMigrationsAnnotationProvider migrationsAnnotations,
[NotNull] IJetOptions options)
[NotNull] IJetOptions options,
ICommandBatchPreparer commandBatchPreparer)
: base(dependencies)
{
_migrationsAnnotations = migrationsAnnotations;
_options = options;
_stringTypeMapping = dependencies.TypeMappingSource.FindMapping(typeof(string))!;
_commandBatchPreparer = commandBatchPreparer;
}
/// <summary>
@ -704,6 +710,41 @@ namespace Microsoft.EntityFrameworkCore.Migrations
}
}
protected override void Generate(
InsertDataOperation operation,
IModel? model,
MigrationCommandListBuilder builder,
bool terminate = true)
{
var sqlBuilder = new StringBuilder();
var modificationCommands = GenerateModificationCommands(operation, model).ToList();
var updateSqlGenerator = (IJetUpdateSqlGenerator)Dependencies.UpdateSqlGenerator;
foreach (var batch in _commandBatchPreparer.CreateCommandBatches(modificationCommands, moreCommandSets: true))
{
updateSqlGenerator.AppendBulkInsertOperation(sqlBuilder, batch.ModificationCommands, commandPosition: 0);
}
if (Options.HasFlag(MigrationsSqlGenerationOptions.Idempotent))
{
builder
.Append("EXEC('")
.Append(sqlBuilder.ToString().TrimEnd('\n', '\r', ';').Replace("'", "''"))
.Append("')")
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
}
else
{
builder.Append(sqlBuilder.ToString());
}
if (terminate)
{
builder.EndCommand();
}
}
protected override void ColumnDefinition(
string? schema,
string table,

@ -93,9 +93,11 @@ namespace EntityFrameworkCore.Jet.Update.Internal
ResultSetMappings.Add(resultSetMapping);
}
if (resultSetMapping != ResultSetMapping.NoResults)
// All result mappings are marked as "not last", mark the last one as "last".
if (resultSetMapping.HasFlag(ResultSetMapping.HasResultRow))
{
ResultSetMappings[^1] = ResultSetMapping.LastInResultSet;
ResultSetMappings[^1] &= ~ResultSetMapping.NotLastInResultSet;
ResultSetMappings[^1] |= ResultSetMapping.LastInResultSet;
}
}
@ -105,33 +107,39 @@ namespace EntityFrameworkCore.Jet.Update.Internal
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override void AddCommand(IReadOnlyModificationCommand modificationCommand)
{
if (modificationCommand.EntityState == EntityState.Added && modificationCommand.StoreStoredProcedure is null)
public override bool TryAddCommand(IReadOnlyModificationCommand modificationCommand)
{
// If there are any pending bulk insert commands and the new command is incompatible with them (not an insert, insert into a
// separate table..), apply the pending commands.
if (_pendingBulkInsertCommands.Count > 0
&& !CanBeInsertedInSameStatement(_pendingBulkInsertCommands[0], modificationCommand))
&& (modificationCommand.EntityState != EntityState.Added
|| modificationCommand.StoreStoredProcedure is not null
|| !CanBeInsertedInSameStatement(_pendingBulkInsertCommands[0], modificationCommand)))
{
// The new Add command cannot be added to the pending bulk insert commands (e.g. different table).
// Write out the pending commands before starting a new pending chain.
ApplyPendingBulkInsertCommands();
_pendingBulkInsertCommands.Clear();
}
return base.TryAddCommand(modificationCommand);
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override void AddCommand(IReadOnlyModificationCommand modificationCommand)
{
// TryAddCommand above already applied any pending commands if the new command is incompatible with them.
// So if the new command is an insert, just append it to pending, otherwise do the regular add logic.
if (modificationCommand is { EntityState: EntityState.Added, StoreStoredProcedure: null })
{
_pendingBulkInsertCommands.Add(modificationCommand);
AddParameters(modificationCommand);
}
else
{
// If we have any pending bulk insert commands, write them out before the next non-Add command
if (_pendingBulkInsertCommands.Count > 0)
{
// Note that we don't care about the transactionality of the bulk insert SQL, since there's the additional non-Add
// command coming right afterwards, and so a transaction is required in any case.
ApplyPendingBulkInsertCommands();
_pendingBulkInsertCommands.Clear();
}
base.AddCommand(modificationCommand);
}
}

@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using System.Text;
using EntityFrameworkCore.Jet.Metadata;
using EntityFrameworkCore.Jet.Utilities;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
@ -34,6 +35,17 @@ namespace EntityFrameworkCore.Jet.Update.Internal
{
}
public override ResultSetMapping AppendInsertOperation(StringBuilder commandStringBuilder, IReadOnlyModificationCommand command,
int commandPosition, out bool requiresTransaction)
{
//No database columns need to be read back
if (command.ColumnModifications.All(o => !o.IsRead))
{
return AppendInsertReturningOperation(commandStringBuilder, command, commandPosition, out requiresTransaction);
}
return base.AppendInsertOperation(commandStringBuilder, command, commandPosition, out requiresTransaction);
}
public ResultSetMapping AppendBulkInsertOperation(StringBuilder commandStringBuilder, IReadOnlyList<IReadOnlyModificationCommand> modificationCommands, int commandPosition, out bool requiresTransaction)
{
var firstCommand = modificationCommands[0];

@ -95,7 +95,6 @@ WHERE @@ROWCOUNT = 1 AND `Id` = @@identity;
INSERT INTO `MeterReadingDetails` (`Id`, `CurrentRead`, `PreviousRead`)
VALUES (@p1, @p2, @p3);
SELECT @@ROWCOUNT;
""",
//
"""

@ -643,26 +643,24 @@ namespace EntityFrameworkCore.Jet.FunctionalTests
+ _eol
+ " `NotFigTime` datetime NOT NULL,"
+ _eol
+ " `ToEat` tinyint NOT NULL,"
+ " `ToEat` byte NOT NULL,"
+ _eol
+ " `OrNothing` float NOT NULL,"
+ " `OrNothing` double NOT NULL,"
+ _eol
+ " `Fuse` smallint NOT NULL,"
+ _eol
+ " `WayRound` bigint NOT NULL,"
+ " `WayRound` decimal(20,0) NOT NULL,"
+ _eol
+ " `On` real NOT NULL,"
+ " `On` single NOT NULL,"
+ _eol
+ " `AndChew` varbinary(max) NULL,"
+ " `AndChew` longbinary NULL,"
+ _eol
+ " `AndRow` rowversion NULL,"
+ " `AndRow` varbinary(8) NULL,"
+ _eol
+ " CONSTRAINT `PK_Blogs` PRIMARY KEY (`Key1`, `Key2`)"
+ _eol
+ ");"
+ _eol
+ "GO"
+ _eol
+ _eol
+ _eol,
script);

@ -312,7 +312,7 @@ CREATE INDEX `IX_Person_Name` ON `Person` (`Name`);
AssertSql(
@"ALTER TABLE `Person` ALTER COLUMN `Id` DROP DEFAULT;
ALTER TABLE `Person` ALTER COLUMN `Id` integer NOT NULL;
ALTER TABLE `Person` ALTER COLUMN `Id` decimal(20,0) NOT NULL;
");
}
@ -694,8 +694,9 @@ VALUES ('John', 'Snow');
AssertSql(
@"INSERT INTO `People` (`First Name`)
VALUES ('John'),
('Daenerys');
VALUES ('John');
INSERT INTO `People` (`First Name`)
VALUES ('Daenerys');
");
}
@ -976,30 +977,18 @@ SELECT @@ROWCOUNT;
MigrationsSqlGenerationOptions.Idempotent);
AssertSql(
@"CREATE INDEX `IX_Table1_Column1` ON `Table1` (`Column1`) WHERE [Column1] IS NOT NULL;
@"CREATE INDEX `IX_Table1_Column1` ON `Table1` (`Column1`) WITH [Column1] IS NOT NULL;
");
}
[ConditionalFact]
public virtual void CreateIndex_generates_exec_when_legacy_filter_and_idempotent()
{
Generate(
modelBuilder =>
[ConditionalTheory(Skip = "No sequences")]
public override void Sequence_restart_operation(long? startsAt)
{
modelBuilder
.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0")
.Entity("Table1").Property<int?>("Column1");
},
migrationBuilder => migrationBuilder.CreateIndex(
name: "IX_Table1_Column1",
table: "Table1",
column: "Column1",
unique: true),
MigrationsSqlGenerationOptions.Idempotent);
base.Sequence_restart_operation(startsAt);
AssertSql(
@"CREATE UNIQUE INDEX `IX_Table1_Column1` ON `Table1` (`Column1`) WHERE `Column1` IS NOT NULL';
");
var expectedSql = startsAt.HasValue
? @$"ALTER SEQUENCE [dbo].[TestRestartSequenceOperation] RESTART WITH {startsAt};"
: @"ALTER SEQUENCE [dbo].[TestRestartSequenceOperation] RESTART;";
AssertSql(expectedSql);
}
[ConditionalFact]

@ -159,7 +159,6 @@ WHERE ([c].[Capacity] IS NOT NULL) AND ([c].[FuelType] IS NOT NULL)
INSERT INTO `LicensedOperators` (`VehicleName`, `LicenseType`)
VALUES (@p0, @p1);
SELECT @@ROWCOUNT;
"""
,
"""

@ -132,11 +132,8 @@ OUTPUT INSERTED.[Id], INSERTED.[Computed], i._Position;
"""
INSERT INTO `Ducks` (`Id`, `Name`, `Quacks`, `ConcurrencyToken`)
VALUES (@p0, @p1, @p2, @p3);
SELECT @@ROWCOUNT;
INSERT INTO `Ducks` (`Id`, `Name`, `Quacks`, `ConcurrencyToken`)
VALUES (@p0, @p1, @p2, @p3);
SELECT @@ROWCOUNT;
""",
stringBuilder.ToString());
Assert.Equal(ResultSetMapping.NoResults, grouping);
@ -179,11 +176,8 @@ INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]);
"""
INSERT INTO `Ducks`
DEFAULT VALUES;
SELECT @@ROWCOUNT;
INSERT INTO `Ducks`
DEFAULT VALUES;
SELECT @@ROWCOUNT;
""";
AssertBaseline(expectedText, stringBuilder.ToString());
Assert.Equal(ResultSetMapping.NoResults, grouping);

@ -44,7 +44,6 @@ namespace EntityFrameworkCore.Jet
INSERT INTO `Cookie` (`CookieId`, `Name`)
VALUES (1, 'Basic');
SELECT @@ROWCOUNT;
SELECT `c`.`CookieId`, `c`.`BestServedBefore`, `c`.`Name`
FROM `Cookie` AS `c`");
@ -124,15 +123,12 @@ CREATE TABLE `CookieBackery` (
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
SELECT @@ROWCOUNT;
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
SELECT @@ROWCOUNT;
INSERT INTO `CookieBackery` (`BackeryId`, `CookieId`)
VALUES (1, 1);
SELECT @@ROWCOUNT;
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);
@ -216,15 +212,12 @@ CREATE TABLE `CookieBackery` (
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
SELECT @@ROWCOUNT;
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
SELECT @@ROWCOUNT;
INSERT INTO `CookieBackery` (`BackeryId`, `CookieId`)
VALUES (1, 1);
SELECT @@ROWCOUNT;
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);
@ -314,15 +307,12 @@ CREATE TABLE `CookieBackery` (
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
SELECT @@ROWCOUNT;
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
SELECT @@ROWCOUNT;
INSERT INTO `CookieBackery` (`BackeryId`, `CookieId`)
VALUES (1, 1);
SELECT @@ROWCOUNT;
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);

Loading…
Cancel
Save