-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Conversation
@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()) |
There was a problem hiding this comment.
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
@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) ; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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) ; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant name
} | ||
} | ||
|
||
return annotation ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space before ';'
There was a problem hiding this comment.
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.
I will get these all cleared up tonight and review the remaining files for similar patterns to get those cleaned as well. |
@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}") ; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needed?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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++) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[CanBeNull]
9285710
to
ee7e80d
Compare
@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. |
There was a problem hiding this 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
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. |
@pmiddleton From your local branch:
Then remove all the commits that aren't yours from the rebase specification that pops up |
1a737b8
to
c802f23
Compare
Ok I have rebased onto rel/2.0.0-preview2 |
@pmiddleton There are still 4 commits that aren't yours. Run
again and make sure to remove them. |
@AndriySvyryd - I rebased and removed the other 4 commits. |
@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? |
Nice work @pmiddleton! And thanks to @smitpatel, @AndriySvyryd and @bricelam for the review. |
Great to see this get in for preview2. Thanks to everybody! |
Thanks to everyone for the help getting this in! |
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
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
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
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
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
@anpete - Here is the new PR for adding UDF support.
This is a major refactor from the last PR.