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

Co-authored-by: Andriy Svyryd <[email protected]>
  • Loading branch information
roji and AndriySvyryd committed Aug 13, 2022
1 parent 5e91dc3 commit a1478eb
Show file tree
Hide file tree
Showing 73 changed files with 2,701 additions and 460 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,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
10 changes: 10 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,16 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy
_ => new Dictionary<string, IProperty>()
};

if (mappingStrategy == RelationalAnnotationNames.TptMappingStrategy
&& storeObjectIdentifier.StoreObjectType == StoreObjectType.InsertStoredProcedure
&& entityType.BaseType?.GetInsertStoredProcedure() != null)
{
foreach (var property in primaryKey.Properties)
{
storeGeneratedProperties.Remove(property.Name);
}
}

var resultColumnNames = new HashSet<string>();
foreach (var resultColumn in sproc.ResultColumns)
{
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public interface IStoreStoredProcedureResultColumn : IColumnBase
/// </summary>
new IReadOnlyList<IStoredProcedureResultColumnMapping> PropertyMappings { get; }

/// <summary>
/// Gets the 0-based position of the result column in the declaring stored procedure's result set.
/// </summary>
int Position { get; }

/// <summary>
/// Returns the property mapping for the given entity type.
/// </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
27 changes: 22 additions & 5 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,9 @@ private static void AddStoredProcedures(
var updateStoredProcedureMappings = new List<StoredProcedureMapping>();

var mappingStrategy = entityType.GetMappingStrategy();
var isTpt = mappingStrategy == RelationalAnnotationNames.TptMappingStrategy;
var isTpc = mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy;
var isTph = mappingStrategy == RelationalAnnotationNames.TphMappingStrategy;
while (mappedType != null)
{
var includesDerivedTypes = !isTpc && mappedType == entityType;
Expand Down Expand Up @@ -990,10 +992,12 @@ private static void AddStoredProcedures(

if (tableMapping != null)
{
Check.DebugAssert(tableMapping.InsertStoredProcedureMapping == null,
"Expected sproc mapping to be unique");
tableMapping.InsertStoredProcedureMapping = insertProcedureMapping;
}
}
else if (entityType == mappedType)
else if (entityType == mappedType && !isTpt)
{
insertStoredProcedureMappings = null;
}
Expand All @@ -1014,10 +1018,12 @@ private static void AddStoredProcedures(

if (tableMapping != null)
{
Check.DebugAssert(tableMapping.DeleteStoredProcedureMapping == null,
"Expected sproc mapping to be unique");
tableMapping.DeleteStoredProcedureMapping = deleteProcedureMapping;
}
}
else if (entityType == mappedType)
else if (entityType == mappedType && !isTpt)
{
deleteStoredProcedureMappings = null;
}
Expand All @@ -1038,15 +1044,17 @@ private static void AddStoredProcedures(

if (tableMapping != null)
{
Check.DebugAssert(tableMapping.UpdateStoredProcedureMapping == null,
"Expected sproc mapping to be unique");
tableMapping.UpdateStoredProcedureMapping = updateProcedureMapping;
}
}
else if (entityType == mappedType)
else if (entityType == mappedType && !isTpt)
{
updateStoredProcedureMappings = null;
}

if (isTpc || mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
if (isTpc || isTph)
{
break;
}
Expand Down Expand Up @@ -1169,13 +1177,16 @@ private static StoredProcedureMapping CreateStoredProcedureMapping(
columnMappings.Add(columnMapping);
}

position = -1;
foreach (var resultColumn in storedProcedure.ResultColumns)
{
position++;
if (resultColumn.PropertyName == null)
{
GetOrCreateStoreStoredProcedureResultColumn(
resultColumn,
null,
position,
storeStoredProcedure,
identifier,
relationalTypeMappingSource);
Expand All @@ -1199,6 +1210,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping(
GetOrCreateStoreStoredProcedureResultColumn(
resultColumn,
derivedProperty,
position,
storeStoredProcedure,
identifier,
relationalTypeMappingSource);
Expand All @@ -1213,6 +1225,7 @@ private static StoredProcedureMapping CreateStoredProcedureMapping(
var column = GetOrCreateStoreStoredProcedureResultColumn(
resultColumn,
property,
position,
storeStoredProcedure,
identifier,
relationalTypeMappingSource);
Expand Down Expand Up @@ -1320,6 +1333,7 @@ static StoreStoredProcedureParameter GetOrCreateStoreStoredProcedureParameter(
static StoreStoredProcedureResultColumn GetOrCreateStoreStoredProcedureResultColumn(
IStoredProcedureResultColumn resultColumn,
IProperty? property,
int position,
StoreStoredProcedure storeStoredProcedure,
StoreObjectIdentifier identifier,
IRelationalTypeMappingSource relationalTypeMappingSource)
Expand All @@ -1334,13 +1348,16 @@ static StoreStoredProcedureResultColumn GetOrCreateStoreStoredProcedureResultCol
column = new StoreStoredProcedureResultColumn(
name,
typeMapping.StoreType,
storeStoredProcedure);
position,
storeStoredProcedure,
typeMapping);
}
else
{
column = new StoreStoredProcedureResultColumn(
name,
property.GetColumnType(identifier),
position,
storeStoredProcedure)
{
IsNullable = property.IsColumnNullable(identifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ public class StoreStoredProcedureResultColumn
public StoreStoredProcedureResultColumn(
string name,
string type,
int position,
StoreStoredProcedure storedProcedure,
RelationalTypeMapping? storeTypeMapping = null)
: base(name, type, storedProcedure)
{
Position = position;
_storeTypeMapping = storeTypeMapping;
}

Expand All @@ -38,7 +40,15 @@ public StoreStoredProcedureResultColumn(
/// </summary>
public virtual StoreStoredProcedure StoredProcedure
=> (StoreStoredProcedure)Table;


/// <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 int Position { 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
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 @@ -996,6 +996,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
Loading

0 comments on commit a1478eb

Please sign in to comment.