-
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
DBFunctions - Add support for instance methods. #9755
Conversation
c4fbf7b
to
599595a
Compare
599595a
to
56d8a92
Compare
I just thought of a use case which needs addressing before this can be merged. Currently we allow any static method to be registered via the fluent api regardless of what type it is defined on. However this code change assumes that the instance method is defined on your DbContext. Do we want to only support instance methods on DbContext, or any instance method from any type? Either way a change will need to be made to the code. |
@pmiddleton @anpete @smitpatel I have created #9811 to track entity instance functions, but reading this comment makes me wonder if there are other interesting scenarios, e.g. a function on a DTO we don't want to block. There is also an interplay between instance methods and extension methods. The latter are static methods, but how do we handle the "this" argument? BTW, are we assuming that the instance is the |
We don't care what instance the Currently there is a if check it the code which is used to avoid attempting to translate instance method calls we know can't be translated. By knowing the type the instance method is attached to is of As an alternative we either have to just try translating ever method, or keep a master list of translatable methods (static and instance) which we can look up. The later would actually help performance a bit. Right now when we try to translate a method the code just loops over all possible translations until it finds one that worked. Having a master list would allow us to bypass a bunch of unneeded processing. I am not sure off the top of my head what will happen with extension methods. I am thinking they will require a custom translate callback, but I can confirm that tonight. |
Extension methods will require a custom translation in order to deal with the "this" parameter. It wouldn't be too hard to detected extension methods for DbContext and throw away the this parameter when the method is registered with the model. |
@pmiddleton thanks. I think it would be ok to create separate Iissues for the outstanding questions that may require additional discussion/work, like extension methods and the “master list”, and move on with this PR. @smitpatel, @anpete, thoughts? Re the |
Also regarding state on the |
@divega - What rules do you guys follow for introducing changes to interfaces which will affect 3rd party database providers? |
@pmiddleton short answer is we avoid doing that in patches or minor releases. What is the context? |
I was just thinking about the “master list” idea and how it would help make things cleaner on the backend for instance methods, but it would require adding an interface method which would touch all of the providers. So we are looking at 3.0 before we would be able to do that? |
@pmiddleton without knowing much about the specific context, would introducing a new interface help? I guess it wouldn't help remove the existing code paths that would still need to be there for providers that don't implement it. |
@divega - so which direction do we want to go with this for now? Instance methods just on |
@pmiddleton I am personally ok with having additional things we need to follow up on accounted for in the issue tracker and take this if @anpete and @smitpatel think it is ready. |
56d8a92
to
bae5058
Compare
I've updated the code to allow instance methods to run from any type. I have been thinking about it and I'm not sure if we want to limit instance methods on It may help with in memory, but could there also be a valid use case that we would then be preventing? |
FWIW, I wasn’t thinking about preventing calls to instance methods on different context from succeeding, but about evaluating them in memory (as self-bootstrapped functions) so that they can bring their own data, act based on the state of the correct context instance, use their own global query filters, connection string, provider, etc. BTW, I don’t know for sure from the top of my head how we handle the introduction of a Also, I don’t think it is at all hard to just use the same If in the future anyone figures out how to optimize execution of queries that merge data from multiple context (e.g. reducing the number of round trips or the amount of data transferred over the network) then we would still likely be sending each function evaluation to their own |
Ok that makes way more sense than what I had thought you were looking for. That feature will be blocked by #8864 so lets get this in as it stand now. |
6996b51
to
8c16ecd
Compare
I have cleaned up the last few issues. This is ready for a review now. |
@pmiddleton - We had a discussion about this. Here is brief summary. |
3846e36
to
39d924a
Compare
@smitpatel - Done |
39d924a
to
3d29baa
Compare
@@ -102,7 +104,7 @@ public static IEnumerable<IDbFunction> GetDbFunctions([NotNull] IModel model, [N | |||
} | |||
|
|||
private static string BuildAnnotationName(string annotationPrefix, MethodBase methodBase) | |||
=> $@"{annotationPrefix}{methodBase.Name}({string.Join(",", methodBase.GetParameters().Select(p => p.ParameterType.Name))})"; | |||
=> $@"{annotationPrefix}{methodBase.DeclaringType.ShortDisplayName()}{methodBase.Name}({string.Join(",", methodBase.GetParameters().Select(p => p.ParameterType.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.
This is changing annotation name for DbFunction. Since everything is runtime, it would not break anything particular. But hand-crafted model can break may be.
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 change is actually fixing a bug I ran into. If you declare two static methods on different types with the same name then the annotation name was colliding. We need the type name to differentiate the methods.
See the new unit test DbFunctions_with_duplicate_names_and_parameters_on_different_types_dont_collide
<data name="DbFunctionGenericMethodNotSupported" xml:space="preserve"> | ||
<value>The DbFunction '{function}' is generic. Generic methods are not supported.</value> | ||
</data> | ||
<data name="DbFunctionExpressionIsNotMethodCall" xml:space="preserve"> | ||
<value>The provided DbFunction expression '{expression}' is invalid. The expression should be a lambda expression containing a single method call to the target static method. Default values can be provided as arguments if required. E.g. () => SomeClass.SomeMethod(null, 0)</value> | ||
</data> | ||
<data name="DbFunctionInvalidInstanceType" xml:space="preserve"> | ||
<value>DbFunction instance methods are only supported on DbContext. The method '{function}' is defined on an invalid type '{type}'.</value> |
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 re-word this message. Its not invalid type always. Sometimes user may just need to make it a static method.
cc: @divega, @ajcvickers to suggest exception message.
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.
Yes please. I suck at naming things :)
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.
How about "The DbFunction '{function}' defined on type '{type}' must be either a static method or an instance method defined on a DbContext subclass. Instance methods on other types are not supported."
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.
works for me - change made
LGTM |
3d29baa
to
b4b299c
Compare
Are we waiting on something from me for this? |
@pmiddleton I think it's just that @anpete and @smitpatel have been otherwise engaged for the last week or so. Sorry for the delay--one of them will either merge or respond further soon. |
ah - no rush. I just thought I might owe something yet and missed it. |
.Where( | ||
mi => mi.IsStatic | ||
&& mi.IsPublic | ||
var functions = context.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy) |
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.
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.
The BindingFlags.Public
will filter out the protected static methods as well as protected instance methods.
I think we should only bring in public methods since users shouldn't be putting their queries directly in their DbContext class - imho. Therefore there is no need for protected 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.
Filed #9956
Merged via 0edbe21 |
Resolve #9213
Allows DbFunctions to be instance methods as well as static methods.