Skip to content

Commit c802f23

Browse files
committed
Add support for UDF Scalar functions
1 parent 0d889f2 commit c802f23

File tree

68 files changed

+3820
-893
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3820
-893
lines changed

NuGet.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<configuration>
33
<packageSources>
44
<clear />
5-
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json" />
5+
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-release/api/v3/index.json" />
66
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
77
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
88
</packageSources>
9-
</configuration>
9+
</configuration>

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
4848
{
4949
GenerateFluentApiForAnnotation(ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), stringBuilder);
5050

51+
IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);
52+
5153
GenerateAnnotations(annotations, stringBuilder);
5254
}
5355

@@ -609,6 +611,18 @@ protected virtual void IgnoreAnnotations(
609611
}
610612
}
611613

614+
protected virtual void IgnoreAnnotationTypes(
615+
[NotNull] IList<IAnnotation> annotations, [NotNull] params string[] annotationPrefixes)
616+
{
617+
Check.NotNull(annotations, nameof(annotations));
618+
Check.NotNull(annotationPrefixes, nameof(annotationPrefixes));
619+
620+
foreach(var ignoreAnnotation in annotations.Where(a => annotationPrefixes.Any(pre => a.Name.StartsWith(pre, StringComparison.OrdinalIgnoreCase))).ToList())
621+
{
622+
annotations.Remove(ignoreAnnotation);
623+
}
624+
}
625+
612626
protected virtual void GenerateAnnotations(
613627
[NotNull] IReadOnlyList<IAnnotation> annotations, [NotNull] IndentedStringBuilder stringBuilder)
614628
{

src/EFCore.InMemory/Extensions/InMemoryServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using JetBrains.Annotations;
5+
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
56
using Microsoft.EntityFrameworkCore.Infrastructure;
67
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
78
using Microsoft.EntityFrameworkCore.Query;
@@ -64,6 +65,7 @@ public static IServiceCollection AddEntityFrameworkInMemoryDatabase([NotNull] th
6465
.TryAdd<IQueryContextFactory, InMemoryQueryContextFactory>()
6566
.TryAdd<IEntityQueryModelVisitorFactory, InMemoryQueryModelVisitorFactory>()
6667
.TryAdd<IEntityQueryableExpressionVisitorFactory, InMemoryEntityQueryableExpressionVisitorFactory>()
68+
.TryAdd<IEvaluatableExpressionFilter, EvaluatableExpressionFilter>()
6769
.TryAddProviderSpecificServices(b => b
6870
.TryAddSingleton<IInMemoryStoreCache, InMemoryStoreCache>()
6971
.TryAddSingleton<IInMemoryTableFactory, InMemoryTableFactory>()
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.EntityFrameworkCore.Metadata;
6+
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
7+
8+
namespace Microsoft.EntityFrameworkCore
9+
{
10+
public class NorthwindDbFunctionContext : NorthwindContext
11+
{
12+
public NorthwindDbFunctionContext(DbContextOptions options, QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
13+
: base(options, queryTrackingBehavior)
14+
{
15+
}
16+
17+
public enum ReportingPeriod
18+
{
19+
Winter = 0,
20+
Spring,
21+
Summer,
22+
Fall
23+
}
24+
25+
public static int MyCustomLength(string s)
26+
{
27+
throw new Exception();
28+
}
29+
30+
[DbFunction(Schema = "dbo", Name = "EmployeeOrderCount")]
31+
public static int EmployeeOrderCount(int employeeId)
32+
{
33+
throw new NotImplementedException();
34+
}
35+
36+
[DbFunction(Schema = "dbo", Name = "EmployeeOrderCount")]
37+
public static int EmployeeOrderCountWithClient(int employeeId)
38+
{
39+
switch (employeeId)
40+
{
41+
case 3: return 127;
42+
default: return 1;
43+
}
44+
}
45+
46+
[DbFunction(Schema = "dbo")]
47+
public static bool IsTopEmployee(int employeeId)
48+
{
49+
throw new NotImplementedException();
50+
}
51+
52+
[DbFunction(Schema = "dbo")]
53+
public static int GetEmployeeWithMostOrdersAfterDate(DateTime? startDate)
54+
{
55+
throw new NotImplementedException();
56+
}
57+
58+
[DbFunction(Schema = "dbo")]
59+
public static DateTime? GetReportingPeriodStartDate(ReportingPeriod periodId)
60+
{
61+
throw new NotImplementedException();
62+
}
63+
64+
[DbFunction(Schema = "dbo")]
65+
public static string StarValue(int starCount, int value)
66+
{
67+
throw new NotImplementedException();
68+
}
69+
70+
[DbFunction(Name = "StarValue", Schema = "dbo")]
71+
public static string StarValueAlternateParamOrder([DbFunctionParameter(ParameterIndex = 1)]int value, [DbFunctionParameter(ParameterIndex = 0)]int starCount)
72+
{
73+
throw new NotImplementedException();
74+
}
75+
76+
[DbFunction(Schema = "dbo")]
77+
public static int AddValues(int a, int b)
78+
{
79+
throw new NotImplementedException();
80+
}
81+
82+
[DbFunction(Schema = "dbo")]
83+
public static DateTime GetBestYearEver()
84+
{
85+
throw new NotImplementedException();
86+
}
87+
}
88+
}

src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Microsoft.EntityFrameworkCore.Update.Internal;
2222
using Microsoft.EntityFrameworkCore.ValueGeneration;
2323
using Microsoft.Extensions.DependencyInjection;
24+
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
2425

2526
namespace Microsoft.EntityFrameworkCore.Infrastructure
2627
{
@@ -65,7 +66,7 @@ private static readonly IDictionary<Type, ServiceCharacteristics> _relationalSer
6566
{ typeof(ISqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
6667
{ typeof(IUpdateSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
6768
{ typeof(IMemberTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
68-
{ typeof(IMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
69+
{ typeof(ICompositeMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
6970
{ typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
7071
{ typeof(ICommandBatchPreparer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7172
{ typeof(IModificationCommandBatchFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
@@ -77,7 +78,7 @@ private static readonly IDictionary<Type, ServiceCharacteristics> _relationalSer
7778
{ typeof(IRelationalConnection), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7879
{ typeof(IRelationalDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
7980
{ typeof(IHistoryRepository), new ServiceCharacteristics(ServiceLifetime.Scoped) },
80-
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) }
81+
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) },
8182
};
8283

8384
/// <summary>
@@ -115,6 +116,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
115116
TryAdd<IMigrationsIdGenerator, MigrationsIdGenerator>();
116117
TryAdd<IKeyValueIndexFactorySource, KeyValueIndexFactorySource>();
117118
TryAdd<IModelSource, RelationalModelSource>();
119+
TryAdd<IModelCustomizer, RelationalModelCustomizer>();
118120
TryAdd<IMigrationsAnnotationProvider, MigrationsAnnotationProvider>();
119121
TryAdd<IModelValidator, RelationalModelValidator>();
120122
TryAdd<IMigrator, Migrator>();
@@ -150,6 +152,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
150152
TryAdd<IExpressionFragmentTranslator, RelationalCompositeExpressionFragmentTranslator>();
151153
TryAdd<ISqlTranslatingExpressionVisitorFactory, SqlTranslatingExpressionVisitorFactory>();
152154
TryAdd<INamedConnectionStringResolver, NamedConnectionStringResolver>();
155+
TryAdd<IEvaluatableExpressionFilter, RelationalEvaluatableExpressionFilter>();
153156

154157
ServiceCollectionMap.GetInfrastructure()
155158
.AddDependencySingleton<RelationalCompositeMemberTranslatorDependencies>()
Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Linq;
54
using JetBrains.Annotations;
6-
using Microsoft.EntityFrameworkCore.Internal;
7-
using Microsoft.EntityFrameworkCore.Metadata.Internal;
85

96
namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal
107
{
@@ -18,25 +15,5 @@ public RelationalModelSource([NotNull] ModelSourceDependencies dependencies)
1815
: base(dependencies)
1916
{
2017
}
21-
22-
/// <summary>
23-
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
24-
/// directly from your code. This API may change or be removed in future releases.
25-
/// </summary>
26-
protected override void FindSets(ModelBuilder modelBuilder, DbContext context)
27-
{
28-
base.FindSets(modelBuilder, context);
29-
30-
var sets = Dependencies.SetFinder.CreateClrTypeDbSetMapping(context);
31-
32-
foreach (var entityType in modelBuilder.Model.GetEntityTypes().Cast<EntityType>())
33-
{
34-
if (entityType.BaseType == null
35-
&& sets.ContainsKey(entityType.ClrType))
36-
{
37-
entityType.Builder.Relational(ConfigurationSource.Convention).ToTable(sets[entityType.ClrType].Name);
38-
}
39-
}
40-
}
4118
}
4219
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Linq;
5+
using System.Reflection;
6+
using JetBrains.Annotations;
7+
using Microsoft.EntityFrameworkCore.Internal;
8+
using Microsoft.EntityFrameworkCore.Metadata;
9+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
10+
using Microsoft.EntityFrameworkCore.Utilities;
11+
12+
namespace Microsoft.EntityFrameworkCore.Infrastructure
13+
{
14+
/// <summary>
15+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
16+
/// directly from your code. This API may change or be removed in future releases.
17+
/// </summary>
18+
public class RelationalModelCustomizer : ModelCustomizer
19+
{
20+
public RelationalModelCustomizer([NotNull] ModelCustomizerDependencies dependencies)
21+
: base(dependencies)
22+
{
23+
}
24+
25+
public override void Customize(ModelBuilder modelBuilder, DbContext dbContext)
26+
{
27+
FindDbFunctions(modelBuilder, dbContext);
28+
29+
base.Customize(modelBuilder, dbContext);
30+
}
31+
32+
/// <summary>
33+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
34+
/// directly from your code. This API may change or be removed in future releases.
35+
/// </summary>
36+
protected virtual void FindDbFunctions([NotNull] ModelBuilder modelBuilder, [NotNull] DbContext context)
37+
{
38+
Check.NotNull(modelBuilder, nameof(modelBuilder));
39+
Check.NotNull(context, nameof(context));
40+
41+
var functions = context.GetType().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
42+
.Where(mi => mi.IsStatic
43+
&& mi.IsPublic
44+
&& mi.GetCustomAttributes(typeof(DbFunctionAttribute)).Any());
45+
46+
foreach (var function in functions)
47+
{
48+
modelBuilder.HasDbFunction(function);
49+
}
50+
}
51+
52+
/// <summary>
53+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
54+
/// directly from your code. This API may change or be removed in future releases.
55+
/// </summary>
56+
protected override void FindSets(ModelBuilder modelBuilder, DbContext context)
57+
{
58+
base.FindSets(modelBuilder, context);
59+
60+
var sets = Dependencies.SetFinder.CreateClrTypeDbSetMapping(context);
61+
62+
foreach (var entityType in modelBuilder.Model.GetEntityTypes().Cast<EntityType>())
63+
{
64+
if (entityType.BaseType == null
65+
&& sets.ContainsKey(entityType.ClrType))
66+
{
67+
entityType.Builder.Relational(ConfigurationSource.Convention).ToTable(sets[entityType.ClrType].Name);
68+
}
69+
}
70+
}
71+
}
72+
}

src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Reflection;
78
using JetBrains.Annotations;
89
using Microsoft.EntityFrameworkCore.Internal;
910
using Microsoft.EntityFrameworkCore.Metadata;
@@ -56,6 +57,53 @@ public override void Validate(IModel model)
5657
ValidateDataTypes(model);
5758
ValidateDefaultValuesOnKeys(model);
5859
ValidateBoolsWithDefaults(model);
60+
ValidateDbFunctions(model);
61+
}
62+
63+
/// <summary>
64+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
65+
/// directly from your code. This API may change or be removed in future releases.
66+
/// </summary>
67+
protected virtual void ValidateDbFunctions([NotNull] IModel model)
68+
{
69+
foreach (var dbFunction in model.Relational().DbFunctions)
70+
{
71+
if (string.IsNullOrEmpty(dbFunction.Name))
72+
throw new InvalidOperationException(CoreStrings.DbFunctionNameEmpty());
73+
74+
var paramIndexes = dbFunction.Parameters.Select(fp => fp.Index).ToArray();
75+
var dbFuncName = $"{dbFunction.MethodInfo.DeclaringType?.Name}.{dbFunction.MethodInfo.Name}";
76+
77+
if (paramIndexes.Distinct().Count() != dbFunction.Parameters.Count)
78+
throw new InvalidOperationException(CoreStrings.DbFunctionDuplicateIndex(dbFuncName));
79+
80+
if (Enumerable.Range(0, paramIndexes.Length).Except(paramIndexes).Any())
81+
throw new InvalidOperationException(CoreStrings.DbFunctionNonContinuousIndex(dbFuncName));
82+
83+
if (dbFunction.MethodInfo.IsStatic == false
84+
&& dbFunction.MethodInfo.DeclaringType.GetTypeInfo().IsSubclassOf(typeof(DbContext)))
85+
{
86+
throw new InvalidOperationException(CoreStrings.DbFunctionDbContextMethodMustBeStatic(dbFuncName));
87+
}
88+
89+
if(dbFunction.TranslateCallback == null)
90+
{
91+
if (dbFunction.ReturnType == null || RelationalDependencies.TypeMapper.IsTypeMapped(dbFunction.ReturnType) == false)
92+
throw new InvalidOperationException(CoreStrings.DbFunctionInvalidReturnType(dbFunction.MethodInfo, dbFunction.ReturnType));
93+
94+
foreach (var parameter in dbFunction.Parameters)
95+
{
96+
if (parameter.ParameterType == null || RelationalDependencies.TypeMapper.IsTypeMapped(parameter.ParameterType) == false)
97+
{
98+
throw new InvalidOperationException(
99+
CoreStrings.DbFunctionInvalidParameterType(
100+
dbFunction.MethodInfo,
101+
parameter.Name,
102+
dbFunction.ReturnType));
103+
}
104+
}
105+
}
106+
}
59107
}
60108

61109
/// <summary>

src/EFCore.Relational/Metadata/Conventions/Internal/RelationalConventionSetBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public virtual ConventionSet AddConventions(ConventionSet conventionSet)
6363

6464
conventionSet.ModelBuiltConventions.Add(new RelationalTypeMappingConvention(Dependencies.TypeMapper));
6565

66+
conventionSet.ModelAnnotationChangedConventions.Add(new RelationalDbFunctionConvention());
67+
6668
return conventionSet;
6769
}
6870

0 commit comments

Comments
 (0)