-
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
Support primitive collections #30738
Conversation
|
||
// Try to see if a parameter already exists - if so, just integrate the same placeholder into the SQL instead of sending the same | ||
// data twice. | ||
// Note that if the type mapping differs, we do send the same data twice (e.g. the same string may be sent once as Unicode, once as | ||
// non-Unicode). | ||
// TODO: Note that we perform Equals comparison on the value converter. We should be able to do reference comparison, but for |
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 FYI, this is #30677
/// </summary> | ||
/// <param name="selectExpression">The <see cref="SelectExpression" /> to check for ordering.</param> | ||
/// <returns>Whether <paramref name="selectExpression"/> is ordered.</returns> | ||
protected virtual bool IsOrdered(SelectExpression selectExpression) |
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.
Added this for PostgreSQL, where the results of unnest are guaranteed to be ordered without any explicit ORDER BY; this allows us to avoid the warning for Skip/Take over unordered collection.
@@ -1913,13 +1945,4 @@ public Expression Convert(Type type) | |||
: new EntityReferenceExpression(this, derivedEntityType); | |||
} | |||
} | |||
|
|||
private sealed class SqlTypeMappingVerifyingExpressionVisitor : ExpressionVisitor |
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.
Note that this verification - that all SqlExpressions have a mapping - has been moved from the end of each individual SQL translations (of which there are potentially several in the query) to the end of the entire query. This is necessary since some mappings are inferred later.
/// not used in application code. | ||
/// </para> | ||
/// </summary> | ||
public class RowValueExpression : SqlExpression |
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 will also provide infrastructure for other uses of row values, e.g. #26822
public virtual IStoreFunction? StoreFunction { get; } | ||
|
||
/// <inheritdoc /> | ||
ITableBase? ITableBasedExpression.Table |
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.
Before this PR, TableValuedFunctionExpression could only be used with a store function from the model; this changes it to be usable for any TVF, without any necessary model data on it (e.g. OpenJson).
// 3. The element CLR type has a supported type mapping which isn't itself a collection (nested collections not yet supported). | ||
|
||
// Note that e.g. Newtonsoft.Json's JToken is enumerable over itself, exclude that scenario to avoid stack overflow. | ||
if (mappingInfo.ClrType?.TryGetElementType(typeof(IEnumerable<>)) is not { } elementClrType |
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 @AndriySvyryd this is another place where we need some type mapping infra (#30730) - at least some parts of this function should probably be in core/relational; we should also consider having providers just support collections via JSON arrays by default, without them needing to actually do anything (but allow Npgsql to override).
Note also the annoying construction of the collection type mapping below, with the cloning etc.
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
protected override ShapedQueryExpression? TranslateCount(ShapedQueryExpression source, LambdaExpression? predicate) |
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.
Note this specialized translation of LINQ Count() when applied to a primitive collection (JSON string); instead of using json_each to convert to a table and use a SQL scalar subquery to do COUNT(*)
, we just use a dedicated function for that.
Npgsql has a ton of such specialized pattern matching translations - it's really nice. See roji/efcore.pg@83842cf#diff-710c2323c08f93bcf612ff7124133abf291f46900fcf8f0adc89eaf5709d22e9R119 and below.
jsonEachExpression, columnName: "value", columnType: elementClrType, columnTypeMapping: elementTypeMapping, | ||
isColumnNullable: null); | ||
|
||
// TODO: SQLite does have REAL and BLOB types, which JSON does not. Need to possibly cast to that. |
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 "standard" JSON timestamp representation is ISO8601, with a T between date and time; but SQLite's representation has | ||
// no T. Apply a conversion on the value coming out of json_each. | ||
SqliteDateTimeTypeMapping => _sqlExpressionFactory.Function( |
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.
To be replaced by #30727
/// <summary> | ||
/// A value converter that converts a .NET primitive collection into a JSON string. | ||
/// </summary> | ||
// TODO: This currently just calls JsonSerialize.Serialize/Deserialize. It should go through the element type mapping's APIs for |
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 where we'd use the JSON serialization/deserialization APIs on the type mapping, which we'd introduce in #30677.
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.
We'll discuss this in detail in the PR for #30677.
527ca05
to
642e5d7
Compare
src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs
Outdated
Show resolved
Hide resolved
src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs
Outdated
Show resolved
Hide resolved
// So we clone the expression to avoid any side-effects, but that also prevents us from directly comparing columns (since | ||
// the ValuesExpression has been cloned too). | ||
// We should simply be able to pattern-match directly on the projection mappings (currently private) without cloning/applying | ||
var clonedSelect = selectExpression.Clone(); |
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.
@maumar note this change - I think this aligns with how things are supposed to happen in the query pipeline.
Instead of cloning and applying the projection on the SelectExpression in order to check its projections, the right thing seems to be to look at the ShaperExpression instead, and use that the resolve the projection from the selectExpression - without needing to apply projections.
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.
apply projection has some really complicated logic, for stuff like group by or general subquery projection, so that might not be a generally safe approach. But for simple pattern match like here, it should be perfectly fine
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.
OK. Though I did remove cloning and applying the projection even for this simpler case - I now access the projection via the shaper instead (via selectExpression.GetProjection(projectionBindingExpression)). It feels more correct/safer (and even more efficient as we don't clone).
I do think we can probably improve things for this sort of pattern-matching - but we can think about it later.
src/EFCore.SqlServer/Infrastructure/Internal/SqlServerOptionsExtension.cs
Show resolved
Hide resolved
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.
Non-query parts look good enough.
src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
...Core.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs
Show resolved
Hide resolved
...Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
Show resolved
Hide resolved
04e0025
to
6f77314
Compare
src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
Outdated
Show resolved
Hide resolved
query stuff looking good as well! 🚀 |
Closes dotnet#29427 Closes dotnet#30426 Closes dotnet#13617
6f77314
to
95120af
Compare
Implements the bulk of #30731
Closes #29427
Closes #30426
Closes #13617
Closes #30163