Skip to content

Commit

Permalink
Raw SQL queries for unmapped types
Browse files Browse the repository at this point in the history
Fixes #10753

Builds an ad-hoc entity type and uses it to query when `SqlQuery` is called for a type that does not have a type mapping.

Things to consider:

- The entity type cannot have relationships
- Properties are mapped by convention and mapping attributes are respected.
- The entity types are keyless.
  • Loading branch information
ajcvickers committed Jan 9, 2023
1 parent 9b33c5c commit df94994
Show file tree
Hide file tree
Showing 26 changed files with 2,756 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,14 @@ public static IQueryable<TResult> SqlQueryRaw<TResult>(
Check.NotNull(parameters, nameof(parameters));

var facadeDependencies = GetFacadeDependencies(databaseFacade);

return facadeDependencies.QueryProvider
.CreateQuery<TResult>(
new SqlQueryRootExpression(
facadeDependencies.QueryProvider, typeof(TResult), sql, Expression.Constant(parameters)));
var queryProvider = facadeDependencies.QueryProvider;
var argumentsExpression = Expression.Constant(parameters);

return queryProvider.CreateQuery<TResult>(
facadeDependencies.TypeMappingSource.FindMapping(typeof(TResult)) != null
? new SqlQueryRootExpression(queryProvider, typeof(TResult), sql, argumentsExpression)
: new FromSqlQueryRootExpression(
queryProvider, facadeDependencies.AdHocMapper.GetOrAddEntityType(typeof(TResult)), sql, argumentsExpression));
}

/// <summary>
Expand Down Expand Up @@ -380,17 +383,7 @@ public static IQueryable<TResult> SqlQueryRaw<TResult>(
public static IQueryable<TResult> SqlQuery<TResult>(
this DatabaseFacade databaseFacade,
[NotParameterized] FormattableString sql)
{
Check.NotNull(sql, nameof(sql));
Check.NotNull(sql.Format, nameof(sql.Format));

var facadeDependencies = GetFacadeDependencies(databaseFacade);

return facadeDependencies.QueryProvider
.CreateQuery<TResult>(
new SqlQueryRootExpression(
facadeDependencies.QueryProvider, typeof(TResult), sql.Format, Expression.Constant(sql.GetArguments())));
}
=> SqlQueryRaw<TResult>(databaseFacade, sql.Format, sql.GetArguments()!);

/// <summary>
/// Executes the given SQL against the database and returns the number of rows affected.
Expand Down Expand Up @@ -654,7 +647,7 @@ public static DbConnection GetDbConnection(this DatabaseFacade databaseFacade)
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal. The default value is <see langword="false"/>.
/// owns the connection and is responsible for its disposal. The default value is <see langword="false" />.
/// </param>
public static void SetDbConnection(this DatabaseFacade databaseFacade, DbConnection? connection, bool contextOwnsConnection = false)
=> GetFacadeDependencies(databaseFacade).RelationalConnection.SetDbConnection(connection, contextOwnsConnection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ public RelationalDatabaseFacadeDependencies(
IRelationalConnection relationalConnection,
IRawSqlCommandBuilder rawSqlCommandBuilder,
ICoreSingletonOptions coreOptions,
IAsyncQueryProvider queryProvider)
IAsyncQueryProvider queryProvider,
IAdHocMapper adHocMapper,
IRelationalTypeMappingSource relationalTypeMappingSource)
{
TransactionManager = transactionManager;
DatabaseCreator = databaseCreator;
Expand All @@ -41,6 +43,8 @@ public RelationalDatabaseFacadeDependencies(
RawSqlCommandBuilder = rawSqlCommandBuilder;
CoreOptions = coreOptions;
QueryProvider = queryProvider;
AdHocMapper = adHocMapper;
TypeMappingSource = relationalTypeMappingSource;
}

/// <summary>
Expand All @@ -49,47 +53,47 @@ public RelationalDatabaseFacadeDependencies(
/// 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 IDbContextTransactionManager TransactionManager { get; init; }
public virtual IDbContextTransactionManager TransactionManager { 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 IDatabaseCreator DatabaseCreator { get; init; }
public virtual IDatabaseCreator DatabaseCreator { 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 IExecutionStrategy ExecutionStrategy { get; init; }
public virtual IExecutionStrategy ExecutionStrategy { 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 IExecutionStrategyFactory ExecutionStrategyFactory { get; init; }
public virtual IExecutionStrategyFactory ExecutionStrategyFactory { 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 IEnumerable<IDatabaseProvider> DatabaseProviders { get; init; }
public virtual IEnumerable<IDatabaseProvider> DatabaseProviders { 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 IRelationalCommandDiagnosticsLogger CommandLogger { get; init; }
public virtual IRelationalCommandDiagnosticsLogger CommandLogger { get; }

IDiagnosticsLogger<DbLoggerCategory.Database.Command> IDatabaseFacadeDependencies.CommandLogger
=> CommandLogger;
Expand All @@ -100,37 +104,53 @@ public RelationalDatabaseFacadeDependencies(
/// 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 IConcurrencyDetector ConcurrencyDetector { get; init; }
public virtual IConcurrencyDetector ConcurrencyDetector { 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 IRelationalConnection RelationalConnection { get; init; }
public virtual IRelationalConnection RelationalConnection { 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 IRawSqlCommandBuilder RawSqlCommandBuilder { get; init; }
public virtual IRawSqlCommandBuilder RawSqlCommandBuilder { 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 ICoreSingletonOptions CoreOptions { get; init; }
public virtual ICoreSingletonOptions CoreOptions { 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 IAsyncQueryProvider QueryProvider { get; init; }
public virtual IAsyncQueryProvider QueryProvider { 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 IAdHocMapper AdHocMapper { 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 ITypeMappingSource TypeMappingSource { get; }
}
4 changes: 3 additions & 1 deletion src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> CoreServices
{ typeof(IQueryTranslationPostprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IShapedQueryCompilingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IAdHocMapper), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Transient) },
{ typeof(IParameterBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
{ typeof(ITypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },
Expand Down Expand Up @@ -301,6 +302,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IQueryTranslationPostprocessorFactory, QueryTranslationPostprocessorFactory>();
TryAdd<INavigationExpansionExtensibilityHelper, NavigationExpansionExtensibilityHelper>();
TryAdd<IExceptionDetector, ExceptionDetector>();
TryAdd<IAdHocMapper, AdHocMapper>();

TryAdd(
p => p.GetService<IDbContextOptions>()?.FindExtension<CoreOptionsExtension>()?.DbContextLogger
Expand Down Expand Up @@ -426,7 +428,7 @@ public virtual EntityFrameworkServicesBuilder TryAdd
/// <returns>This builder, such that further calls can be chained.</returns>
public virtual EntityFrameworkServicesBuilder TryAdd
<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
Func<IServiceProvider, TImplementation> factory)
Func<IServiceProvider, TImplementation> factory)
where TService : class
where TImplementation : class, TService
=> TryAdd(typeof(TService), typeof(TImplementation), factory);
Expand Down
14 changes: 10 additions & 4 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,11 @@ protected virtual void ValidatePropertyMapping(
}

throw new InvalidOperationException(
CoreStrings.NavigationNotAdded(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
Equals(model.FindAnnotation(CoreAnnotationNames.AdHocModel)?.Value, true)
? CoreStrings.NavigationNotAddedAdHoc(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())
: CoreStrings.NavigationNotAdded(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}

// ReSharper restore CheckForReferenceEqualityInstead.3
Expand All @@ -254,8 +257,11 @@ protected virtual void ValidatePropertyMapping(
else
{
throw new InvalidOperationException(
CoreStrings.PropertyNotAdded(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
Equals(model.FindAnnotation(CoreAnnotationNames.AdHocModel)?.Value, true)
? CoreStrings.PropertyNotAddedAdHoc(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName())
: CoreStrings.PropertyNotAdded(
entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions src/EFCore/Internal/DbContextDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,69 +59,69 @@ public DbContextDependencies(
/// 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 IDbSetSource SetSource { get; init; }
public IDbSetSource SetSource { 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 IEntityFinderFactory EntityFinderFactory { get; init; }
public IEntityFinderFactory EntityFinderFactory { 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 IAsyncQueryProvider QueryProvider { get; init; }
public IAsyncQueryProvider QueryProvider { 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 IStateManager StateManager { get; init; }
public IStateManager StateManager { 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 IChangeDetector ChangeDetector { get; init; }
public IChangeDetector ChangeDetector { 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 IEntityGraphAttacher EntityGraphAttacher { get; init; }
public IEntityGraphAttacher EntityGraphAttacher { 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 IExceptionDetector ExceptionDetector { get; init; }
public IExceptionDetector ExceptionDetector { 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 IDiagnosticsLogger<DbLoggerCategory.Update> UpdateLogger { get; init; }
public IDiagnosticsLogger<DbLoggerCategory.Update> UpdateLogger { 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 IDiagnosticsLogger<DbLoggerCategory.Infrastructure> InfrastructureLogger { get; init; }
public IDiagnosticsLogger<DbLoggerCategory.Infrastructure> InfrastructureLogger { get; }
}
Loading

0 comments on commit df94994

Please sign in to comment.