Skip to content

Commit

Permalink
Implement stored procedure update mapping
Browse files Browse the repository at this point in the history
Closes dotnet#245
Closes dotnet#28435
  • Loading branch information
roji committed Aug 9, 2022
1 parent 5a0853b commit 1a505b8
Show file tree
Hide file tree
Showing 59 changed files with 2,074 additions and 408 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,7 @@ private static bool IsOptionalSharingDependent(

return optional ?? (entityType.BaseType != null && entityType.FindDiscriminatorProperty() != null);
}

/// <summary>
/// Returns the comment for the column this property is mapped to.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy
RelationalStrings.StoredProcedureKeyless(
entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
}

var properties = entityType.GetDeclaredProperties().ToDictionary(p => p.Name);
if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
{
Expand Down
8 changes: 0 additions & 8 deletions src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,6 @@ bool TryGetDefaultValue(out object? defaultValue)
=> PropertyMappings.First().Property
.GetCollation(StoreObjectIdentifier.Table(Table.Name, Table.Schema));

/// <summary>
/// Gets the <see cref="ValueComparer" /> for this column.
/// </summary>
/// <returns>The comparer.</returns>
ValueComparer ProviderValueComparer
=> PropertyMappings.First().Property
.GetProviderValueComparer();

/// <summary>
/// Returns the property mapping for the given entity type.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Relational/Metadata/IColumnBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public interface IColumnBase : IAnnotatable
/// </summary>
IReadOnlyList<IColumnMappingBase> PropertyMappings { get; }

/// <summary>
/// Gets the <see cref="ValueComparer" /> for this column.
/// </summary>
/// <returns>The comparer.</returns>
ValueComparer ProviderValueComparer
=> PropertyMappings.First().Property
.GetProviderValueComparer();

/// <summary>
/// Returns the property mapping for the given entity type.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public interface IStoreStoredProcedureParameter : IColumnBase
/// Gets the property mappings.
/// </summary>
new IReadOnlyList<IStoredProcedureParameterMapping> PropertyMappings { get; }

/// <summary>
/// Gets the direction of the parameter.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/Internal/JsonColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ bool IColumn.IsRowVersion
/// 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>
ValueComparer IColumn.ProviderValueComparer
ValueComparer IColumnBase.ProviderValueComparer
=> _providerValueComparer;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2073,7 +2073,9 @@ protected virtual void DiffData(
var anyColumnsModified = false;
foreach (var targetColumnModification in targetRow.ColumnModifications)
{
var targetColumn = targetColumnModification.Column!;
var targetColumnBase = targetColumnModification.Column!;
Check.DebugAssert(targetColumnBase is IColumn, "Non-IColumn columns not allowed");
var targetColumn = (IColumn)targetColumnBase;
var targetMapping = targetColumn.PropertyMappings.First();
var targetProperty = targetMapping.Property;

Expand Down Expand Up @@ -2281,7 +2283,7 @@ private IEnumerable<MigrationOperation> GetDataOperations(
Check.DebugAssert(forSource, "Delete using the target model");

var keyColumns = command.ColumnModifications.Where(col => col.IsKey)
.Select(c => c.Column!);
.Select(c => (IColumn)c.Column!);
var anyKeyColumnDropped = keyColumns.Any(c => diffContext.FindDrop(c) != null);

yield return new DeleteDataOperation
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,9 @@
<data name="StoredProcedureOutputParameterNotGenerated" xml:space="preserve">
<value>The property '{entityType}.{property}' is mapped to an output parameter of the stored procedure '{sproc}', but it is not configured as store-generated. Either configure it as store-generated or don't configure the parameter as output.</value>
</data>
<data name="StoredProcedureRowsAffectedNotPopulated" xml:space="preserve">
<value>Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure.</value>
</data>
<data name="StoredProcedureOverrideMismatch" xml:space="preserve">
<value>The property '{propertySpecification}' has specific configuration for the stored procedure '{sproc}', but it isn't mapped to a parameter or a result column on that stored procedure. Remove the specific configuration, or map an entity type that contains this property to '{sproc}'.</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Storage/IRelationalParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ public interface IRelationalParameter
/// </summary>
/// <param name="command">The command to add the parameter to.</param>
/// <param name="parameterValues">The map of parameter values</param>
void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?> parameterValues);
void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?>? parameterValues);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public RawRelationalParameter(
/// 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>
public override void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?> parameterValues)
public override void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?>? parameterValues)
=> AddDbParameter(command, _parameter);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ protected RelationalParameterBase(string invariantName)
/// 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>
public virtual void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?> parameterValues)
public virtual void AddDbParameter(DbCommand command, IReadOnlyDictionary<string, object?>? parameterValues)
{
if (parameterValues.TryGetValue(InvariantName, out var parameterValue))
if (parameterValues is not null && parameterValues.TryGetValue(InvariantName, out var parameterValue))
{
AddDbParameter(command, parameterValue);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;

namespace Microsoft.EntityFrameworkCore.Storage.Internal;

/// <summary>
Expand All @@ -21,12 +23,14 @@ public TypeMappedRelationalParameter(
string invariantName,
string name,
RelationalTypeMapping relationalTypeMapping,
bool? nullable)
bool? nullable,
ParameterDirection direction = ParameterDirection.Input)
: base(invariantName)
{
Name = name;
RelationalTypeMapping = relationalTypeMapping;
IsNullable = nullable;
Direction = direction;
}

/// <summary>
Expand All @@ -37,6 +41,14 @@ public TypeMappedRelationalParameter(
/// </summary>
public virtual string Name { get; }

/// <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>
public virtual ParameterDirection Direction { get; }

// internal for testing
internal RelationalTypeMapping RelationalTypeMapping { get; }

Expand All @@ -51,5 +63,5 @@ public TypeMappedRelationalParameter(
/// </summary>
public override void AddDbParameter(DbCommand command, object? value)
=> command.Parameters.Add(
RelationalTypeMapping.CreateParameter(command, Name, value, IsNullable));
RelationalTypeMapping.CreateParameter(command, Name, value, IsNullable, Direction));
}
15 changes: 2 additions & 13 deletions src/EFCore.Relational/Storage/RelationalCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -817,20 +817,9 @@ public virtual DbCommand CreateDbCommand(
command.CommandTimeout = (int)connection.CommandTimeout;
}

if (Parameters.Count > 0)
for (var i = 0; i < Parameters.Count; i++)
{
var parameterValues = parameterObject.ParameterValues;
if (parameterValues == null)
{
throw new InvalidOperationException(
RelationalStrings.MissingParameterValue(
Parameters[0].InvariantName));
}

for (var i = 0; i < Parameters.Count; i++)
{
Parameters[i].AddDbParameter(command, parameterValues);
}
Parameters[i].AddDbParameter(command, parameterObject.ParameterValues);
}

if (logCommandCreate)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Internal;

namespace Microsoft.EntityFrameworkCore.Storage;
Expand Down Expand Up @@ -114,19 +115,22 @@ public static IRelationalCommandBuilder AddParameter(
/// </param>
/// <param name="relationalTypeMapping">The relational type mapping for this parameter.</param>
/// <param name="nullable">A value indicating whether the parameter could contain a null value.</param>
/// <param name="direction">The parameter direction.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static IRelationalCommandBuilder AddParameter(
this IRelationalCommandBuilder commandBuilder,
string invariantName,
string name,
RelationalTypeMapping relationalTypeMapping,
bool? nullable)
bool? nullable,
ParameterDirection direction = ParameterDirection.Input)
=> commandBuilder.AddParameter(
new TypeMappedRelationalParameter(
invariantName,
name,
relationalTypeMapping,
nullable));
nullable,
direction));

/// <summary>
/// Adds a parameter that is ultimately represented as multiple <see cref="DbParameter" />s in the
Expand Down
16 changes: 7 additions & 9 deletions src/EFCore.Relational/Storage/RelationalGeometryTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,13 @@ private static RelationalTypeMappingParameters CreateRelationalTypeMappingParame
/// </summary>
protected virtual ValueConverter<TGeometry, TProvider>? SpatialConverter { get; }

/// <summary>
/// Creates a <see cref="DbParameter" /> with the appropriate type information configured.
/// </summary>
/// <param name="command">The command the parameter should be created on.</param>
/// <param name="name">The name of the parameter.</param>
/// <param name="value">The value to be assigned to the parameter.</param>
/// <param name="nullable">A value indicating whether the parameter should be a nullable type.</param>
/// <returns>The newly created parameter.</returns>
public override DbParameter CreateParameter(DbCommand command, string name, object? value, bool? nullable = null)
/// <inheritdoc />
public override DbParameter CreateParameter(
DbCommand command,
string name,
object? value,
bool? nullable = null,
ParameterDirection direction = ParameterDirection.Input)
{
var parameter = command.CreateParameter();
parameter.Direction = ParameterDirection.Input;
Expand Down
26 changes: 17 additions & 9 deletions src/EFCore.Relational/Storage/RelationalTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,29 +483,37 @@ protected virtual string ProcessStoreType(
/// <param name="name">The name of the parameter.</param>
/// <param name="value">The value to be assigned to the parameter.</param>
/// <param name="nullable">A value indicating whether the parameter should be a nullable type.</param>
/// <param name="direction">The direction of the parameter.</param>
/// <returns>The newly created parameter.</returns>
public virtual DbParameter CreateParameter(
DbCommand command,
string name,
object? value,
bool? nullable = null)
bool? nullable = null,
ParameterDirection direction = ParameterDirection.Input)
{
var parameter = command.CreateParameter();
parameter.Direction = ParameterDirection.Input;
parameter.Direction = direction;
parameter.ParameterName = name;

value = NormalizeEnumValue(value);

if (Converter != null)
if (direction.HasFlag(ParameterDirection.Input))
{
value = Converter.ConvertToProvider(value);
}
value = NormalizeEnumValue(value);

parameter.Value = value ?? DBNull.Value;
if (Converter != null)
{
value = Converter.ConvertToProvider(value);
}

parameter.Value = value ?? DBNull.Value;
}

if (nullable.HasValue)
{
Check.DebugAssert(nullable.Value || value != null, "Null value in a non-nullable parameter");
Check.DebugAssert(nullable.Value
|| !direction.HasFlag(ParameterDirection.Input)
|| value != null,
"Null value in a non-nullable input parameter");

parameter.IsNullable = nullable.Value;
}
Expand Down
Loading

0 comments on commit 1a505b8

Please sign in to comment.