Skip to content

Commit

Permalink
Add support for UDF Scalar functions
Browse files Browse the repository at this point in the history
  • Loading branch information
pmiddleton committed Jun 9, 2017
1 parent b39e445 commit f4d74e6
Show file tree
Hide file tree
Showing 66 changed files with 3,114 additions and 187 deletions.
14 changes: 14 additions & 0 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui
{
GenerateFluentApiForAnnotation(ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), stringBuilder);

IgnoreAnnotationTypes(annotations, RelationalAnnotationNames.DbFunction);

GenerateAnnotations(annotations, stringBuilder);
}

Expand Down Expand Up @@ -609,6 +611,18 @@ protected virtual void GenerateForeignKeyAnnotations([NotNull] IForeignKey forei
}
}

protected virtual void IgnoreAnnotationTypes(
[NotNull] IList<IAnnotation> annotations, [NotNull] params string[] annotationPrefixes)
{
Check.NotNull(annotations, nameof(annotations));
Check.NotNull(annotationPrefixes, nameof(annotationPrefixes));

foreach(var ignoreAnnotation in annotations.Where(a => annotationPrefixes.Any(pre => a.Name.StartsWith(pre, StringComparison.OrdinalIgnoreCase))).ToList())
{
annotations.Remove(ignoreAnnotation);
}
}

protected virtual void GenerateAnnotations(
[NotNull] IReadOnlyList<IAnnotation> annotations, [NotNull] IndentedStringBuilder stringBuilder)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using JetBrains.Annotations;
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Query;
Expand Down Expand Up @@ -64,6 +65,7 @@ public static IServiceCollection AddEntityFrameworkInMemoryDatabase([NotNull] th
.TryAdd<IQueryContextFactory, InMemoryQueryContextFactory>()
.TryAdd<IEntityQueryModelVisitorFactory, InMemoryQueryModelVisitorFactory>()
.TryAdd<IEntityQueryableExpressionVisitorFactory, InMemoryEntityQueryableExpressionVisitorFactory>()
.TryAdd<IEvaluatableExpressionFilter, EvaluatableExpressionFilter>()
.TryAddProviderSpecificServices(b => b
.TryAddSingleton<IInMemoryStoreCache, InMemoryStoreCache>()
.TryAddSingleton<IInMemoryTableFactory, InMemoryTableFactory>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;

namespace Microsoft.EntityFrameworkCore
{
public class NorthwindDbFunctionContext : NorthwindContext
{
public NorthwindDbFunctionContext(DbContextOptions options, QueryTrackingBehavior queryTrackingBehavior = QueryTrackingBehavior.TrackAll)
: base(options, queryTrackingBehavior)
{
}

public enum ReportingPeriod
{
Winter = 0,
Spring,
Summer,
Fall
}

public static int MyCustomLength(string s)
{
throw new Exception();
}

[DbFunction(Schema = "dbo", Name = "EmployeeOrderCount")]
public static int EmployeeOrderCount(int employeeId)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo", Name = "EmployeeOrderCount")]
public static int EmployeeOrderCountWithClient(int employeeId)
{
switch (employeeId)
{
case 3: return 127;
default: return 1;
}
}

[DbFunction(Schema = "dbo")]
public static bool IsTopEmployee(int employeeId)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo")]
public static int GetEmployeeWithMostOrdersAfterDate(DateTime? startDate)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo")]
public static DateTime? GetReportingPeriodStartDate(ReportingPeriod periodId)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo")]
public static string StarValue(int starCount, int value)
{
throw new NotImplementedException();
}

[DbFunction(Name = "StarValue", Schema = "dbo")]
public static string StarValueAlternateParamOrder([DbFunctionParameter(ParameterIndex = 1)]int value, [DbFunctionParameter(ParameterIndex = 0)]int starCount)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo")]
public static int AddValues(int a, int b)
{
throw new NotImplementedException();
}

[DbFunction(Schema = "dbo")]
public static DateTime GetBestYearEver()
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Microsoft.EntityFrameworkCore.Update.Internal;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.Extensions.DependencyInjection;
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;

namespace Microsoft.EntityFrameworkCore.Infrastructure
{
Expand Down Expand Up @@ -65,7 +66,7 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB
{ typeof(ISqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IUpdateSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMemberTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(ICompositeMethodCallTranslator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(ICommandBatchPreparer), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IModificationCommandBatchFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) },
Expand All @@ -77,7 +78,7 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB
{ typeof(IRelationalConnection), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IHistoryRepository), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) }
{ typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) },
};

/// <summary>
Expand Down Expand Up @@ -115,6 +116,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IMigrationsIdGenerator, MigrationsIdGenerator>();
TryAdd<IKeyValueIndexFactorySource, KeyValueIndexFactorySource>();
TryAdd<IModelSource, RelationalModelSource>();
TryAdd<IModelCustomizer, RelationalModelCustomizer>();
TryAdd<IMigrationsAnnotationProvider, MigrationsAnnotationProvider>();
TryAdd<IModelValidator, RelationalModelValidator>();
TryAdd<IMigrator, Migrator>();
Expand Down Expand Up @@ -150,6 +152,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IExpressionFragmentTranslator, RelationalCompositeExpressionFragmentTranslator>();
TryAdd<ISqlTranslatingExpressionVisitorFactory, SqlTranslatingExpressionVisitorFactory>();
TryAdd<INamedConnectionStringResolver, NamedConnectionStringResolver>();
TryAdd<IEvaluatableExpressionFilter, RelationalEvaluatableExpressionFilter>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalCompositeMemberTranslatorDependencies>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal
{
Expand All @@ -18,25 +15,5 @@ public RelationalModelSource([NotNull] ModelSourceDependencies dependencies)
: base(dependencies)
{
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override void FindSets(ModelBuilder modelBuilder, DbContext context)
{
base.FindSets(modelBuilder, context);

var sets = Dependencies.SetFinder.CreateClrTypeDbSetMapping(context);

foreach (var entityType in modelBuilder.Model.GetEntityTypes().Cast<EntityType>())
{
if (entityType.BaseType == null
&& sets.ContainsKey(entityType.ClrType))
{
entityType.Builder.Relational(ConfigurationSource.Convention).ToTable(sets[entityType.ClrType].Name);
}
}
}
}
}
72 changes: 72 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalModelCustomizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Infrastructure
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class RelationalModelCustomizer : ModelCustomizer
{
public RelationalModelCustomizer([NotNull] ModelCustomizerDependencies dependencies)
: base(dependencies)
{
}

public override void Customize(ModelBuilder modelBuilder, DbContext dbContext)
{
FindDbFunctions(modelBuilder, dbContext);

base.Customize(modelBuilder, dbContext);
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected virtual void FindDbFunctions([NotNull] ModelBuilder modelBuilder, [NotNull] DbContext context)
{
Check.NotNull(modelBuilder, nameof(modelBuilder));
Check.NotNull(context, nameof(context));

var functions = context.GetType().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Where(mi => mi.IsStatic
&& mi.IsPublic
&& mi.GetCustomAttributes(typeof(DbFunctionAttribute)).Any());

foreach (var function in functions)
{
modelBuilder.HasDbFunction(function);
}
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected override void FindSets(ModelBuilder modelBuilder, DbContext context)
{
base.FindSets(modelBuilder, context);

var sets = Dependencies.SetFinder.CreateClrTypeDbSetMapping(context);

foreach (var entityType in modelBuilder.Model.GetEntityTypes().Cast<EntityType>())
{
if (entityType.BaseType == null
&& sets.ContainsKey(entityType.ClrType))
{
entityType.Builder.Relational(ConfigurationSource.Convention).ToTable(sets[entityType.ClrType].Name);
}
}
}
}
}
48 changes: 48 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -56,6 +57,53 @@ public override void Validate(IModel model)
ValidateDataTypes(model);
ValidateDefaultValuesOnKeys(model);
ValidateBoolsWithDefaults(model);
ValidateDbFunctions(model);
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
protected virtual void ValidateDbFunctions([NotNull] IModel model)
{
foreach (var dbFunction in model.Relational().DbFunctions)
{
if (string.IsNullOrEmpty(dbFunction.Name))
throw new InvalidOperationException(CoreStrings.DbFunctionNameEmpty());

var paramIndexes = dbFunction.Parameters.Select(fp => fp.Index).ToArray();
var dbFuncName = $"{dbFunction.MethodInfo.DeclaringType?.Name}.{dbFunction.MethodInfo.Name}";

if (paramIndexes.Distinct().Count() != dbFunction.Parameters.Count)
throw new InvalidOperationException(CoreStrings.DbFunctionDuplicateIndex(dbFuncName));

if (Enumerable.Range(0, paramIndexes.Length).Except(paramIndexes).Any())
throw new InvalidOperationException(CoreStrings.DbFunctionNonContinuousIndex(dbFuncName));

if (dbFunction.MethodInfo.IsStatic == false
&& dbFunction.MethodInfo.DeclaringType.GetTypeInfo().IsSubclassOf(typeof(DbContext)))
{
throw new InvalidOperationException(CoreStrings.DbFunctionDbContextMethodMustBeStatic(dbFuncName));
}

if(dbFunction.TranslateCallback == null)
{
if (dbFunction.ReturnType == null || RelationalDependencies.TypeMapper.IsTypeMapped(dbFunction.ReturnType) == false)
throw new InvalidOperationException(CoreStrings.DbFunctionInvalidReturnType(dbFunction.MethodInfo, dbFunction.ReturnType));

foreach (var parameter in dbFunction.Parameters)
{
if (parameter.ParameterType == null || RelationalDependencies.TypeMapper.IsTypeMapped(parameter.ParameterType) == false)
{
throw new InvalidOperationException(
CoreStrings.DbFunctionInvalidParameterType(
dbFunction.MethodInfo,
parameter.Name,
dbFunction.ReturnType));
}
}
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public virtual ConventionSet AddConventions(ConventionSet conventionSet)

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

conventionSet.ModelAnnotationChangedConventions.Add(new RelationalDbFunctionConvention());

return conventionSet;
}

Expand Down
Loading

0 comments on commit f4d74e6

Please sign in to comment.