Skip to content

Commit

Permalink
Query: Configure and use TypeMapping on DbFunction
Browse files Browse the repository at this point in the history
- Add a convention which configures actual type mapping from store type on DbFunction
- While translating db function use the TypeMapping if available

Resolves #27954
Resolves #27524
  • Loading branch information
smitpatel committed May 10, 2022
1 parent 305710f commit de7a8d0
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public override ConventionSet CreateConventionSet()
typeof(ModelCleanupConvention));
conventionSet.ModelFinalizingConventions.Add(new TableSharingConcurrencyTokenConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention);
conventionSet.ModelFinalizingConventions.Add(new ScalarDbFunctionTypeMappingConvention(Dependencies, RelationalDependencies));
conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention);
conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention);
conventionSet.ModelFinalizingConventions.Add(new EntityTypeHierarchyMappingConvention(Dependencies, RelationalDependencies));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;

/// <summary>
/// A convention that configures the store type to which a scalar function is mapped.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see> and
/// <see href="https://aka.ms/efcore-docs-database-functions">Database functions</see> for more information and examples.
/// </remarks>
public class ScalarDbFunctionTypeMappingConvention : IModelFinalizingConvention
{
/// <summary>
/// Creates a new instance of <see cref="ScalarDbFunctionTypeMappingConvention" />.
/// </summary>
/// <param name="dependencies">Parameter object containing dependencies for this convention.</param>
/// <param name="relationalDependencies"> Parameter object containing relational dependencies for this convention.</param>
public ScalarDbFunctionTypeMappingConvention(
ProviderConventionSetBuilderDependencies dependencies,
RelationalConventionSetBuilderDependencies relationalDependencies)
{
Dependencies = dependencies;
RelationalDependencies = relationalDependencies;
}

/// <summary>
/// Dependencies for this service.
/// </summary>
protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; }

/// <summary>
/// Relational provider-specific dependencies for this service.
/// </summary>
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }

/// <inheritdoc />
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var function in modelBuilder.Metadata.GetDbFunctions())
{
ProcessDbFunction(function.Builder);
}
}

private void ProcessDbFunction(
IConventionDbFunctionBuilder dbFunctionBuilder)
{
var function = dbFunctionBuilder.Metadata;
if (!function.IsScalar
|| function.StoreType == null)
{
return;
}

var typeMapping = ((IRelationalTypeMappingSource)Dependencies.TypeMappingSource).FindMapping(function.ReturnType, function.StoreType);
dbFunctionBuilder.HasTypeMapping(typeMapping);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ public RelationalMethodCallTranslatorProvider(RelationalMethodCallTranslatorProv
arguments,
dbFunction.IsNullable,
argumentsPropagateNullability,
method.ReturnType.UnwrapNullableType())
method.ReturnType.UnwrapNullableType(),
dbFunction.TypeMapping)
: _sqlExpressionFactory.Function(
dbFunction.Schema,
dbFunction.Name,
arguments,
dbFunction.IsNullable,
argumentsPropagateNullability,
method.ReturnType.UnwrapNullableType());
method.ReturnType.UnwrapNullableType(),
dbFunction.TypeMapping);
}

return _plugins.Concat(_translators)
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;
using System.ComponentModel.DataAnnotations.Schema;
using NameSpace1;

namespace Microsoft.EntityFrameworkCore.Query
Expand Down Expand Up @@ -49,6 +51,56 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
mb.ToTable((string)null);
}
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task StoreType_for_UDF_used(bool async)
{
var contextFactory = await InitializeAsync<Context27954>();
using var context = contextFactory.CreateContext();

var date = new DateTime(2012, 12, 12);
var query1 = context.Set<MyEntity>().Where(x => x.SomeDate == date);
var query2 = context.Set<MyEntity>().Where(x => MyEntity.Modify(x.SomeDate) == date);

if (async)
{
await query1.ToListAsync();
await Assert.ThrowsAnyAsync<Exception>(() => query2.ToListAsync());
}
else
{
query1.ToList();
Assert.ThrowsAny<Exception>(() => query2.ToList());
}
}

protected class Context27954 : DbContext
{
public Context27954(DbContextOptions options)
: base(options)
{
}

public DbSet<MyEntity> MyEntities { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.HasDbFunction(typeof(MyEntity).GetMethod(nameof(MyEntity.Modify)))
.HasName("ModifyDate")
.HasStoreType("datetime")
.HasSchema("dbo");
}
}

protected class MyEntity
{
public int Id { get; set; }
[Column(TypeName = "datetime")]
public DateTime SomeDate { get; set; }
public static DateTime Modify(DateTime date) => throw new NotSupportedException();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,22 @@ public override async Task SelectMany_where_Select(bool async)
) AS [t0] ON [p].[Id] = [t0].[ParentId]
WHERE [t0].[SomeOtherNullableDateTime] IS NOT NULL");
}

public override async Task StoreType_for_UDF_used(bool async)
{
await base.StoreType_for_UDF_used(async);

AssertSql(
@"@__date_0='2012-12-12T00:00:00.0000000' (DbType = DateTime)
SELECT [m].[Id], [m].[SomeDate]
FROM [MyEntities] AS [m]
WHERE [m].[SomeDate] = @__date_0",
//
@"@__date_0='2012-12-12T00:00:00.0000000' (DbType = DateTime)
SELECT [m].[Id], [m].[SomeDate]
FROM [MyEntities] AS [m]
WHERE [dbo].[ModifyDate]([m].[SomeDate]) = @__date_0");
}
}

0 comments on commit de7a8d0

Please sign in to comment.