Skip to content

Commit

Permalink
Use OUTPUT INTO for generated values in SqlServer to support tables w…
Browse files Browse the repository at this point in the history
…ith triggers

Add commandPosition argument to methods on IUpdateSqlGenerator to provide a unique id for a modification command in a batch

Fixes #1441
  • Loading branch information
AndriySvyryd committed Nov 25, 2015
1 parent 8c1e269 commit 2627626
Show file tree
Hide file tree
Showing 22 changed files with 527 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ protected override EntityTypeBuilder New(InternalEntityTypeBuilder builder)
/// </summary>
/// <param name="name"> The name of the base type. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual EntityTypeBuilder<TEntity> HasBaseType([NotNull] string name)
public new virtual EntityTypeBuilder<TEntity> HasBaseType([CanBeNull] string name)
=> (EntityTypeBuilder<TEntity>)base.HasBaseType(name);

/// <summary>
/// Sets the base type of this entity in an inheritance hierarchy.
/// </summary>
/// <param name="entityType"> The base type. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public new virtual EntityTypeBuilder<TEntity> HasBaseType([NotNull] Type entityType)
public new virtual EntityTypeBuilder<TEntity> HasBaseType([CanBeNull] Type entityType)
=> (EntityTypeBuilder<TEntity>)base.HasBaseType(entityType);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
<Compile Include="Storage\Internal\SqlServerSqlGenerator.cs" />
<Compile Include="Storage\Internal\SqlServerTypeMapper.cs" />
<Compile Include="Update\Internal\ISqlServerUpdateSqlGenerator.cs" />
<Compile Include="Update\Internal\ResultsGrouping.cs" />
<Compile Include="Update\Internal\SqlServerModificationCommandBatch.cs" />
<Compile Include="Update\Internal\SqlServerModificationCommandBatchFactory.cs" />
<Compile Include="Update\Internal\SqlServerUpdateSqlGenerator.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Utilities;

namespace Microsoft.Data.Entity.Storage.Internal
Expand All @@ -13,13 +12,13 @@ public class SqlServerSqlGenerator : RelationalSqlGenerator
{
public override string BatchSeparator => "GO" + Environment.NewLine + Environment.NewLine;

public override string EscapeIdentifier([NotNull] string identifier)
public override string EscapeIdentifier(string identifier)
=> Check.NotEmpty(identifier, nameof(identifier)).Replace("]", "]]");

public override string DelimitIdentifier([NotNull] string identifier)
public override string DelimitIdentifier(string identifier)
=> $"[{EscapeIdentifier(Check.NotEmpty(identifier, nameof(identifier)))}]";

protected override string GenerateLiteralValue([NotNull] byte[] value)
protected override string GenerateLiteralValue(byte[] value)
{
Check.NotNull(value, nameof(value));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ namespace Microsoft.Data.Entity.Update.Internal
{
public interface ISqlServerUpdateSqlGenerator : IUpdateSqlGenerator
{
SqlServerUpdateSqlGenerator.ResultsGrouping AppendBulkInsertOperation(
ResultsGrouping AppendBulkInsertOperation(
[NotNull] StringBuilder commandStringBuilder,
[NotNull] IReadOnlyList<ModificationCommand> modificationCommands);
[NotNull] IReadOnlyList<ModificationCommand> modificationCommands,
int commandPosition);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.Data.Entity.Update.Internal
{
public enum ResultsGrouping
{
OneResultSet,
OneCommandPerResultSet
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class SqlServerModificationCommandBatch : AffectedCountModificationComman
public SqlServerModificationCommandBatch(
[NotNull] IRelationalCommandBuilderFactory commandBuilderFactory,
[NotNull] ISqlGenerator sqlGenerator,
// ReSharper disable once SuggestBaseTypeForParameter
[NotNull] ISqlServerUpdateSqlGenerator updateSqlGenerator,
[NotNull] IRelationalValueBufferFactoryFactory valueBufferFactoryFactory,
[CanBeNull] int? maxBatchSize)
Expand All @@ -43,6 +44,8 @@ public SqlServerModificationCommandBatch(
_maxBatchSize = Math.Min(maxBatchSize ?? int.MaxValue, MaxRowCount);
}

protected new virtual ISqlServerUpdateSqlGenerator UpdateSqlGenerator => (ISqlServerUpdateSqlGenerator)base.UpdateSqlGenerator;

protected override bool CanAddCommand(ModificationCommand modificationCommand)
{
if (_maxBatchSize <= ModificationCommands.Count)
Expand Down Expand Up @@ -115,10 +118,10 @@ private string GetBulkInsertCommandText(int lastIndex)
}

var stringBuilder = new StringBuilder();
var grouping = ((ISqlServerUpdateSqlGenerator)UpdateSqlGenerator).AppendBulkInsertOperation(stringBuilder, _bulkInsertCommands);
var grouping = UpdateSqlGenerator.AppendBulkInsertOperation(stringBuilder, _bulkInsertCommands, lastIndex);
for (var i = lastIndex - _bulkInsertCommands.Count; i < lastIndex; i++)
{
ResultSetEnds[i] = grouping == SqlServerUpdateSqlGenerator.ResultsGrouping.OneCommandPerResultSet;
ResultSetEnds[i] = grouping == ResultsGrouping.OneCommandPerResultSet;
}

ResultSetEnds[lastIndex - 1] = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Data.Entity.Metadata;
using Microsoft.Data.Entity.Storage;
using Microsoft.Data.Entity.Utilities;

namespace Microsoft.Data.Entity.Update.Internal
{
public class SqlServerUpdateSqlGenerator : UpdateSqlGenerator, ISqlServerUpdateSqlGenerator
{
public SqlServerUpdateSqlGenerator([NotNull] ISqlGenerator sqlGenerator)
private readonly IRelationalTypeMapper _typeMapper;

public SqlServerUpdateSqlGenerator([NotNull] ISqlGenerator sqlGenerator,
[NotNull] IRelationalTypeMapper typeMapper)
: base(sqlGenerator)
{
_typeMapper = typeMapper;
}

public override void AppendInsertOperation(
StringBuilder commandStringBuilder,
ModificationCommand command)
public override void AppendInsertOperation(StringBuilder commandStringBuilder, ModificationCommand command, int commandPosition)
{
Check.NotNull(command, nameof(command));

AppendBulkInsertOperation(commandStringBuilder, new[] { command });
AppendBulkInsertOperation(commandStringBuilder, new[] { command }, commandPosition);
}

public virtual ResultsGrouping AppendBulkInsertOperation(
StringBuilder commandStringBuilder,
IReadOnlyList<ModificationCommand> modificationCommands)
IReadOnlyList<ModificationCommand> modificationCommands,
int commandPosition)
{
Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));
Check.NotEmpty(modificationCommands, nameof(modificationCommands));
Expand All @@ -51,10 +56,15 @@ public virtual ResultsGrouping AppendBulkInsertOperation(
var writeOperations = operations.Where(o => o.IsWrite).ToArray();
var readOperations = operations.Where(o => o.IsRead).ToArray();

if (readOperations.Length > 0)
{
AppendDeclareGeneratedTable(commandStringBuilder, readOperations, commandPosition);
}

AppendInsertCommandHeader(commandStringBuilder, name, schema, writeOperations);
if (readOperations.Length > 0)
{
AppendOutputClause(commandStringBuilder, readOperations);
AppendOutputClause(commandStringBuilder, readOperations, commandPosition);
}
AppendValuesHeader(commandStringBuilder, writeOperations);
AppendValues(commandStringBuilder, writeOperations);
Expand All @@ -65,9 +75,13 @@ public virtual ResultsGrouping AppendBulkInsertOperation(
}
commandStringBuilder.Append(SqlGenerator.BatchCommandSeparator).AppendLine();

if (readOperations.Length == 0)
if (readOperations.Length > 0)
{
AppendSelectGeneratedCommand(commandStringBuilder, readOperations, commandPosition);
}
else
{
AppendSelectAffectedCountCommand(commandStringBuilder, name, schema);
AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition);
}
}

Expand All @@ -76,9 +90,7 @@ public virtual ResultsGrouping AppendBulkInsertOperation(
: ResultsGrouping.OneResultSet;
}

public override void AppendUpdateOperation(
StringBuilder commandStringBuilder,
ModificationCommand command)
public override void AppendUpdateOperation(StringBuilder commandStringBuilder, ModificationCommand command, int commandPosition)
{
Check.NotNull(commandStringBuilder, nameof(commandStringBuilder));
Check.NotNull(command, nameof(command));
Expand All @@ -91,30 +103,80 @@ public override void AppendUpdateOperation(
var conditionOperations = operations.Where(o => o.IsCondition).ToArray();
var readOperations = operations.Where(o => o.IsRead).ToArray();

if (readOperations.Length > 0)
{
AppendDeclareGeneratedTable(commandStringBuilder, readOperations, commandPosition);
}
AppendUpdateCommandHeader(commandStringBuilder, name, schema, writeOperations);
if (readOperations.Length > 0)
{
AppendOutputClause(commandStringBuilder, readOperations);
AppendOutputClause(commandStringBuilder, readOperations, commandPosition);
}
AppendWhereClause(commandStringBuilder, conditionOperations);
commandStringBuilder.Append(SqlGenerator.BatchCommandSeparator).AppendLine();

if (readOperations.Length == 0)
if (readOperations.Length > 0)
{
AppendSelectAffectedCountCommand(commandStringBuilder, name, schema);
AppendSelectGeneratedCommand(commandStringBuilder, readOperations, commandPosition);
}
else
{
AppendSelectAffectedCountCommand(commandStringBuilder, name, schema, commandPosition);
}
}

private void AppendDeclareGeneratedTable(StringBuilder commandStringBuilder, ColumnModification[] readOperations, int commandPosition)
{
commandStringBuilder
.Append($"DECLARE @generated{commandPosition} TABLE (")
.AppendJoin(readOperations.Select(c =>
SqlGenerator.DelimitIdentifier(c.ColumnName) + " " + GetTypeNameForCopy(c.Property)))
.Append(")")
.Append(SqlGenerator.BatchCommandSeparator)
.AppendLine();
}

private string GetTypeNameForCopy(IProperty property)
{
var mapping = _typeMapper.GetMapping(property);
var typeName = mapping.DefaultTypeName;
if (property.IsConcurrencyToken
&& (typeName.Equals("rowversion", StringComparison.OrdinalIgnoreCase)
|| typeName.Equals("timestamp", StringComparison.OrdinalIgnoreCase)))
{
return property.IsNullable ? "varbinary(8)" : "binary(8)";
}

return typeName;
}

// ReSharper disable once ParameterTypeCanBeEnumerable.Local
private void AppendOutputClause(
StringBuilder commandStringBuilder,
IReadOnlyList<ColumnModification> operations)
=> commandStringBuilder
IReadOnlyList<ColumnModification> operations,
int commandPosition)
{
commandStringBuilder
.AppendLine()
.Append("OUTPUT ")
.AppendJoin(operations.Select(c => "INSERTED." + SqlGenerator.DelimitIdentifier(c.ColumnName)));

protected override void AppendSelectAffectedCountCommand(StringBuilder commandStringBuilder, string name, string schema)
commandStringBuilder
.AppendLine()
.Append($"INTO @generated{commandPosition}");
}

private void AppendSelectGeneratedCommand(StringBuilder commandStringBuilder, ColumnModification[] readOperations, int commandPosition)
{
commandStringBuilder
.Append("SELECT ")
.AppendJoin(readOperations.Select(c => SqlGenerator.DelimitIdentifier(c.ColumnName)))
.Append($" FROM @generated{commandPosition}")
.Append(SqlGenerator.BatchCommandSeparator)
.AppendLine();
}

protected override void AppendSelectAffectedCountCommand(StringBuilder commandStringBuilder, string name, string schema, int commandPosition)
=> Check.NotNull(commandStringBuilder, nameof(commandStringBuilder))
.Append("SELECT @@ROWCOUNT")
.Append(SqlGenerator.BatchCommandSeparator).AppendLine();
Expand All @@ -133,11 +195,5 @@ protected override void AppendIdentityWhereCondition(StringBuilder commandString
protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected)
=> Check.NotNull(commandStringBuilder, nameof(commandStringBuilder))
.Append("@@ROWCOUNT = " + expectedRowsAffected);

public enum ResultsGrouping
{
OneResultSet,
OneCommandPerResultSet
}
}
}
12 changes: 9 additions & 3 deletions src/EntityFramework.Relational/Update/IUpdateSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ public interface IUpdateSqlGenerator
{
string GenerateNextSequenceValueOperation([NotNull] string name, [CanBeNull] string schema);
void AppendBatchHeader([NotNull] StringBuilder commandStringBuilder);
void AppendDeleteOperation([NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command);
void AppendInsertOperation([NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command);
void AppendUpdateOperation([NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command);

void AppendDeleteOperation(
[NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command, int commandPosition);

void AppendInsertOperation(
[NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command, int commandPosition);

void AppendUpdateOperation(
[NotNull] StringBuilder commandStringBuilder, [NotNull] ModificationCommand command, int commandPosition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ protected virtual void UpdateCachedCommandText(int commandPosition)
switch (newModificationCommand.EntityState)
{
case EntityState.Added:
UpdateSqlGenerator.AppendInsertOperation(CachedCommandText, newModificationCommand);
UpdateSqlGenerator.AppendInsertOperation(CachedCommandText, newModificationCommand, commandPosition);
break;
case EntityState.Modified:
UpdateSqlGenerator.AppendUpdateOperation(CachedCommandText, newModificationCommand);
UpdateSqlGenerator.AppendUpdateOperation(CachedCommandText, newModificationCommand, commandPosition);
break;
case EntityState.Deleted:
UpdateSqlGenerator.AppendDeleteOperation(CachedCommandText, newModificationCommand);
UpdateSqlGenerator.AppendDeleteOperation(CachedCommandText, newModificationCommand, commandPosition);
break;
}

Expand Down
Loading

0 comments on commit 2627626

Please sign in to comment.