Skip to content

Commit

Permalink
Implement SqlQuery<T>
Browse files Browse the repository at this point in the history
Resolves #11624
  • Loading branch information
smitpatel committed Aug 11, 2022
1 parent d6f1418 commit 1bcfa84
Show file tree
Hide file tree
Showing 28 changed files with 750 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

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

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -260,6 +261,27 @@ public static int ExecuteSqlRaw(
}
}

/// <summary>
/// .
/// </summary>
/// <param name="databaseFacade">.</param>
/// <param name="sql">.</param>
/// <param name="parameters">.</param>
/// <returns>.</returns>
public static IQueryable<TResult> SqlQuery<TResult>(
this DatabaseFacade databaseFacade,
[NotParameterized] string sql,
params object[] parameters)
{
Check.NotNull(sql, nameof(sql));
Check.NotNull(parameters, nameof(parameters));

var facadeDependencies = GetFacadeDependencies(databaseFacade);

return facadeDependencies.QueryProvider
.CreateQuery<TResult>(new SqlQueryRootExpression(typeof(TResult), sql, Expression.Constant(parameters)));
}

/// <summary>
/// Executes the given SQL against the database and returns the number of rows affected.
/// </summary>
Expand Down
240 changes: 120 additions & 120 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Large diffs are not rendered by default.

96 changes: 48 additions & 48 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,6 @@
<data name="FromSqlMissingColumn" xml:space="preserve">
<value>The required column '{column}' was not present in the results of a 'FromSql' operation.</value>
</data>
<data name="FromSqlNonComposable" xml:space="preserve">
<value>'FromSqlRaw' or 'FromSqlInterpolated' was called with non-composable SQL and with a query composing over it. Consider calling 'AsEnumerable' after the method to perform the composition on the client side.</value>
</data>
<data name="FunctionOverrideMismatch" xml:space="preserve">
<value>The property '{propertySpecification}' has specific configuration for the function '{function}', but it isn't mapped to a column on that function return. Remove the specific configuration, or map an entity type that contains this property to '{function}'.</value>
</data>
Expand Down Expand Up @@ -451,9 +448,6 @@
<data name="InvalidDerivedTypeInEntityProjection" xml:space="preserve">
<value>The specified entity type '{derivedType}' is not derived from '{entityType}'.</value>
</data>
<data name="InvalidFromSqlArguments" xml:space="preserve">
<value>A FromSqlExpression has an invalid arguments expression type '{expressionType}' or value type '{valueType}'.</value>
</data>
<data name="InvalidKeySelectorForGroupBy" xml:space="preserve">
<value>The grouping key '{keySelector}' is of type '{keyType}' which is not valid key.</value>
</data>
Expand All @@ -478,6 +472,48 @@
<data name="InvalidMinBatchSize" xml:space="preserve">
<value>The specified 'MinBatchSize' value '{value}' is not valid. It must be a positive number.</value>
</data>
<data name="InvalidRawSqlQueryArguments" xml:space="preserve">
<value>A RawSqlQuerylExpression has an invalid arguments expression type '{expressionType}' or value type '{valueType}'.</value>
</data>
<data name="JsonEntityMappedToDifferentViewThanOwner" xml:space="preserve">
<value>Entity '{jsonType}' is mapped to JSON and also mapped to a view '{viewName}', however it's owner '{ownerType}' is mapped to a different view '{ownerViewName}'. Every entity mapped to JSON must also map to the same view as it's owner.</value>
</data>
<data name="JsonEntityMultipleRootsMappedToTheSameJsonColumn" xml:space="preserve">
<value>Multiple owned root entities are mapped to the same JSON column '{column}' in table '{table}'. Each owned root entity must map to a different column.</value>
</data>
<data name="JsonEntityOwnedByNonJsonOwnedType" xml:space="preserve">
<value>Owned entity type '{nonJsonType}' is mapped to table '{table}' and contains JSON columns. This is currently not supported. All owned types containing a JSON column must be mapped to a JSON column themselves.</value>
</data>
<data name="JsonEntityReferencingRegularEntity" xml:space="preserve">
<value>Entity type '{jsonEntity}' is mapped to JSON and has navigation to a regular entity which is not the owner.</value>
</data>
<data name="JsonEntityWithDefaultValueSetOnItsProperty" xml:space="preserve">
<value>Setting default value on properties of an entity mapped to JSON is not supported. Entity: '{jsonEntity}', property: '{property}'.</value>
</data>
<data name="JsonEntityWithExplicitlyConfiguredJsonPropertyNameOnKey" xml:space="preserve">
<value>Key property '{keyProperty}' on JSON-mapped entity '{jsonEntity}' should not have JSON property name configured explicitly.</value>
</data>
<data name="JsonEntityWithExplicitlyConfiguredOrdinalKey" xml:space="preserve">
<value>Entity type '{jsonEntity}' is part of collection mapped to JSON and has it's ordinal key defined explicitly. Only implicitly defined ordinal keys are supported.</value>
</data>
<data name="JsonEntityWithIncorrectNumberOfKeyProperties" xml:space="preserve">
<value>Entity type '{jsonEntity}' has incorrect number of primary key properties. Expected number is: {expectedCount}, actual number is: {actualCount}.</value>
</data>
<data name="JsonEntityWithMultiplePropertiesMappedToSameJsonProperty" xml:space="preserve">
<value>Entity '{jsonEntity}' is mapped to JSON and it contains multiple properties or navigations which are mapped to the same JSON property '{property}'. Each property should map to a unique JSON property.</value>
</data>
<data name="JsonEntityWithNonTphInheritanceOnOwner" xml:space="preserve">
<value>Entity type '{rootType}' references entities mapped to JSON. Only '{tph}' inheritance is supported for those entities.</value>
</data>
<data name="JsonEntityWithOwnerNotMappedToTableOrView" xml:space="preserve">
<value>Entity type '{entity}' references entities mapped to JSON but is not itself mapped to a table or a view.This is not supported.</value>
</data>
<data name="JsonEntityWithTableSplittingIsNotSupported" xml:space="preserve">
<value>Table splitting is not supported for entities containing entities mapped to JSON.</value>
</data>
<data name="JsonPropertyNameShouldBeConfiguredOnNestedNavigation" xml:space="preserve">
<value>JSON property name should only be configured on nested owned navigations.</value>
</data>
<data name="KeylessMappingStrategy" xml:space="preserve">
<value>The mapping strategy '{mappingStrategy}' used for '{entityType}' is not supported for keyless entity types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.</value>
</data>
Expand Down Expand Up @@ -774,6 +810,9 @@
<data name="MappingFragmentMissingName" xml:space="preserve">
<value>Table name must be specified to configure a table-specific property mapping.</value>
</data>
<data name="MethodNeedsToBeImplementedInTheProvider" xml:space="preserve">
<value>This method needs to be implemented in the provider.</value>
</data>
<data name="MethodOnNonTphRootNotSupported" xml:space="preserve">
<value>Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property.</value>
</data>
Expand Down Expand Up @@ -876,6 +915,9 @@
<data name="PropertyNotMappedToTable" xml:space="preserve">
<value>The property '{property}' on entity type '{entityType}' is not mapped to '{table}'.</value>
</data>
<data name="RawSqlQueryNonComposable" xml:space="preserve">
<value>'FromSqlRaw' or 'FromSqlInterpolated' or 'SqlQuery' was called with non-composable SQL and with a query composing over it. Consider calling 'AsEnumerable' after the method to perform the composition on the client side.</value>
</data>
<data name="ReadonlyEntitySaved" xml:space="preserve">
<value>The entity type '{entityType}' is not mapped to a table, therefore the entities cannot be persisted to the database. Call 'ToTable' in 'OnModelCreating' to map it to a table.</value>
</data>
Expand Down Expand Up @@ -1086,46 +1128,4 @@
<data name="VisitChildrenMustBeOverridden" xml:space="preserve">
<value>'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.</value>
</data>
<data name="JsonEntityOwnedByNonJsonOwnedType" xml:space="preserve">
<value>Owned entity type '{nonJsonType}' is mapped to table '{table}' and contains JSON columns. This is currently not supported. All owned types containing a JSON column must be mapped to a JSON column themselves.</value>
</data>
<data name="JsonEntityWithTableSplittingIsNotSupported" xml:space="preserve">
<value>Table splitting is not supported for entities containing entities mapped to JSON.</value>
</data>
<data name="JsonEntityMultipleRootsMappedToTheSameJsonColumn" xml:space="preserve">
<value>Multiple owned root entities are mapped to the same JSON column '{column}' in table '{table}'. Each owned root entity must map to a different column.</value>
</data>
<data name="JsonEntityWithOwnerNotMappedToTableOrView" xml:space="preserve">
<value>Entity type '{entity}' references entities mapped to JSON but is not itself mapped to a table or a view.This is not supported.</value>
</data>
<data name="JsonEntityMappedToDifferentViewThanOwner" xml:space="preserve">
<value>Entity '{jsonType}' is mapped to JSON and also mapped to a view '{viewName}', however it's owner '{ownerType}' is mapped to a different view '{ownerViewName}'. Every entity mapped to JSON must also map to the same view as it's owner.</value>
</data>
<data name="JsonEntityWithNonTphInheritanceOnOwner" xml:space="preserve">
<value>Entity type '{rootType}' references entities mapped to JSON. Only '{tph}' inheritance is supported for those entities.</value>
</data>
<data name="JsonEntityReferencingRegularEntity" xml:space="preserve">
<value>Entity type '{jsonEntity}' is mapped to JSON and has navigation to a regular entity which is not the owner.</value>
</data>
<data name="JsonEntityWithExplicitlyConfiguredJsonPropertyNameOnKey" xml:space="preserve">
<value>Key property '{keyProperty}' on JSON-mapped entity '{jsonEntity}' should not have JSON property name configured explicitly.</value>
</data>
<data name="JsonEntityWithExplicitlyConfiguredOrdinalKey" xml:space="preserve">
<value>Entity type '{jsonEntity}' is part of collection mapped to JSON and has it's ordinal key defined explicitly. Only implicitly defined ordinal keys are supported.</value>
</data>
<data name="JsonEntityWithIncorrectNumberOfKeyProperties" xml:space="preserve">
<value>Entity type '{jsonEntity}' has incorrect number of primary key properties. Expected number is: {expectedCount}, actual number is: {actualCount}.</value>
</data>
<data name="JsonEntityWithDefaultValueSetOnItsProperty" xml:space="preserve">
<value>Setting default value on properties of an entity mapped to JSON is not supported. Entity: '{jsonEntity}', property: '{property}'.</value>
</data>
<data name="JsonEntityWithMultiplePropertiesMappedToSameJsonProperty" xml:space="preserve">
<value>Entity '{jsonEntity}' is mapped to JSON and it contains multiple properties or navigations which are mapped to the same JSON property '{property}'. Each property should map to a unique JSON property.</value>
</data>
<data name="JsonPropertyNameShouldBeConfiguredOnNestedNavigation" xml:space="preserve">
<value>JSON property name should only be configured on nested owned navigations.</value>
</data>
<data name="MethodNeedsToBeImplementedInTheProvider" xml:space="preserve">
<value>This method needs to be implemented in the provider.</value>
</data>
</root>
9 changes: 7 additions & 2 deletions src/EFCore.Relational/Query/Internal/BufferedDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ private void InitializeFields()
if (_columns.Count > 0
&& _columns.Any(e => e?.Name != null))
{
// Non-Composed FromSql
// Non-Composed RawSqlQuery
var readerColumns = _fieldNameLookup.Value;

_indexMap = new int[_columns.Count];
Expand All @@ -1264,7 +1264,12 @@ private void InitializeFields()

if (!readerColumns.TryGetValue(column.Name!, out var ordinal))
{
throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(column.Name));
if (_columns.Count != 1)
{
throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(column.Name));
}

ordinal = 0;
}

newColumnMap[ordinal] = column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ public static int[] BuildIndexMap(IReadOnlyList<string> columnNames, DbDataReade
var columnName = columnNames[i];
if (!readerColumns.TryGetValue(columnName, out var ordinal))
{
throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName));
if (columnNames.Count != 1)
{
throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName));
}

ordinal = 0;
}

indexMap[i] = ordinal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.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>
public class FromSqlParameterExpandingExpressionVisitor : ExpressionVisitor
public class RawSqlParameterExpandingExpressionVisitor : ExpressionVisitor
{
private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpressions
= new Dictionary<FromSqlExpression, Expression>(LegacyReferenceEqualityComparer.Instance);
private readonly IDictionary<RawSqlQueryExpression, Expression> _visitedRawSqlExpressions
= new Dictionary<RawSqlQueryExpression, Expression>(LegacyReferenceEqualityComparer.Instance);

private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;
Expand All @@ -33,7 +33,7 @@ private readonly IDictionary<FromSqlExpression, Expression> _visitedFromSqlExpre
/// 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 FromSqlParameterExpandingExpressionVisitor(
public RawSqlParameterExpandingExpressionVisitor(
RelationalParameterBasedSqlProcessorDependencies dependencies)
{
Dependencies = dependencies;
Expand Down Expand Up @@ -61,7 +61,7 @@ public virtual Expression Expand(
IReadOnlyDictionary<string, object?> parameterValues,
out bool canCache)
{
_visitedFromSqlExpressions.Clear();
_visitedRawSqlExpressions.Clear();
_parameterNameGenerator = _parameterNameGeneratorFactory.Create();
_parametersValues = parameterValues;
_canCache = true;
Expand All @@ -81,17 +81,17 @@ public virtual Expression Expand(
[return: NotNullIfNotNull("expression")]
public override Expression? Visit(Expression? expression)
{
if (expression is not FromSqlExpression fromSql)
if (expression is not RawSqlQueryExpression rawSqlQuery)
{
return base.Visit(expression);
}

if (_visitedFromSqlExpressions.TryGetValue(fromSql, out var visitedFromSql))
if (_visitedRawSqlExpressions.TryGetValue(rawSqlQuery, out var visitedRawSql))
{
return visitedFromSql;
return visitedRawSql;
}

switch (fromSql.Arguments)
switch (rawSqlQuery.Arguments)
{
case ParameterExpression parameterExpression:
// parameter value will never be null. It could be empty object?[]
Expand Down Expand Up @@ -127,7 +127,7 @@ public virtual Expression Expand(
}
}

return _visitedFromSqlExpressions[fromSql] = fromSql.Update(
return _visitedRawSqlExpressions[rawSqlQuery] = rawSqlQuery.Update(
Expression.Constant(new CompositeRelationalParameter(parameterExpression.Name!, subParameters)));

case ConstantExpression constantExpression:
Expand Down Expand Up @@ -157,10 +157,10 @@ public virtual Expression Expand(
}
}

return _visitedFromSqlExpressions[fromSql] = fromSql.Update(Expression.Constant(constantValues, typeof(object[])));
return _visitedRawSqlExpressions[rawSqlQuery] = rawSqlQuery.Update(Expression.Constant(constantValues, typeof(object[])));

default:
Check.DebugFail("FromSql.Arguments must be Constant/ParameterExpression");
Check.DebugFail("RawSqlQueryExpression.Arguments must be Constant/ParameterExpression");
return null;
}
}
Expand Down
Loading

0 comments on commit 1bcfa84

Please sign in to comment.