Conversation

pmiddleton

@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

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

@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

@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

@anpete

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

@anpete

@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]

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

@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.

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

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

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

@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

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

Ok I have rebased onto rel/2.0.0-preview2

@AndriySvyryd

@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

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

@pmiddleton

@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?

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

Nice work @pmiddleton!

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

@ajcvickers

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

@pmiddleton

Thanks to everyone for the help getting this in!

@pmiddletonpmiddleton 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 . Already have an account? Sign in to comment
None yet
None yet

Successfully merging this pull request may close these issues.