Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take 2 - Add support for user defined scalar functions #8507

Merged
merged 1 commit into from
Jun 9, 2017

Conversation

pmiddleton
Copy link
Contributor

@anpete - Here is the new PR for adding UDF support.

This is a major refactor from the last PR.

  • Core UDF code moved to EFCore.Relational from EFCore
  • UDF metadata now stored in annotations on the model
  • UDF translation now ties into the existing function translation mechanism
  • Code first APIs now use the provider extension method pattern

@anpete anpete self-assigned this May 18, 2017
@anpete anpete requested review from bricelam and smitpatel May 18, 2017 19:26
@anpete
Copy link
Contributor

anpete commented May 18, 2017

@bricelam Looks like there are some minor changes to Migrations/Design here

Check.NotNull(annotations, nameof(annotations));
Check.NotNull(annotationPrefixes, nameof(annotationPrefixes));

foreach(var ignoreAnnotation in annotations.Where(a => !annotationPrefixes.All(pre => a.Name.StartsWith(pre) == false)).ToList())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add braces even when single line block

@anpete
Copy link
Contributor

anpete commented May 18, 2017

@smitpatel Can you look at CF changes?

@@ -79,6 +79,8 @@ public virtual IModel GetModel(DbContext context, IConventionSetBuilder conventi

FindSets(modelBuilder, context);

CreateProviderModel(modelBuilder, context) ;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajcvickers Would a relational IModelCustomizer be a better option here?

/// </summary>
public class DbFunction : IDbFunction, IMethodCallTranslator
{
private SortedDictionary<string, DbFunctionParameter> _parameters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly all fields where possible.


private Type _returnType;
private readonly string _annotationName;
private readonly IModel _model;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused field.


MethodInfo = dbFunctionMethodInfo;
ReturnType = MethodInfo.ReturnType;
Name = name ?? dbFunctionMethodInfo.Name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name has NotNull attribute.

{
foreach (var dbFunction in RelationalExtensions.For(model).DbFunctions)
{
if (String.IsNullOrEmpty(dbFunction.Name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use built-in type names.

if (dbFunction.MethodInfo.IsStatic == false && dbFunction.MethodInfo.DeclaringType.GetTypeInfo().IsSubclassOf(typeof(DbContext)) == false)
ShowError(CoreStrings.DbFunctionDbContextMethodMustBeStatic(dbFuncName));

var missingType = dbFunction.Parameters.Where(p => p.ParameterType == null).FirstOrDefault();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can move predicate to FirstOrDefault and remove Where.

if (missingType != null)
ShowError(CoreStrings.DbFunctionParameterMissingType(dbFuncName, missingType.Name));

var arrayParam = dbFunction.Parameters.Where(p => p.ParameterType.GetTypeInfo().IsArray == true).FirstOrDefault();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can move predicate to FirstOrDefault and remove Where.
redundant '== true'

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

if (paramIndexes.Distinct().Count() != dbFunction.Parameters.Count())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Use Parameters.Count property

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

if (dbFunction.MethodInfo.IsStatic == false && dbFunction.MethodInfo.DeclaringType.GetTypeInfo().IsSubclassOf(typeof(DbContext)) == false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

break long lines. We break these before '&&'

@@ -23,6 +23,18 @@ public RelationalModelSource([NotNull] ModelSourceDependencies dependencies)
/// 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 CreateProviderModel([NotNull] ModelBuilder modelBuilder, [NotNull] DbContext context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't put NotNull annotations on overriding/implementing methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should Check.NotNull still be performed in overriding methods for NotNull parameters?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but Checks are only required for public components (not in .Internal namespace).


namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
class RelationalDbFunctionConvention : IModelAnnotationSetConvention
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public

{
public Annotation Apply(InternalModelBuilder modelBuilder, string name, Annotation annotation, Annotation oldAnnotation)
{
if (name.StartsWith(RelationalFullAnnotationNames.Instance.DbFunctionPrefix))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add StringComparison to StartsWith call

{
Check.NotNull(modelBuilder, nameof(modelBuilder));

var dbFunctionBuilder = new RelationalDbFunctionBuilder(annotation.Value as DbFunction) ;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotation has CanBeNull

ParameterInfo = pi,
DbFuncParamAttr = pi.GetCustomAttributes<DbFunctionParameterAttribute>().SingleOrDefault(),
IsParams = pi.CustomAttributes.Any(ca => ca.AttributeType == typeof(ParamArrayAttribute)),
ParameterType = pi.ParameterType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant name

}
}

return annotation ;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the formatting nit here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space before ';'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea my first TA in college had us do that to more easily see missing semis, back in the era before fancy ides and good compiler errors. 😄 Old habits die hard. I'll find a remove the rest of those.

@pmiddleton
Copy link
Contributor Author

@anpete

I will get these all cleared up tonight and review the remaining files for similar patterns to get those cleaned as well.

@anpete
Copy link
Contributor

anpete commented May 18, 2017

@pmiddleton Not sure if you have R#, but it will help with a lot of these - based on our EFCore.sln.DotSettings file.


private static string BuildAnnotationName(string annotationPrefix, MethodInfo methodInfo)
{
StringBuilder sb = new StringBuilder($"{annotationPrefix}{methodInfo.Name}") ;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var

/// 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 virtual bool HasTranslateCallback => TranslateCallback != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you asking about just the HasTranslateCallback property or the entire TranslateCallback piece?

I added The TranslateCallback as a way for users to provider their own methodcall to SqlFunctionExpression translation in the cases where the fluent api isn't expressive enough. It gives the end user the most flexibility to perform a translation, but will it be too confusing to the average user to leave in there?

The HasTranslateCallback bool property itself can go away - I can move the != null checks to where they are actually used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmiddleton +1 to moving the null checks.

{
Check.NotNull(name, nameof(name));

DbFunctionParameter item;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out var all the things.

{
Check.NotNull(name, nameof(name));

if (shiftParameters == true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant '== true'


var parameters = Parameters;

for (int i = 0; i < parameters.Count; i++)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use var everywhere

_parameters.Remove(name);
}

private bool IsValidReturnType(Type returnType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method can be static.

/// 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 virtual InternalDbFunctionBuilder HasSchema([NotNull] string schema, ConfigurationSource configurationSource)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CanBeNull]

@pmiddleton pmiddleton force-pushed the udfTake2 branch 2 times, most recently from 9285710 to ee7e80d Compare June 7, 2017 04:43
@smitpatel
Copy link
Contributor

@pmiddleton - Also if you are rebasing then rebase on rel/2.0.0-preview2 branch and change base in this PR. If its good and done in time, we may want to put this in preview2 release. It would be easier to rebase/merge into dev in either case.

@AndriySvyryd AndriySvyryd changed the base branch from dev to rel/2.0.0-preview2 June 8, 2017 05:01
Copy link
Member

@AndriySvyryd AndriySvyryd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata changes look good.
Rebase on origin/rel/2.0.0-preview2

@pmiddleton
Copy link
Contributor Author

Ok I need a little git help. What command do I need to run to rebase onto origin/rel/2.0.0-preview2.

I pulled a tracked branch of rel/2.0.0-preview locally, but I'm not sure what to run to perform the rebase.

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Jun 8, 2017

@pmiddleton From your local branch:

git rebase -i origin/rel/2.0.0-preview2

Then remove all the commits that aren't yours from the rebase specification that pops up

@pmiddleton pmiddleton force-pushed the udfTake2 branch 2 times, most recently from 1a737b8 to c802f23 Compare June 8, 2017 15:56
@pmiddleton
Copy link
Contributor Author

Ok I have rebased onto rel/2.0.0-preview2

@AndriySvyryd
Copy link
Member

@pmiddleton There are still 4 commits that aren't yours. Run

git rebase -i origin/rel/2.0.0-preview2

again and make sure to remove them.

@pmiddleton
Copy link
Contributor Author

@AndriySvyryd - I rebased and removed the other 4 commits.

@pmiddleton
Copy link
Contributor Author

@AndriySvyryd - There are conflicts I need to resolve. At this point in the process can I do a pull --rebase to resolve or should I be doing a rebase -i, or does it not matter now?

@anpete anpete merged commit f4d74e6 into dotnet:rel/2.0.0-preview2 Jun 9, 2017
@anpete
Copy link
Contributor

anpete commented Jun 9, 2017

Nice work @pmiddleton!

And thanks to @smitpatel, @AndriySvyryd and @bricelam for the review.

@ajcvickers
Copy link
Member

Great to see this get in for preview2. Thanks to everybody!

@pmiddleton
Copy link
Contributor Author

Thanks to everyone for the help getting this in!

@pmiddleton pmiddleton deleted the udfTake2 branch June 9, 2017 20:11
roji added a commit to roji/efcore.pg that referenced this pull request Jul 22, 2018
Function quoting logic was changed (for now): all function names are
quoted just like any other identifier (the default EF Core behavior
is only to quote schema-less functions). This meant changing all
expression translators to generate lower-case names (lower() instead
of LOWER()), and also to add a hardcoded list of functions which
EF Core generates in upper-case but which shouldn't get quoted
(e.g. SUM()).

See:
* dotnet/efcore#8507
* dotnet/efcore#12044
* dotnet/efcore#12757
* dotnet/efcore#9558
* dotnet/efcore#9303
roji added a commit to roji/efcore.pg that referenced this pull request Aug 3, 2018
Function quoting logic was changed (for now): all function names are
quoted just like any other identifier (the default EF Core behavior
is only to quote schema-less functions). This meant changing all
expression translators to generate lower-case names (lower() instead
of LOWER()), and also to add a hardcoded list of functions which
EF Core generates in upper-case but which shouldn't get quoted
(e.g. SUM()).

See:
* dotnet/efcore#8507
* dotnet/efcore#12044
* dotnet/efcore#12757
* dotnet/efcore#9558
* dotnet/efcore#9303
roji added a commit to roji/efcore.pg that referenced this pull request Aug 12, 2018
For now, only unquoted function names are supported (i.e. all lower-
case, no special chars). Quoting logic for functions is quite
complicated, see below issues for details.

See:
* dotnet/efcore#8507
* dotnet/efcore#12044
* dotnet/efcore#12757
* dotnet/efcore#9558
* dotnet/efcore#9303
roji added a commit to roji/efcore.pg that referenced this pull request Aug 12, 2018
For now, only unquoted function names are supported (i.e. all lower-
case, no special chars). Quoting logic for functions is quite
complicated, see below issues for details.

See:
* dotnet/efcore#8507
* dotnet/efcore#12044
* dotnet/efcore#12757
* dotnet/efcore#9558
* dotnet/efcore#9303
roji added a commit to roji/efcore.pg that referenced this pull request Aug 12, 2018
For now, only unquoted function names are supported (i.e. all lower-
case, no special chars). Quoting logic for functions is quite
complicated, see below issues for details.

See:
* dotnet/efcore#8507
* dotnet/efcore#12044
* dotnet/efcore#12757
* dotnet/efcore#9558
* dotnet/efcore#9303
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants