From 0b5321f72b8da7be28698f0731bf3e997145ed46 Mon Sep 17 00:00:00 2001 From: esentio Date: Sun, 14 Feb 2021 17:34:06 +0100 Subject: [PATCH 1/4] Support for filling properties not defined in model in SELECT queries (WIP) --- .../Generator/CodeGenerator.Include.vb | 116 +++++++ ...electedSelectSqlExpressionCodeGenerator.vb | 3 + .../Builders/SelectSqlExpressionBuilder.vb | 26 ++ .../SelectedSelectSqlExpression.vb | 44 +++ .../SelectedSelectSqlExpressionOfT1T2.vb | 106 +++++++ .../SelectedSelectSqlExpressionOfT1T2T3.vb | 170 ++++++++++ .../SelectedSelectSqlExpressionOfT1T2T3T4.vb | 256 +++++++++++++++ ...SelectedSelectSqlExpressionOfT1T2T3T4T5.vb | 44 +++ ...lectedSelectSqlExpressionOfT1T2T3T4T5T6.vb | 44 +++ ...ctedSelectSqlExpressionOfT1T2T3T4T5T6T7.vb | 44 +++ ...edSelectSqlExpressionOfT1T2T3T4T5T6T7T8.vb | 44 +++ ...SelectSqlExpressionOfT1T2T3T4T5T6T7T8T9.vb | 44 +++ ...ectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10.vb | 44 +++ ...SqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11.vb | 44 +++ ...ExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12.vb | 44 +++ ...ressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13.vb | 44 +++ ...sionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14.vb | 44 +++ ...nOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14T15.vb | 44 +++ .../EntityMemberSetterFactory.vb | 93 ++++++ .../EntityRelationshipSetterFactory.vb | 111 ------- .../Yamo/Internal/EntityMemberSetterCache.vb | 199 ++++++++++++ .../Internal/EntityRelationshipSetterCache.vb | 144 --------- .../Yamo/Internal/ExpressionTranslateMode.vb | 1 + .../Yamo/Internal/Query/EntityReadInfo.vb | 4 +- .../Yamo/Internal/SqlExpressionVisitor.vb | 104 +++++-- ...lectTests.vb => SelectWithExcludeTests.vb} | 4 +- .../Tests/SelectWithIncludeTests.vb | 14 + ...lectTests.vb => SelectWithExcludeTests.vb} | 4 +- .../Tests/SelectWithIncludeTests.vb | 14 + Source/Test/Yamo.Test/Model/Article.vb | 6 + Source/Test/Yamo.Test/Model/ArticlePart.vb | 2 + Source/Test/Yamo.Test/Model/Label.vb | 2 + ...lectTests.vb => SelectWithExcludeTests.vb} | 2 +- .../Yamo.Test/Tests/SelectWithIncludeTests.vb | 291 ++++++++++++++++++ 34 files changed, 1912 insertions(+), 288 deletions(-) create mode 100644 Source/Source/Yamo.CodeGenerator/Generator/CodeGenerator.Include.vb create mode 100644 Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb delete mode 100644 Source/Source/Yamo/Infrastructure/EntityRelationshipSetterFactory.vb create mode 100644 Source/Source/Yamo/Internal/EntityMemberSetterCache.vb delete mode 100644 Source/Source/Yamo/Internal/EntityRelationshipSetterCache.vb rename Source/Test/Yamo.Test.SQLite/Tests/{SelectTests.vb => SelectWithExcludeTests.vb} (70%) create mode 100644 Source/Test/Yamo.Test.SQLite/Tests/SelectWithIncludeTests.vb rename Source/Test/Yamo.Test.SqlServer/Tests/{SelectTests.vb => SelectWithExcludeTests.vb} (71%) create mode 100644 Source/Test/Yamo.Test.SqlServer/Tests/SelectWithIncludeTests.vb rename Source/Test/Yamo.Test/Tests/{SelectTests.vb => SelectWithExcludeTests.vb} (99%) create mode 100644 Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb diff --git a/Source/Source/Yamo.CodeGenerator/Generator/CodeGenerator.Include.vb b/Source/Source/Yamo.CodeGenerator/Generator/CodeGenerator.Include.vb new file mode 100644 index 0000000..c145755 --- /dev/null +++ b/Source/Source/Yamo.CodeGenerator/Generator/CodeGenerator.Include.vb @@ -0,0 +1,116 @@ +Namespace Generator + + Partial Public Class CodeGenerator + + Protected Sub GenerateInclude(builder As CodeBuilder, entityCount As Int32) + Dim limit = 5 + + If entityCount < limit Then + For i = 1 To entityCount + GenerateIncludeWithActionWithOneEntity(builder, i, entityCount) + builder.AppendLine() + Next + + For i = 1 To entityCount + For j = 1 To entityCount + GenerateIncludeWithKeyValueSelectorsWithOneEntity(builder, i, j, entityCount) + builder.AppendLine() + Next + Next + End If + + If 1 < entityCount Then + GenerateIncludeWithActionWithIJoin(builder, entityCount) + builder.AppendLine() + + GenerateIncludeWithKeyValueSelectorsWithIJoin(builder, entityCount) + builder.AppendLine() + End If + + GenerateInternalIncludeWithAction(builder, entityCount) + builder.AppendLine() + + GenerateInternalIncludeWithKeyValueSelectors(builder, entityCount) + End Sub + + Protected Sub GenerateIncludeWithActionWithOneEntity(builder As CodeBuilder, index As Int32, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim params = {"action"} + AddComment(builder, comment, params:=params, returns:="") + + Dim generic = GetGenericName(index, index = entityCount) + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Public Function Include(action As Expression(Of Action(Of {generic}))) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine($"Return InternalInclude(action, {GetEntityIndexHintsForEntity(index - 1)})").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + Protected Sub GenerateIncludeWithKeyValueSelectorsWithOneEntity(builder As CodeBuilder, index1 As Int32, index2 As Int32, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim typeParams = {"TProperty"} + Dim params = {"keySelector", "valueSelector"} + AddComment(builder, comment, typeParams:=typeParams, params:=params, returns:="") + + Dim generic1 = GetGenericName(index1, index1 = entityCount) + Dim generic2 = GetGenericName(index2, index2 = entityCount) + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of {generic1}, TProperty)), valueSelector As Expression(Of Func(Of {generic2}, TProperty))) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine($"Return InternalInclude(keySelector, valueSelector, {GetEntityIndexHintsForEntity(index1 - 1)}, {GetEntityIndexHintsForEntity(index2 - 1)})").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + Protected Sub GenerateIncludeWithActionWithIJoin(builder As CodeBuilder, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim params = {"action"} + AddComment(builder, comment, params:=params, returns:="") + + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Public Function Include(action As Expression(Of Action(Of Join(Of {generics})))) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine("Return InternalInclude(action, Nothing)").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + Protected Sub GenerateIncludeWithKeyValueSelectorsWithIJoin(builder As CodeBuilder, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim typeParams = {"TProperty"} + Dim params = {"keySelector", "valueSelector"} + AddComment(builder, comment, typeParams:=typeParams, params:=params, returns:="") + + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of {generics}), TProperty)), valueSelector As Expression(Of Func(Of Join(Of {generics}), TProperty))) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine("Return InternalInclude(keySelector, valueSelector, Nothing, Nothing)").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + Protected Sub GenerateInternalIncludeWithAction(builder As CodeBuilder, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim params = {"action", "entityIndexHints"} + AddComment(builder, comment, params:=params, returns:="") + + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine("Me.Builder.IncludeToSelected(action, entityIndexHints)") + builder.Indent().AppendLine("Return Me").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + Protected Sub GenerateInternalIncludeWithKeyValueSelectors(builder As CodeBuilder, entityCount As Int32) + Dim comment = "Includes <column(s)> to SELECT clause." + Dim params = {"keySelector", "valueSelector", "keySelectorEntityIndexHints", "valueSelectorEntityIndexHints"} + AddComment(builder, comment, params:=params, returns:="") + + Dim generics = String.Join(", ", GetGenericNames(entityCount)) + + builder.Indent().AppendLine($"Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of {generics})").PushIndent() + builder.Indent().AppendLine("Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints)") + builder.Indent().AppendLine("Return Me").PopIndent() + builder.Indent().AppendLine("End Function") + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo.CodeGenerator/Generator/SelectedSelectSqlExpressionCodeGenerator.vb b/Source/Source/Yamo.CodeGenerator/Generator/SelectedSelectSqlExpressionCodeGenerator.vb index 60978ab..7e92464 100644 --- a/Source/Source/Yamo.CodeGenerator/Generator/SelectedSelectSqlExpressionCodeGenerator.vb +++ b/Source/Source/Yamo.CodeGenerator/Generator/SelectedSelectSqlExpressionCodeGenerator.vb @@ -35,6 +35,9 @@ GenerateExclude(builder, entityCount) builder.AppendLine() + GenerateInclude(builder, entityCount) + builder.AppendLine() + GenerateDistinct(builder, entityCount) builder.AppendLine() diff --git a/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb b/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb index 129fd1d..f039bec 100644 --- a/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb +++ b/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb @@ -663,6 +663,32 @@ Namespace Expressions.Builders m_Model.GetEntity(entityIndex).Exclude() End Sub + ''' + ''' Includes selected property.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub IncludeToSelected(action As Expression, entityIndexHints As Int32()) + ' TODO: SIP - implement + 'Dim result = m_Visitor.TranslateInclude(action, entityIndexHints, m_Parameters.Count) + 'm_SelectExpression = result.SqlString.Sql + 'm_Parameters.AddRange(result.SqlString.Parameters) + 'm_Model.SetCustomEntities(result.CustomEntities) + End Sub + + ''' + ''' Includes selected property.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Sub IncludeToSelected(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) + ' TODO: SIP - implement + End Sub + ''' ''' Adds select count.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpression.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpression.vb index 97bc415..a09c300 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpression.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpression.vb @@ -40,6 +40,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T))) As SelectedSelectSqlExpression(Of T) + Return InternalInclude(action, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T, TProperty)), valueSelector As Expression(Of Func(Of T, TProperty))) As SelectedSelectSqlExpression(Of T) + Return InternalInclude(keySelector, valueSelector, {0}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2.vb index 2cbe0d4..de88d68 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2.vb @@ -79,6 +79,112 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T1))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(action, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T2))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(action, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(keySelector, valueSelector, {0}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(keySelector, valueSelector, {0}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(keySelector, valueSelector, {1}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(keySelector, valueSelector, {1}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2)))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2), TProperty))) As SelectedSelectSqlExpression(Of T1, T2) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3.vb index 6d5b474..c8146b6 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3.vb @@ -98,6 +98,176 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T1))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(action, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T2))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(action, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T3))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(action, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {0}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {0}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {0}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {1}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {1}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {1}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {2}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {2}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, {2}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3)))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4.vb index 0552ad4..4f61929 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4.vb @@ -117,6 +117,262 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T1))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(action, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T2))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(action, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T3))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(action, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of T4))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(action, {3}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {0}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {0}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {0}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T1, TProperty)), valueSelector As Expression(Of Func(Of T4, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {0}, {3}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {1}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {1}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {1}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T2, TProperty)), valueSelector As Expression(Of Func(Of T4, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {1}, {3}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {2}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {2}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {2}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T3, TProperty)), valueSelector As Expression(Of Func(Of T4, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {2}, {3}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T4, TProperty)), valueSelector As Expression(Of Func(Of T1, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {3}, {0}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T4, TProperty)), valueSelector As Expression(Of Func(Of T2, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {3}, {1}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T4, TProperty)), valueSelector As Expression(Of Func(Of T3, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {3}, {2}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of T4, TProperty)), valueSelector As Expression(Of Func(Of T4, TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, {3}, {3}) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5.vb index f0113c1..63f0727 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5.vb @@ -136,6 +136,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6.vb index 2ec7a97..a9f01b3 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6.vb @@ -155,6 +155,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7.vb index 24a391d..62d10ac 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7.vb @@ -174,6 +174,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8.vb index 2fb8759..2af0927 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8.vb @@ -113,6 +113,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9.vb index 061f5c7..d00332b 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9.vb @@ -122,6 +122,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10.vb index 99adb45..3a76f7c 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10.vb @@ -131,6 +131,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11.vb index 30d7ca8..6e6bc66 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11.vb @@ -140,6 +140,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12.vb index c5d693b..e0d1335 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12.vb @@ -149,6 +149,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13.vb index 74812d6..3cea2ce 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13.vb @@ -158,6 +158,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14.vb index cd8c02e..f7ddbde 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14.vb @@ -167,6 +167,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14T15.vb b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14T15.vb index 5c04e4b..4ecceb8 100644 --- a/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14T15.vb +++ b/Source/Source/Yamo/Expressions/SelectedSelectSqlExpressionOfT1T2T3T4T5T6T7T8T9T10T11T12T13T14T15.vb @@ -176,6 +176,50 @@ Namespace Expressions Return Me End Function + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + Public Function Include(action As Expression(Of Action(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) + Return InternalInclude(action, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + Public Function Include(Of TProperty)(keySelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), TProperty)), valueSelector As Expression(Of Func(Of Join(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), TProperty))) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) + Return InternalInclude(keySelector, valueSelector, Nothing, Nothing) + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + Private Function InternalInclude(action As Expression, entityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) + Me.Builder.IncludeToSelected(action, entityIndexHints) + Return Me + End Function + + ''' + ''' Includes <column(s)> to SELECT clause. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function InternalInclude(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) As SelectedSelectSqlExpression(Of T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) + Me.Builder.IncludeToSelected(keySelector, valueSelector, keySelectorEntityIndexHints, valueSelectorEntityIndexHints) + Return Me + End Function + ''' ''' Adds DISTINCT clause. ''' diff --git a/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb b/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb new file mode 100644 index 0000000..3fb8b7e --- /dev/null +++ b/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb @@ -0,0 +1,93 @@ +Imports System.Data +Imports System.Linq.Expressions +Imports Yamo.Metadata + +Namespace Infrastructure + + ''' + ''' Entity member setter factory.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class EntityMemberSetterFactory + + ''' + ''' Creates property or field setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function CreateSetter(entityType As Type, propertyOrFieldName As String, valueType As Type) As Action(Of Object, Object) + Dim entityParam = Expression.Parameter(GetType(Object), "entity") + Dim valueParam = Expression.Parameter(GetType(Object), "value") + Dim parameters = {entityParam, valueParam} + + Dim entityCasted = Expression.Convert(entityParam, entityType) + Dim valueCasted = Expression.Convert(valueParam, valueType) + + Dim prop = Expression.PropertyOrField(entityCasted, propertyOrFieldName) + Dim propAssign = Expression.Assign(prop, valueCasted) + + Dim body = Expression.Block(propAssign) + + Dim setter = Expression.Lambda(Of Action(Of Object, Object))(body, parameters) + Return setter.Compile() + End Function + + ''' + ''' Creates setter that adds an item to the collection of a property or a field.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function CreateCollectionAddSetter(entityType As Type, propertyOrFieldName As String, itemType As Type) As Action(Of Object, Object) + Dim entityParam = Expression.Parameter(GetType(Object), "entity") + Dim valueParam = Expression.Parameter(GetType(Object), "value") + Dim parameters = {entityParam, valueParam} + + Dim entityCasted = Expression.Convert(entityParam, entityType) + Dim valueCasted = Expression.Convert(valueParam, itemType) + + Dim prop = Expression.PropertyOrField(entityCasted, propertyOrFieldName) + Dim propAdd = Expression.Call(prop, "Add", Nothing, valueCasted) + + Dim body = Expression.Block(propAdd) + + Dim setter = Expression.Lambda(Of Action(Of Object, Object))(body, parameters) + Return setter.Compile() + End Function + + ''' + ''' Creates property or field setter that initializes a collection.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + ''' + Public Shared Function CreateCollectionInitSetter(entityType As Type, propertyOrFieldName As String, collectionType As Type, itemType As Type) As Action(Of Object) + Dim entityParam = Expression.Parameter(GetType(Object), "entity") + Dim parameters = {entityParam} + + Dim entityCasted = Expression.Convert(entityParam, entityType) + + If collectionType.IsInterface Then + collectionType = GetType(List(Of )).MakeGenericType(itemType) + End If + + Dim value = Expression.[New](collectionType) + Dim prop = Expression.PropertyOrField(entityCasted, propertyOrFieldName) + Dim propAssign = Expression.Assign(prop, value) + Dim isNull = Expression.Equal(prop, Expression.Constant(Nothing)) + Dim cond = Expression.IfThen(isNull, propAssign) + + Dim setter = Expression.Lambda(Of Action(Of Object))(cond, parameters) + Return setter.Compile() + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Infrastructure/EntityRelationshipSetterFactory.vb b/Source/Source/Yamo/Infrastructure/EntityRelationshipSetterFactory.vb deleted file mode 100644 index 64349eb..0000000 --- a/Source/Source/Yamo/Infrastructure/EntityRelationshipSetterFactory.vb +++ /dev/null @@ -1,111 +0,0 @@ -Imports System.Data -Imports System.Linq.Expressions -Imports Yamo.Metadata - -Namespace Infrastructure - - ''' - ''' Entity relationship setter factory.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class EntityRelationshipSetterFactory - - ''' - ''' Creates setter.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function CreateSetter(model As Model, entityType As Type, relationshipNavigation As RelationshipNavigation) As Action(Of Object, Object) - Select Case relationshipNavigation.GetType() - Case GetType(ReferenceNavigation) - Return CreateReferenceSetter(model, entityType, DirectCast(relationshipNavigation, ReferenceNavigation)) - Case GetType(CollectionNavigation) - Return CreateCollectionSetter(model, entityType, DirectCast(relationshipNavigation, CollectionNavigation)) - Case Else - Throw New NotSupportedException($"Relationship of type '{relationshipNavigation.GetType()}' is not supported.") - End Select - End Function - - ''' - ''' Creates reference setter. - ''' - ''' - ''' - ''' - ''' - Private Shared Function CreateReferenceSetter(model As Model, entityType As Type, referenceNavigation As ReferenceNavigation) As Action(Of Object, Object) - Dim entityParam = Expression.Parameter(GetType(Object), "entity") - Dim valueParam = Expression.Parameter(GetType(Object), "value") - Dim parameters = {entityParam, valueParam} - - Dim entityCasted = Expression.Convert(entityParam, entityType) - Dim valueCasted = Expression.Convert(valueParam, referenceNavigation.RelatedEntityType) - - Dim prop = Expression.Property(entityCasted, referenceNavigation.PropertyName) - Dim propAssign = Expression.Assign(prop, valueCasted) - - Dim body = Expression.Block(propAssign) - - Dim setter = Expression.Lambda(Of Action(Of Object, Object))(body, parameters) - Return setter.Compile() - End Function - - ''' - ''' Creates collection setter. - ''' - ''' - ''' - ''' - ''' - Private Shared Function CreateCollectionSetter(model As Model, entityType As Type, collectionNavigation As CollectionNavigation) As Action(Of Object, Object) - Dim entityParam = Expression.Parameter(GetType(Object), "entity") - Dim valueParam = Expression.Parameter(GetType(Object), "value") - Dim parameters = {entityParam, valueParam} - - Dim entityCasted = Expression.Convert(entityParam, entityType) - Dim valueCasted = Expression.Convert(valueParam, collectionNavigation.RelatedEntityType) - - Dim prop = Expression.Property(entityCasted, collectionNavigation.PropertyName) - Dim propAdd = Expression.Call(prop, "Add", Nothing, valueCasted) - - Dim body = Expression.Block(propAdd) - - Dim setter = Expression.Lambda(Of Action(Of Object, Object))(body, parameters) - Return setter.Compile() - End Function - - ''' - ''' Creates collection init setter.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function CreateCollectionInitSetter(model As Model, entityType As Type, collectionNavigation As CollectionNavigation) As Action(Of Object) - Dim entityParam = Expression.Parameter(GetType(Object), "entity") - Dim parameters = {entityParam} - - Dim entityCasted = Expression.Convert(entityParam, entityType) - - Dim collectionType = collectionNavigation.CollectionType - - If collectionType.IsInterface Then - collectionType = GetType(List(Of )).MakeGenericType(collectionNavigation.RelatedEntityType) - End If - - Dim value = Expression.[New](collectionType) - Dim prop = Expression.Property(entityCasted, collectionNavigation.PropertyName) - Dim propAssign = Expression.Assign(prop, value) - Dim isNull = Expression.Equal(prop, Expression.Constant(Nothing)) - Dim cond = Expression.IfThen(isNull, propAssign) - - Dim setter = Expression.Lambda(Of Action(Of Object))(cond, parameters) - Return setter.Compile() - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/EntityMemberSetterCache.vb b/Source/Source/Yamo/Internal/EntityMemberSetterCache.vb new file mode 100644 index 0000000..c3d2733 --- /dev/null +++ b/Source/Source/Yamo/Internal/EntityMemberSetterCache.vb @@ -0,0 +1,199 @@ +Imports System.Data +Imports Yamo.Infrastructure +Imports Yamo.Metadata + +Namespace Internal + + ''' + ''' Entity member setter cache.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class EntityMemberSetterCache + + ''' + ''' Stores cache instances. + ''' + Private Shared m_Instances As Dictionary(Of Model, EntityMemberSetterCache) + + ''' + ''' Stores cached setter instances. + ''' + Private m_Setters As Dictionary(Of (Type, String), Action(Of Object, Object)) + + ''' + ''' Stores cached collection add setter instances. + ''' + Private m_CollectionAddSetters As Dictionary(Of (Type, String), Action(Of Object, Object)) + + ''' + ''' Stores cached collection init setter instances. + ''' + Private m_CollectionInitSetters As Dictionary(Of (Type, String), Action(Of Object)) + + ''' + ''' Initializes related static data. + ''' + Shared Sub New() + m_Instances = New Dictionary(Of Model, EntityMemberSetterCache) + End Sub + + ''' + ''' Creates new instance of . + ''' + Private Sub New() + m_Setters = New Dictionary(Of (Type, String), Action(Of Object, Object)) + m_CollectionAddSetters = New Dictionary(Of (Type, String), Action(Of Object, Object)) + m_CollectionInitSetters = New Dictionary(Of (Type, String), Action(Of Object)) + End Sub + + ''' + ''' Gets setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function GetSetter(model As Model, entityType As Type, relationshipNavigation As RelationshipNavigation) As Action(Of Object, Object) + If TypeOf relationshipNavigation Is ReferenceNavigation Then + Return GetInstance(model).GetOrCreateSetter(entityType, relationshipNavigation.PropertyName, relationshipNavigation.RelatedEntityType) + ElseIf TypeOf relationshipNavigation Is CollectionNavigation Then + Return GetInstance(model).GetOrCreateCollectionAddSetter(entityType, relationshipNavigation.PropertyName, relationshipNavigation.RelatedEntityType) + Else + Throw New NotSupportedException($"Relationship of type '{relationshipNavigation.GetType()}' is not supported.") + End If + End Function + + ''' + ''' Gets setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + ''' + Public Shared Function GetSetter(model As Model, entityType As Type, propertyOrFieldName As String, valueType As Type) As Action(Of Object, Object) + Return GetInstance(model).GetOrCreateSetter(entityType, propertyOrFieldName, valueType) + End Function + + ''' + ''' Gets collection init setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function GetCollectionInitSetter(model As Model, entityType As Type, collectionNavigation As CollectionNavigation) As Action(Of Object) + Return GetInstance(model).GetOrCreateCollectionInitSetter(entityType, collectionNavigation.PropertyName, collectionNavigation.CollectionType, collectionNavigation.RelatedEntityType) + End Function + + ''' + ''' Gets cache instance. If it doesn't exist, it is created. + ''' + ''' + ''' + Private Shared Function GetInstance(model As Model) As EntityMemberSetterCache + Dim instance As EntityMemberSetterCache = Nothing + + SyncLock m_Instances + If Not m_Instances.TryGetValue(model, instance) Then + instance = New EntityMemberSetterCache + m_Instances.Add(model, instance) + End If + End SyncLock + + Return instance + End Function + + ''' + ''' Gets or creates setter. + ''' + ''' + ''' + ''' + ''' + Private Function GetOrCreateSetter(entityType As Type, propertyOrFieldName As String, valueType As Type) As Action(Of Object, Object) + Dim setter As Action(Of Object, Object) = Nothing + + Dim key = (entityType, propertyOrFieldName) + + SyncLock m_Setters + m_Setters.TryGetValue(key, setter) + End SyncLock + + If setter Is Nothing Then + setter = EntityMemberSetterFactory.CreateSetter(entityType, propertyOrFieldName, valueType) + Else + Return setter + End If + + SyncLock m_Setters + m_Setters(key) = setter + End SyncLock + + Return setter + End Function + + ''' + ''' Gets or creates collection add setter. + ''' + ''' + ''' + ''' + ''' + Private Function GetOrCreateCollectionAddSetter(entityType As Type, propertyOrFieldName As String, itemType As Type) As Action(Of Object, Object) + Dim setter As Action(Of Object, Object) = Nothing + + Dim key = (entityType, propertyOrFieldName) + + SyncLock m_CollectionAddSetters + m_CollectionAddSetters.TryGetValue(key, setter) + End SyncLock + + If setter Is Nothing Then + setter = EntityMemberSetterFactory.CreateCollectionAddSetter(entityType, propertyOrFieldName, itemType) + Else + Return setter + End If + + SyncLock m_CollectionAddSetters + m_CollectionAddSetters(key) = setter + End SyncLock + + Return setter + End Function + + ''' + ''' Gets or creates collection init setter. + ''' + ''' + ''' + ''' + ''' + ''' + Private Function GetOrCreateCollectionInitSetter(entityType As Type, propertyOrFieldName As String, collectionType As Type, itemType As Type) As Action(Of Object) + Dim setter As Action(Of Object) = Nothing + + Dim key = (entityType, propertyOrFieldName) + + SyncLock m_CollectionInitSetters + m_CollectionInitSetters.TryGetValue(key, setter) + End SyncLock + + If setter Is Nothing Then + setter = EntityMemberSetterFactory.CreateCollectionInitSetter(entityType, propertyOrFieldName, collectionType, itemType) + Else + Return setter + End If + + SyncLock m_CollectionInitSetters + m_CollectionInitSetters(key) = setter + End SyncLock + + Return setter + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/EntityRelationshipSetterCache.vb b/Source/Source/Yamo/Internal/EntityRelationshipSetterCache.vb deleted file mode 100644 index 402215e..0000000 --- a/Source/Source/Yamo/Internal/EntityRelationshipSetterCache.vb +++ /dev/null @@ -1,144 +0,0 @@ -Imports System.Data -Imports Yamo.Infrastructure -Imports Yamo.Metadata - -Namespace Internal - - ''' - ''' Entity relationship setter cache.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class EntityRelationshipSetterCache - - ''' - ''' Stores cache instances. - ''' - Private Shared m_Instances As Dictionary(Of Model, EntityRelationshipSetterCache) - - ''' - ''' Stores cached setter instances. - ''' - Private m_Setters As Dictionary(Of (Type, String), Action(Of Object, Object)) - - ''' - ''' Stores cached collection init setter instances. - ''' - Private m_CollectionInitSetters As Dictionary(Of (Type, String), Action(Of Object)) - - ''' - ''' Initializes related static data. - ''' - Shared Sub New() - m_Instances = New Dictionary(Of Model, EntityRelationshipSetterCache) - End Sub - - ''' - ''' Creates new instance of . - ''' - Private Sub New() - m_Setters = New Dictionary(Of (Type, String), Action(Of Object, Object)) - m_CollectionInitSetters = New Dictionary(Of (Type, String), Action(Of Object)) - End Sub - - ''' - ''' Gets setter.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function GetSetter(model As Model, entityType As Type, relationshipNavigation As RelationshipNavigation) As Action(Of Object, Object) - Return GetInstance(model).GetOrCreateSetter(model, entityType, relationshipNavigation) - End Function - - ''' - ''' Gets collection init setter.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function GetCollectionInitSetter(model As Model, entityType As Type, collectionNavigation As CollectionNavigation) As Action(Of Object) - Return GetInstance(model).GetOrCreateCollectionInitSetter(model, entityType, collectionNavigation) - End Function - - ''' - ''' Gets cache instance. If it doesn't exist, it is created. - ''' - ''' - ''' - Private Shared Function GetInstance(model As Model) As EntityRelationshipSetterCache - Dim instance As EntityRelationshipSetterCache = Nothing - - SyncLock m_Instances - If Not m_Instances.TryGetValue(model, instance) Then - instance = New EntityRelationshipSetterCache - m_Instances.Add(model, instance) - End If - End SyncLock - - Return instance - End Function - - ''' - ''' Gets or creates setter. - ''' - ''' - ''' - ''' - ''' - Private Function GetOrCreateSetter(model As Model, entityType As Type, relationshipNavigation As RelationshipNavigation) As Action(Of Object, Object) - Dim setter As Action(Of Object, Object) = Nothing - - Dim key = (entityType, relationshipNavigation.PropertyName) - - SyncLock m_Setters - m_Setters.TryGetValue(key, setter) - End SyncLock - - If setter Is Nothing Then - setter = EntityRelationshipSetterFactory.CreateSetter(model, entityType, relationshipNavigation) - Else - Return setter - End If - - SyncLock m_Setters - m_Setters(key) = setter - End SyncLock - - Return setter - End Function - - ''' - ''' Gets or creates collection init setter. - ''' - ''' - ''' - ''' - ''' - Private Function GetOrCreateCollectionInitSetter(model As Model, entityType As Type, collectionNavigation As CollectionNavigation) As Action(Of Object) - Dim setter As Action(Of Object) = Nothing - - Dim key = (entityType, collectionNavigation.PropertyName) - - SyncLock m_CollectionInitSetters - m_CollectionInitSetters.TryGetValue(key, setter) - End SyncLock - - If setter Is Nothing Then - setter = EntityRelationshipSetterFactory.CreateCollectionInitSetter(model, entityType, collectionNavigation) - Else - Return setter - End If - - SyncLock m_CollectionInitSetters - m_CollectionInitSetters(key) = setter - End SyncLock - - Return setter - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/ExpressionTranslateMode.vb b/Source/Source/Yamo/Internal/ExpressionTranslateMode.vb index 85f84fa..ab71447 100644 --- a/Source/Source/Yamo/Internal/ExpressionTranslateMode.vb +++ b/Source/Source/Yamo/Internal/ExpressionTranslateMode.vb @@ -9,6 +9,7 @@ GroupBy OrderBy CustomSelect + Include [Set] End Enum End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb b/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb index 79c690c..eacc1e9 100644 --- a/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb +++ b/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb @@ -176,7 +176,7 @@ Namespace Internal.Query Dim collectionInitializers = New Action(Of Object)(collectionNavigations.Count - 1) {} For i = 0 To collectionNavigations.Count - 1 - collectionInitializers(i) = EntityRelationshipSetterCache.GetCollectionInitSetter(model, entity.Entity.EntityType, collectionNavigations(i)) + collectionInitializers(i) = EntityMemberSetterCache.GetCollectionInitSetter(model, entity.Entity.EntityType, collectionNavigations(i)) Next readInfo._CollectionInitializers = collectionInitializers @@ -187,7 +187,7 @@ Namespace Internal.Query If entity.Relationship Is Nothing Then readInfo._RelationshipSetter = Nothing Else - readInfo._RelationshipSetter = EntityRelationshipSetterCache.GetSetter(model, entity.Relationship.DeclaringEntity.Entity.EntityType, entity.Relationship.RelationshipNavigation) + readInfo._RelationshipSetter = EntityMemberSetterCache.GetSetter(model, entity.Relationship.DeclaringEntity.Entity.EntityType, entity.Relationship.RelationshipNavigation) End If Return readInfo diff --git a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb index 2dc2d08..9e9d70a 100644 --- a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb +++ b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb @@ -105,23 +105,15 @@ Namespace Internal End Sub ''' - ''' Translates expression to SQL string.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + ''' Initializes values before translate call. '''
- ''' + ''' ''' - ''' Null for and not null for . + ''' ''' ''' ''' - ''' - Public Function Translate(expression As Expression, mode As ExpressionTranslateMode, entityIndexHints As Int32(), parameterIndex As Int32, useAliases As Boolean, useTableNamesOrAliases As Boolean) As SqlString - If TypeOf expression IsNot LambdaExpression Then - Throw New ArgumentException("Expression must be of type LambdaExpression.") - End If - - Dim lambda = DirectCast(expression, LambdaExpression) - + Private Sub Initialize(lambda As LambdaExpression, mode As ExpressionTranslateMode, entityIndexHints As Int32(), parameterIndex As Int32, useAliases As Boolean, useTableNamesOrAliases As Boolean) m_Mode = mode m_ExpressionParameters = lambda.Parameters m_ExpressionParametersType = If(entityIndexHints Is Nothing, ExpressionParametersType.IJoin, ExpressionParametersType.Entities) @@ -135,6 +127,27 @@ Namespace Internal m_CustomEntities = Nothing m_CustomEntityIndex = 0 m_Stack.Clear() + End Sub + + ''' + ''' Translates expression to SQL string.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' for and not for . + ''' + ''' + ''' + ''' + Public Function Translate(expression As Expression, mode As ExpressionTranslateMode, entityIndexHints As Int32(), parameterIndex As Int32, useAliases As Boolean, useTableNamesOrAliases As Boolean) As SqlString + If TypeOf expression IsNot LambdaExpression Then + Throw New ArgumentException("Expression must be of type LambdaExpression.") + End If + + Dim lambda = DirectCast(expression, LambdaExpression) + + Initialize(lambda, mode, entityIndexHints, parameterIndex, useAliases, useTableNamesOrAliases) Visit(lambda.Body) @@ -148,7 +161,7 @@ Namespace Internal ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - ''' Null for and not null for . + ''' for and not for . ''' ''' Public Function TranslateCustomSelect(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, CustomEntities As CustomSqlEntity()) @@ -158,19 +171,7 @@ Namespace Internal Dim lambda = DirectCast(expression, LambdaExpression) - m_Mode = ExpressionTranslateMode.CustomSelect - m_ExpressionParameters = lambda.Parameters - m_ExpressionParametersType = If(entityIndexHints Is Nothing, ExpressionParametersType.IJoin, ExpressionParametersType.Entities) - m_EntityIndexHints = entityIndexHints - m_Sql = New StringBuilder() - m_Parameters = New List(Of SqlParameter) - m_ParameterIndex = parameterIndex - m_UseAliases = True - m_UseTableNamesOrAliases = True - m_CurrentLikeParameterFormat = Nothing - m_CustomEntities = Nothing - m_CustomEntityIndex = 0 - m_Stack.Clear() + Initialize(lambda, ExpressionTranslateMode.CustomSelect, entityIndexHints, parameterIndex, True, True) VisitInCustomSelectMode(lambda.Body) @@ -181,6 +182,57 @@ Namespace Internal Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomEntities) End Function + 'Public Function TranslateInclude(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, CustomEntities As CustomSqlEntity()) + ' If TypeOf expression IsNot LambdaExpression Then + ' Throw New ArgumentException("Expression must be of type LambdaExpression.") + ' End If + + ' Dim lambda = DirectCast(expression, LambdaExpression) + + ' Initialize(lambda, ExpressionTranslateMode.Include, entityIndexHints, parameterIndex, True, True) + + ' If Not lambda.Body.NodeType = ExpressionType.Call Then + ' Throw New Exception($"Cannot process the expression. Body NodeType {lambda.Body.NodeType} is not allowed.") + ' End If + + ' Dim node = DirectCast(lambda.Body, MethodCallExpression) + ' Dim isEntity = False + ' Dim isJoinedEntity = False + + ' If node.Method.IsSpecialName AndAlso node.Object IsNot Nothing AndAlso node.Method.Name.StartsWith("set_") Then + ' isEntity = Me.IsEntity(node.Object) + ' isJoinedEntity = Me.IsJoinedEntity(node.Object) + ' End If + + ' Dim entityIndex As Int32 + + ' If isEntity Then + ' entityIndex = GetEntityIndex(DirectCast(node.Object, ParameterExpression)) + ' ElseIf isJoinedEntity Then + ' entityIndex = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Object, MemberExpression).Member.Name) + ' Else + ' Throw New Exception("Cannot process the expression.") + ' End If + + ' Dim propertyName = node.Method.Name.Substring(4) ' trim "set_" + + + + ' Dim arg = node.Arguments(0) + + + + + + ' VisitInCustomSelectMode(lambda.Body) + + ' m_ExpressionParameters = Nothing + + ' CustomResultReaderCache.CreateResultFactoryIfNotExists(m_Model.Model, lambda.Body, m_CustomEntities) + + ' Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomEntities) + 'End Function + ''' ''' Visits expression.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. diff --git a/Source/Test/Yamo.Test.SQLite/Tests/SelectTests.vb b/Source/Test/Yamo.Test.SQLite/Tests/SelectWithExcludeTests.vb similarity index 70% rename from Source/Test/Yamo.Test.SQLite/Tests/SelectTests.vb rename to Source/Test/Yamo.Test.SQLite/Tests/SelectWithExcludeTests.vb index 74c34e4..bf721de 100644 --- a/Source/Test/Yamo.Test.SQLite/Tests/SelectTests.vb +++ b/Source/Test/Yamo.Test.SQLite/Tests/SelectWithExcludeTests.vb @@ -3,8 +3,8 @@ Namespace Tests - Public Class SelectTests - Inherits Yamo.Test.Tests.SelectTests + Public Class SelectWithExcludeTests + Inherits Yamo.Test.Tests.SelectWithExcludeTests Protected Overrides Function CreateTestEnvironment() As ITestEnvironment Return SQLiteTestEnvironment.Create() diff --git a/Source/Test/Yamo.Test.SQLite/Tests/SelectWithIncludeTests.vb b/Source/Test/Yamo.Test.SQLite/Tests/SelectWithIncludeTests.vb new file mode 100644 index 0000000..42a52b9 --- /dev/null +++ b/Source/Test/Yamo.Test.SQLite/Tests/SelectWithIncludeTests.vb @@ -0,0 +1,14 @@ +Imports Yamo.Test + +Namespace Tests + + + Public Class SelectWithIncludeTests + Inherits Yamo.Test.Tests.SelectWithIncludeTests + + Protected Overrides Function CreateTestEnvironment() As ITestEnvironment + Return SQLiteTestEnvironment.Create() + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Test/Yamo.Test.SqlServer/Tests/SelectTests.vb b/Source/Test/Yamo.Test.SqlServer/Tests/SelectWithExcludeTests.vb similarity index 71% rename from Source/Test/Yamo.Test.SqlServer/Tests/SelectTests.vb rename to Source/Test/Yamo.Test.SqlServer/Tests/SelectWithExcludeTests.vb index ed95a5a..ba46d63 100644 --- a/Source/Test/Yamo.Test.SqlServer/Tests/SelectTests.vb +++ b/Source/Test/Yamo.Test.SqlServer/Tests/SelectWithExcludeTests.vb @@ -3,8 +3,8 @@ Namespace Tests - Public Class SelectTests - Inherits Yamo.Test.Tests.SelectTests + Public Class SelectWithExcludeTests + Inherits Yamo.Test.Tests.SelectWithExcludeTests Protected Overrides Function CreateTestEnvironment() As ITestEnvironment Return SqlServerTestEnvironment.Create() diff --git a/Source/Test/Yamo.Test.SqlServer/Tests/SelectWithIncludeTests.vb b/Source/Test/Yamo.Test.SqlServer/Tests/SelectWithIncludeTests.vb new file mode 100644 index 0000000..d227e41 --- /dev/null +++ b/Source/Test/Yamo.Test.SqlServer/Tests/SelectWithIncludeTests.vb @@ -0,0 +1,14 @@ +Imports Yamo.Test + +Namespace Tests + + + Public Class SelectWithIncludeTests + Inherits Yamo.Test.Tests.SelectWithIncludeTests + + Protected Overrides Function CreateTestEnvironment() As ITestEnvironment + Return SqlServerTestEnvironment.Create() + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Test/Yamo.Test/Model/Article.vb b/Source/Test/Yamo.Test/Model/Article.vb index c3bad53..887eb99 100644 --- a/Source/Test/Yamo.Test/Model/Article.vb +++ b/Source/Test/Yamo.Test/Model/Article.vb @@ -16,6 +16,12 @@ Public Property AlternativeLabel2 As Label ' do not define this in model + Public Property PriceWithDiscount As Decimal + + Public Property LabelDescription As String + + Public Property Tag As Object + Public Overrides Function Equals(obj As Object) As Boolean If obj Is Nothing OrElse TypeOf obj IsNot Article Then Return False diff --git a/Source/Test/Yamo.Test/Model/ArticlePart.vb b/Source/Test/Yamo.Test/Model/ArticlePart.vb index fe78121..45739eb 100644 --- a/Source/Test/Yamo.Test/Model/ArticlePart.vb +++ b/Source/Test/Yamo.Test/Model/ArticlePart.vb @@ -10,6 +10,8 @@ Public Property Label As Label + Public Property PriceWithDiscount As Decimal + Public Overrides Function Equals(obj As Object) As Boolean If obj Is Nothing OrElse TypeOf obj IsNot ArticlePart Then Return False diff --git a/Source/Test/Yamo.Test/Model/Label.vb b/Source/Test/Yamo.Test/Model/Label.vb index d6c7d6c..2aa981b 100644 --- a/Source/Test/Yamo.Test/Model/Label.vb +++ b/Source/Test/Yamo.Test/Model/Label.vb @@ -10,6 +10,8 @@ Public Property Description As String + Public Property Tag As Object + Public Overrides Function Equals(obj As Object) As Boolean If obj Is Nothing OrElse TypeOf obj IsNot Label Then Return False diff --git a/Source/Test/Yamo.Test/Tests/SelectTests.vb b/Source/Test/Yamo.Test/Tests/SelectWithExcludeTests.vb similarity index 99% rename from Source/Test/Yamo.Test/Tests/SelectTests.vb rename to Source/Test/Yamo.Test/Tests/SelectWithExcludeTests.vb index 0e9fba6..a06e4b3 100644 --- a/Source/Test/Yamo.Test/Tests/SelectTests.vb +++ b/Source/Test/Yamo.Test/Tests/SelectWithExcludeTests.vb @@ -2,7 +2,7 @@ Namespace Tests - Public MustInherit Class SelectTests + Public MustInherit Class SelectWithExcludeTests Inherits BaseIntegrationTests Protected Const English As String = "en" diff --git a/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb b/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb new file mode 100644 index 0000000..30ea72f --- /dev/null +++ b/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb @@ -0,0 +1,291 @@ +Imports Yamo.Test.Model + +Namespace Tests + + Public MustInherit Class SelectWithIncludeTests + Inherits BaseIntegrationTests + + Protected Const English As String = "en" + + + Public Overridable Sub SelectWithIncludeUsingAction() + ' same as test below, just use API allowed only in VB.NET + + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + Dim article3 = Me.ModelFactory.CreateArticle(3, 300D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + Dim label2En = Me.ModelFactory.CreateLabel("", 2, English, "Article 2") + Dim label3En = Me.ModelFactory.CreateLabel("", 3, English, "Article 3") + + InsertItems(article1, article2, article3, label1En, label2En, label3En) + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(a) a.PriceWithDiscount = a.Price * 0.9D). + Include(Sub(a) a.LabelDescription = "foo"). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("foo", result.LabelDescription) + Assert.IsNull(result.Label) + End Using + + ' similar as above, but use IJoin + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("Article1", result.LabelDescription) + Assert.IsNull(result.Label) + End Using + + ' same as above, but don't exclude label + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual(label1En, result.Label) + End Using + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + Assert.IsTrue(result.All(Function(x) x.Label Is Nothing)) + End Using + + ' same as above, but don't exclude label + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) + End Using + End Sub + + + Public Overridable Sub SelectWithIncludeUsingKeyValueSelectors() + ' same as test above, just use different API + + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + Dim article3 = Me.ModelFactory.CreateArticle(3, 300D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + Dim label2En = Me.ModelFactory.CreateLabel("", 2, English, "Article 2") + Dim label3En = Me.ModelFactory.CreateLabel("", 3, English, "Article 3") + + InsertItems(article1, article2, article3, label1En, label2En, label3En) + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(a) a.PriceWithDiscount, Function(a) a.Price * 0.9D). + Include(Function(a) a.LabelDescription, Function(l) l.Description). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("Article1", result.LabelDescription) + Assert.IsNull(result.Label) + End Using + + ' same as above, but use IJoin + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). + Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("Article1", result.LabelDescription) + Assert.IsNull(result.Label) + End Using + + ' same as above, but don't exclude label + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). + Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual(label1En, result.Label) + End Using + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). + Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + Assert.IsTrue(result.All(Function(x) x.Label Is Nothing)) + End Using + + ' same as above, but don't exclude label + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). + Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) + End Using + End Sub + + + Public Overridable Sub SelectWithIncludeInJoinedTables() + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + Dim article3 = Me.ModelFactory.CreateArticle(3, 300D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + Dim label2En = Me.ModelFactory.CreateLabel("", 2, English, "Article 2") + Dim label3En = Me.ModelFactory.CreateLabel("", 3, English, "Article 3") + + Dim article1Part1 = Me.ModelFactory.CreateArticlePart(1001, 1, 10D) + Dim article1Part2 = Me.ModelFactory.CreateArticlePart(1002, 1, 11D) + Dim article1Part3 = Me.ModelFactory.CreateArticlePart(1003, 1, 12D) + Dim article2Part1 = Me.ModelFactory.CreateArticlePart(2001, 2, 13D) + Dim article2Part2 = Me.ModelFactory.CreateArticlePart(2002, 2, 14D) + Dim article3Part1 = Me.ModelFactory.CreateArticlePart(3001, 3, 15D) + Dim article3Part2 = Me.ModelFactory.CreateArticlePart(3002, 3, 16D) + + InsertItems(article1, article2, article3) + InsertItems(label1En, label2En, label3En) + InsertItems(article1Part1, article1Part2, article1Part3, article2Part1, article2Part2, article3Part1, article3Part2) + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + Join(Of ArticlePart)(Function(j) j.T1.Id = j.T3.ArticleId). + OrderBy(Function(j) j.T1.Id).ThenBy(Function(j) j.T3.Price). + SelectAll(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + Include(Sub(j) j.T2.Tag = j.T2.Id). + Include(Sub(j) j.T3.PriceWithDiscount = j.T3.Price * 0.8D). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) + CollectionAssert.AreEqual({label1En.Id, label2En.Id, label3En.Id}, result.Select(Function(x) x.Label.Tag).ToArray()) + CollectionAssert.AreEqual({article1Part1, article1Part2, article1Part3}, result(0).Parts) + CollectionAssert.AreEqual({article2Part1, article2Part2}, result(1).Parts) + CollectionAssert.AreEqual({article3Part1, article3Part2}, result(2).Parts) + CollectionAssert.AreEqual({article1Part1.Price * 0.8D, article1Part2.Price * 0.8D, article1Part3.Price * 0.8D}, result(0).Parts.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({article2Part1.Price * 0.8D, article2Part2.Price * 0.8D}, result(1).Parts.Select(Function(x) x.PriceWithDiscount).ToArray()) + CollectionAssert.AreEqual({article3Part1.Price * 0.8D, article3Part2.Price * 0.8D}, result(2).Parts.Select(Function(x) x.PriceWithDiscount).ToArray()) + End Using + End Sub + + + Public Overridable Sub SelectWithIncludeOfComplexType() + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + Dim article3 = Me.ModelFactory.CreateArticle(3, 300D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + Dim label2En = Me.ModelFactory.CreateLabel("", 2, English, "Article 2") + Dim label3En = Me.ModelFactory.CreateLabel("", 3, English, "Article 3") + + InsertItems(article1, article2, article3, label1En, label2En, label3En) + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + Include(Sub(j) j.T1.Tag = (LabelId:=j.T2.Id, LabelDescription:=j.T2.Description)). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) + CollectionAssert.AreEqual({(label1En.Id, label1En.Description), (label2En.Id, label2En.Description), (label3En.Id, label3En.Description)}, result.Select(Function(x) x.Tag).ToArray()) + End Using + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + Include(Sub(j) j.T1.Tag = New With {.LabelId = j.T2.Id, .LabelDescription = j.T2.Description}). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) + CollectionAssert.AreEqual({New With {.LabelId = label1En.Id, .LabelDescription = label1En.Description}, New With {.LabelId = label2En.Id, .LabelDescription = label2En.Description}, New With {.LabelId = label3En.Id, .LabelDescription = label3En.Description}}, result.Select(Function(x) x.Tag).ToArray()) + End Using + End Sub + End Class +End Namespace From 888b7d02029db6c3cd7ab60bd0e5f6b29a31816a Mon Sep 17 00:00:00 2001 From: esentio Date: Sun, 16 May 2021 18:39:39 +0200 Subject: [PATCH 2/4] Refactoring --- .../SelectSqlExpressionCodeGenerator.vb | 9 +- .../Builders/DeleteSqlExpressionBuilder.vb | 27 +- .../Builders/SelectSqlExpressionBuilder.vb | 31 +- .../Builders/UpdateSqlExpressionBuilder.vb | 21 +- .../Yamo/Expressions/DeleteSqlExpression.vb | 3 +- .../Expressions/DeleteSqlExpressionBase.vb | 6 + .../Yamo/Expressions/SelectSqlExpression.vb | 9 +- .../Yamo/Expressions/UpdateSqlExpression.vb | 3 +- .../CustomResultReaderFactory.vb | 215 ------------ .../Infrastructure/SqlResultReaderFactory.vb | 306 ++++++++++++++++++ .../Yamo/Internal/CustomResultReaderCache.vb | 136 -------- .../Query/AnonymousTypeSqlResultReaderData.vb | 38 +++ .../Yamo/Internal/Query/BaseReadInfo.vb | 46 --- .../Internal/Query/CustomEntityReadInfo.vb | 204 ------------ .../Yamo/Internal/Query/EntityReadInfo.vb | 197 ----------- .../Query/EntitySqlResultReaderData.vb | 120 +++++++ ...=> EntitySqlResultReaderDataCollection.vb} | 52 +-- .../Query/Metadata/AnonymousTypeSqlResult.vb | 29 ++ .../Query/Metadata/CustomSqlEntity.vb | 70 ---- .../Internal/Query/Metadata/DeleteSqlModel.vb | 23 ++ .../Query/Metadata/EntitySqlResult.vb | 28 ++ .../Query/Metadata/ScalarValueSqlResult.vb | 20 ++ .../Internal/Query/Metadata/SelectSqlModel.vb | 61 ++++ .../Yamo/Internal/Query/Metadata/SqlModel.vb | 191 ----------- .../Internal/Query/Metadata/SqlModelBase.vb | 107 ++++++ .../Internal/Query/Metadata/SqlResultBase.vb | 26 ++ .../Internal/Query/Metadata/UpdateSqlModel.vb | 23 ++ .../Query/Metadata/ValueTupleSqlResult.vb | 29 ++ .../Yamo/Internal/Query/QueryExecutor.vb | 245 ++++++++------ .../Yamo/Internal/Query/ReaderDataBase.vb | 33 ++ .../Yamo/Internal/Query/ReaderDataFactory.vb | 239 ++++++++++++++ .../Query/ScalarValueSqlResultReaderData.vb | 38 +++ .../Source/Yamo/Internal/Query/SelectQuery.vb | 6 +- .../Query/ValueTupleSqlResultReaderData.vb | 38 +++ .../Yamo/Internal/SqlExpressionVisitor.vb | 71 ++-- .../Yamo/Internal/SqlResultReaderCache.vb | 104 ++++++ .../Yamo.Test.VB/Tests/ExpressionTests.vb | 3 +- 37 files changed, 1505 insertions(+), 1302 deletions(-) delete mode 100644 Source/Source/Yamo/Infrastructure/CustomResultReaderFactory.vb create mode 100644 Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb delete mode 100644 Source/Source/Yamo/Internal/CustomResultReaderCache.vb create mode 100644 Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb delete mode 100644 Source/Source/Yamo/Internal/Query/BaseReadInfo.vb delete mode 100644 Source/Source/Yamo/Internal/Query/CustomEntityReadInfo.vb delete mode 100644 Source/Source/Yamo/Internal/Query/EntityReadInfo.vb create mode 100644 Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb rename Source/Source/Yamo/Internal/Query/{EntityReadInfoCollection.vb => EntitySqlResultReaderDataCollection.vb} (85%) create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb delete mode 100644 Source/Source/Yamo/Internal/Query/Metadata/CustomSqlEntity.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/DeleteSqlModel.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/SelectSqlModel.vb delete mode 100644 Source/Source/Yamo/Internal/Query/Metadata/SqlModel.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/SqlModelBase.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/UpdateSqlModel.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb create mode 100644 Source/Source/Yamo/Internal/Query/ReaderDataBase.vb create mode 100644 Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb create mode 100644 Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb create mode 100644 Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb create mode 100644 Source/Source/Yamo/Internal/SqlResultReaderCache.vb diff --git a/Source/Source/Yamo.CodeGenerator/Generator/SelectSqlExpressionCodeGenerator.vb b/Source/Source/Yamo.CodeGenerator/Generator/SelectSqlExpressionCodeGenerator.vb index d2b2295..737e97b 100644 --- a/Source/Source/Yamo.CodeGenerator/Generator/SelectSqlExpressionCodeGenerator.vb +++ b/Source/Source/Yamo.CodeGenerator/Generator/SelectSqlExpressionCodeGenerator.vb @@ -89,8 +89,7 @@ AddComment(builder, comment, params:=params) builder.Indent().AppendLine("Friend Sub New(context As DbContext)").PushIndent() - builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context))") - builder.Indent().AppendLine("Me.Builder.SetMainTable(Of T)()").PopIndent() + builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context))").PopIndent() builder.Indent().AppendLine("End Sub") builder.AppendLine() @@ -100,8 +99,7 @@ AddComment(builder, comment, params:=params) builder.Indent().AppendLine("Friend Sub New(context As DbContext, tableSource As FormattableString)").PushIndent() - builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context))") - builder.Indent().AppendLine("Me.Builder.SetMainTable(Of T)()") + builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context))") builder.Indent().AppendLine("Me.Builder.SetMainTableSource(tableSource)").PopIndent() builder.Indent().AppendLine("End Sub") @@ -112,8 +110,7 @@ AddComment(builder, comment, params:=params) builder.Indent().AppendLine("Friend Sub New(context As DbContext, tableSource As RawSqlString, ParamArray parameters() As Object)").PushIndent() - builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context))") - builder.Indent().AppendLine("Me.Builder.SetMainTable(Of T)()") + builder.Indent().AppendLine("MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context))") builder.Indent().AppendLine("Me.Builder.SetMainTableSource(tableSource, parameters)").PopIndent() builder.Indent().AppendLine("End Sub") Else diff --git a/Source/Source/Yamo/Expressions/Builders/DeleteSqlExpressionBuilder.vb b/Source/Source/Yamo/Expressions/Builders/DeleteSqlExpressionBuilder.vb index e9e3d69..041f64e 100644 --- a/Source/Source/Yamo/Expressions/Builders/DeleteSqlExpressionBuilder.vb +++ b/Source/Source/Yamo/Expressions/Builders/DeleteSqlExpressionBuilder.vb @@ -16,7 +16,7 @@ Namespace Expressions.Builders ''' ''' Stores SQL model. ''' - Private m_Model As SqlModel + Private m_Model As DeleteSqlModel ''' ''' Stores whether soft delete is used. @@ -58,10 +58,12 @@ Namespace Expressions.Builders ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. ''' ''' + ''' ''' - Public Sub New(context As DbContext, softDelete As Boolean, tableNameOverride As String) + ''' + Public Sub New(context As DbContext, mainEntityType As Type, softDelete As Boolean, tableNameOverride As String) MyBase.New(context) - m_Model = New SqlModel(Me.DbContext.Model) + m_Model = New DeleteSqlModel(Me.DbContext.Model, mainEntityType) m_SoftDelete = softDelete m_TableNameOverride = tableNameOverride m_TableHints = Nothing @@ -71,15 +73,6 @@ Namespace Expressions.Builders m_ParameterIndexShift = Nothing ' lazy assigned End Sub - ''' - ''' Sets main table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub SetMainTable(Of T)() - m_Model.SetMainTable(Of T)() - End Sub - ''' ''' Sets table hint(s).
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -97,7 +90,7 @@ Namespace Expressions.Builders Public Sub AddWhere(predicate As Expression) If Not m_ParameterIndexShift.HasValue Then If m_SoftDelete Then - m_ParameterIndexShift = m_Model.GetFirstEntity().Entity.GetSetOnDeleteProperties().Count + m_ParameterIndexShift = m_Model.MainEntity.Entity.GetSetOnDeleteProperties().Count Else m_ParameterIndexShift = 0 End If @@ -147,7 +140,7 @@ Namespace Expressions.Builders sql.Append("DELETE FROM ") If m_TableNameOverride Is Nothing Then - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity Me.DialectProvider.Formatter.AppendIdentifier(sql, entity.TableName, entity.Schema) Else sql.Append(m_TableNameOverride) @@ -173,7 +166,7 @@ Namespace Expressions.Builders '''
''' Private Function CreateSoftDeleteQuery() As Query - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity Dim table = If(m_TableNameOverride, Me.DialectProvider.Formatter.CreateIdentifier(entity.TableName, entity.Schema)) @@ -217,7 +210,7 @@ Namespace Expressions.Builders Dim table As String If m_TableNameOverride Is Nothing Then - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity table = Me.DialectProvider.Formatter.CreateIdentifier(entity.TableName, entity.Schema) Else table = m_TableNameOverride @@ -243,7 +236,7 @@ Namespace Expressions.Builders Dim table As String If m_TableNameOverride Is Nothing Then - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity table = Me.DialectProvider.Formatter.CreateIdentifier(entity.TableName, entity.Schema) Else table = m_TableNameOverride diff --git a/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb b/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb index f039bec..0047457 100644 --- a/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb +++ b/Source/Source/Yamo/Expressions/Builders/SelectSqlExpressionBuilder.vb @@ -18,7 +18,7 @@ Namespace Expressions.Builders ''' ''' Stores SQL model. ''' - Private m_Model As SqlModel + Private m_Model As SelectSqlModel ''' ''' Stores SQL expression visitor. @@ -100,9 +100,10 @@ Namespace Expressions.Builders ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. ''' ''' - Public Sub New(context As DbContext) + ''' + Public Sub New(context As DbContext, mainEntityType As Type) MyBase.New(context) - m_Model = New SqlModel(Me.DbContext.Model) + m_Model = New SelectSqlModel(Me.DbContext.Model, mainEntityType) m_Visitor = New SqlExpressionVisitor(Me, m_Model) ' lists are created only when necessary m_MainTableSourceExpression = Nothing @@ -121,15 +122,6 @@ Namespace Expressions.Builders m_Parameters = New List(Of SqlParameter) End Sub - ''' - ''' Sets main table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub SetMainTable(Of T)() - m_Model.SetMainTable(Of T)() - End Sub - ''' ''' Sets main table source.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -254,7 +246,9 @@ Namespace Expressions.Builders relationship = TryGetRelationship(Of TJoined)(entityIndexHints) End If - m_Model.AddJoinedTable(Of TJoined)(relationship) + Dim sqlEntity = m_Model.AddJoin(Of TJoined)(relationship) + Dim entity = sqlEntity.Entity + Dim tableAlias = sqlEntity.TableAlias Dim sql As String Dim joinTypeString As String @@ -274,9 +268,6 @@ Namespace Expressions.Builders Throw New NotSupportedException($"Unsupported join type '{joinInfo.JoinType}'.") End Select - Dim entity = m_Model.Model.GetEntity(GetType(TJoined)) - Dim tableAlias = m_Model.GetLastTableAlias() - If predicate Is Nothing Then If joinInfo.TableSource Is Nothing Then sql = joinTypeString & " " & Me.DialectProvider.Formatter.CreateIdentifier(entity.TableName, entity.Schema) & " " & Me.DialectProvider.Formatter.CreateIdentifier(tableAlias) & If(tableHints Is Nothing, "", " " & tableHints) @@ -305,7 +296,7 @@ Namespace Expressions.Builders '''
''' Public Sub AddIgnoredJoin(entityType As Type) - m_Model.AddIgnoredJoinedTable(entityType) + m_Model.AddIgnoredJoin(entityType) End Sub ''' @@ -707,7 +698,7 @@ Namespace Expressions.Builders Dim result = m_Visitor.TranslateCustomSelect(selector, entityIndexHints, m_Parameters.Count) m_SelectExpression = result.SqlString.Sql m_Parameters.AddRange(result.SqlString.Parameters) - m_Model.SetCustomEntities(result.CustomEntities) + m_Model.CustomSqlResult = result.SqlResult End Sub ''' @@ -776,14 +767,14 @@ Namespace Expressions.Builders sql.Append(" FROM ") If m_MainTableSourceExpression Is Nothing Then - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity Me.DialectProvider.Formatter.AppendIdentifier(sql, entity.TableName, entity.Schema) Else sql.Append(m_MainTableSourceExpression) End If sql.Append(" ") - Me.DialectProvider.Formatter.AppendIdentifier(sql, m_Model.GetFirstTableAlias()) + Me.DialectProvider.Formatter.AppendIdentifier(sql, m_Model.MainEntity.TableAlias) If m_MainTableHints IsNot Nothing Then sql.Append(" ") diff --git a/Source/Source/Yamo/Expressions/Builders/UpdateSqlExpressionBuilder.vb b/Source/Source/Yamo/Expressions/Builders/UpdateSqlExpressionBuilder.vb index 082b9cc..1c5efcc 100644 --- a/Source/Source/Yamo/Expressions/Builders/UpdateSqlExpressionBuilder.vb +++ b/Source/Source/Yamo/Expressions/Builders/UpdateSqlExpressionBuilder.vb @@ -16,7 +16,7 @@ Namespace Expressions.Builders ''' ''' Stores SQL model. ''' - Private m_Model As SqlModel + Private m_Model As UpdateSqlModel ''' ''' Stores table name override. @@ -53,9 +53,11 @@ Namespace Expressions.Builders ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. ''' ''' - Public Sub New(context As DbContext, tableNameOverride As String) + ''' + ''' + Public Sub New(context As DbContext, mainEntityType As Type, tableNameOverride As String) MyBase.New(context) - m_Model = New SqlModel(Me.DbContext.Model) + m_Model = New UpdateSqlModel(Me.DbContext.Model, mainEntityType) m_TableNameOverride = tableNameOverride m_TableHints = Nothing m_Visitor = New SqlExpressionVisitor(Me, m_Model) @@ -64,15 +66,6 @@ Namespace Expressions.Builders m_Parameters = New List(Of SqlParameter) End Sub - ''' - ''' Sets main table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub SetMainTable(Of T)() - m_Model.SetMainTable(Of T)() - End Sub - ''' ''' Sets table hint(s).
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -171,7 +164,7 @@ Namespace Expressions.Builders ''' ''' Public Function CreateQuery(setAutoFields As Boolean) As Query - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity Dim sql = New StringBuilder @@ -228,7 +221,7 @@ Namespace Expressions.Builders Dim table As String If m_TableNameOverride Is Nothing Then - Dim entity = m_Model.GetFirstEntity().Entity + Dim entity = m_Model.MainEntity.Entity table = Me.DialectProvider.Formatter.CreateIdentifier(entity.TableName, entity.Schema) Else table = m_TableNameOverride diff --git a/Source/Source/Yamo/Expressions/DeleteSqlExpression.vb b/Source/Source/Yamo/Expressions/DeleteSqlExpression.vb index a1cb7e5..dc50e76 100644 --- a/Source/Source/Yamo/Expressions/DeleteSqlExpression.vb +++ b/Source/Source/Yamo/Expressions/DeleteSqlExpression.vb @@ -24,8 +24,7 @@ Namespace Expressions ''' ''' Friend Sub New(context As DbContext, softDelete As Boolean, Optional tableNameOverride As String = Nothing) - MyBase.New(context, New DeleteSqlExpressionBuilder(context, softDelete, tableNameOverride), New QueryExecutor(context)) - Me.Builder.SetMainTable(Of T)() + MyBase.New(context, New DeleteSqlExpressionBuilder(context, GetType(T), softDelete, tableNameOverride), New QueryExecutor(context)) m_SoftDelete = softDelete End Sub diff --git a/Source/Source/Yamo/Expressions/DeleteSqlExpressionBase.vb b/Source/Source/Yamo/Expressions/DeleteSqlExpressionBase.vb index ff7d7d7..06ff179 100644 --- a/Source/Source/Yamo/Expressions/DeleteSqlExpressionBase.vb +++ b/Source/Source/Yamo/Expressions/DeleteSqlExpressionBase.vb @@ -30,6 +30,12 @@ Namespace Expressions ''' Protected Property Executor As QueryExecutor + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' Friend Sub New(context As DbContext, builder As DeleteSqlExpressionBuilder, executor As QueryExecutor) Me.DbContext = context Me.Builder = builder diff --git a/Source/Source/Yamo/Expressions/SelectSqlExpression.vb b/Source/Source/Yamo/Expressions/SelectSqlExpression.vb index 660b2b3..95b463f 100644 --- a/Source/Source/Yamo/Expressions/SelectSqlExpression.vb +++ b/Source/Source/Yamo/Expressions/SelectSqlExpression.vb @@ -17,8 +17,7 @@ Namespace Expressions '''
''' Friend Sub New(context As DbContext) - MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context)) - Me.Builder.SetMainTable(Of T)() + MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context)) End Sub ''' @@ -27,8 +26,7 @@ Namespace Expressions ''' ''' Friend Sub New(context As DbContext, tableSource As FormattableString) - MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context)) - Me.Builder.SetMainTable(Of T)() + MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context)) Me.Builder.SetMainTableSource(tableSource) End Sub @@ -39,8 +37,7 @@ Namespace Expressions ''' ''' Friend Sub New(context As DbContext, tableSource As RawSqlString, ParamArray parameters() As Object) - MyBase.New(New SelectSqlExpressionBuilder(context), New QueryExecutor(context)) - Me.Builder.SetMainTable(Of T)() + MyBase.New(New SelectSqlExpressionBuilder(context, GetType(T)), New QueryExecutor(context)) Me.Builder.SetMainTableSource(tableSource, parameters) End Sub diff --git a/Source/Source/Yamo/Expressions/UpdateSqlExpression.vb b/Source/Source/Yamo/Expressions/UpdateSqlExpression.vb index 574ee38..40db42f 100644 --- a/Source/Source/Yamo/Expressions/UpdateSqlExpression.vb +++ b/Source/Source/Yamo/Expressions/UpdateSqlExpression.vb @@ -18,8 +18,7 @@ Namespace Expressions ''' ''' Friend Sub New(context As DbContext, Optional tableNameOverride As String = Nothing) - MyBase.New(context, New UpdateSqlExpressionBuilder(context, tableNameOverride), New QueryExecutor(context)) - Me.Builder.SetMainTable(Of T)() + MyBase.New(context, New UpdateSqlExpressionBuilder(context, GetType(T), tableNameOverride), New QueryExecutor(context)) End Sub ''' diff --git a/Source/Source/Yamo/Infrastructure/CustomResultReaderFactory.vb b/Source/Source/Yamo/Infrastructure/CustomResultReaderFactory.vb deleted file mode 100644 index 3858c8c..0000000 --- a/Source/Source/Yamo/Infrastructure/CustomResultReaderFactory.vb +++ /dev/null @@ -1,215 +0,0 @@ -Imports System.Data -Imports System.Linq.Expressions -Imports System.Reflection -Imports Yamo.Internal -Imports Yamo.Internal.Helpers -Imports Yamo.Internal.Query -Imports Yamo.Internal.Query.Metadata - -Namespace Infrastructure - - ''' - ''' Custom result reader factory.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class CustomResultReaderFactory - Inherits ReaderFactoryBase - - ''' - ''' Result type. - ''' - Private Enum ResultType - SingleValueOrEntity - AnonymousType - ValueTuple - NullableValueTuple - End Enum - - ''' - ''' Creates result factory.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - Public Shared Function CreateResultFactory(node As Expression, customEntities As CustomSqlEntity()) As Object - If node.NodeType = ExpressionType.New Then - ' ValueTuple or anonymous type - Dim result = node.Type - - If Helpers.Types.IsValueTupleOrNullableValueTuple(result) Then - Dim valueTupleTypeInfo = Helpers.Types.GetValueTupleTypeInfo(result) - Return CreateResultFactory(ResultType.ValueTuple, customEntities, valueTupleTypeInfo:=valueTupleTypeInfo) - Else - Dim resultConstructorInfo = DirectCast(node, NewExpression).Constructor - Return CreateResultFactory(ResultType.AnonymousType, customEntities, resultConstructorInfo:=resultConstructorInfo) - End If - Else - ' single value or entity - Return CreateResultFactory(ResultType.SingleValueOrEntity, customEntities) - End If - End Function - - ''' - ''' Creates result factory.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Public Shared Function CreateResultFactory(result As Type) As Object - ' NOTE: right now this should only be called from Query/QueryFirstOrDefault and only following types are supported: - ' - nullable and non-nullable ValueTuples: with basic value-types or model entities as a field value - ' - model entities - - If Helpers.Types.IsValueTupleOrNullableValueTuple(result) Then - ' ValueTuple - Dim valueTupleTypeInfo = Helpers.Types.GetValueTupleTypeInfo(result) - Dim customEntities = valueTupleTypeInfo.FlattenedArguments.Select(AddressOf CreateCustomSqlEntity).ToArray() - Return CreateResultFactory(ResultType.ValueTuple, customEntities, valueTupleTypeInfo:=valueTupleTypeInfo) - Else - ' entity model - Dim customEntities = {CreateCustomSqlEntity(result, 0)} - Return CreateResultFactory(ResultType.SingleValueOrEntity, customEntities) - End If - End Function - - ''' - ''' Creates instance of . - ''' - ''' - ''' - ''' - Private Shared Function CreateCustomSqlEntity(type As Type, index As Int32) As CustomSqlEntity - If Helpers.Types.IsProbablyModel(type) Then - ' If type is not defined in model, there will be an exception thrown later from - ' CustomEntityReadInfo.CreateForGenericType() or CustomEntityReadInfo.CreateForModelType(). - ' We could check it also here, but it would be slower ;) - ' Also, we don't need (and don't know) entityIndex, so it's set to -1 in this scenario. - Return New CustomSqlEntity(index, -1, type) - Else - Return New CustomSqlEntity(index, type) - End If - End Function - - ''' - ''' Creates result factory. - ''' - ''' - ''' - ''' - ''' - ''' - Private Shared Function CreateResultFactory(resultType As ResultType, customEntities As CustomSqlEntity(), Optional resultConstructorInfo As ConstructorInfo = Nothing, Optional valueTupleTypeInfo As ValueTupleTypeInfo = Nothing) As Object - Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method - Dim customEntityInfosParam = Expression.Parameter(GetType(CustomEntityReadInfo()), "customEntityInfos") - Dim parameters = {readerParam, customEntityInfosParam} - - Dim variables = New List(Of ParameterExpression) - Dim expressions = New List(Of Expression) - - Dim arguments = New List(Of Expression) - - For i = 0 To customEntities.Length - 1 - Dim customEntity = customEntities(i) - Dim type = customEntity.Type - - Dim valueVar = Expression.Variable(type, $"value{i.ToString(Globalization.CultureInfo.InvariantCulture)}") - variables.Add(valueVar) - - Dim customEntityInfoVar = Expression.Variable(GetType(CustomEntityReadInfo), $"readInfo{i.ToString(Globalization.CultureInfo.InvariantCulture)}") - variables.Add(customEntityInfoVar) - - Dim customEntityInfoIndex = Expression.ArrayIndex(customEntityInfosParam, Expression.Constant(i)) - expressions.Add(Expression.Assign(customEntityInfoVar, customEntityInfoIndex)) - - Dim readerIndexProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.ReaderIndex)) - - If customEntity.IsEntity Then - Dim entityProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.Entity)) - Dim includedColumnsProp = Expression.Property(entityProp, NameOf(SqlEntity.IncludedColumns)) - Dim pkOffsetsProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.PKOffsets)) - - Dim containsPKReaderProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.ContainsPKReader)) - Dim containsPKReaderType = GetType(Func(Of, , ,)).MakeGenericType(GetType(IDataReader), GetType(Int32), GetType(Int32()), GetType(Boolean)) - Dim containsPKReaderInvokeMethodInfo = containsPKReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) - Dim containsPKReaderCall = Expression.Call(containsPKReaderProp, containsPKReaderInvokeMethodInfo, readerParam, readerIndexProp, pkOffsetsProp) - - Dim entityReaderProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.EntityReader)) - Dim entityReaderType = GetType(Func(Of, , ,)).MakeGenericType(GetType(IDataReader), GetType(Int32), GetType(Boolean()), GetType(Object)) - Dim entityReaderInvokeMethodInfo = entityReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) - Dim entityReaderCall = Expression.Call(entityReaderProp, entityReaderInvokeMethodInfo, readerParam, readerIndexProp, includedColumnsProp) - Dim entityReaderCallCast = Expression.Convert(entityReaderCall, type) - - Dim valueAssign = Expression.Assign(valueVar, entityReaderCallCast) - Dim valueAssignNull = Expression.Assign(valueVar, Expression.Default(type)) - - Dim valueAssignBlock As Expression - - Dim ihpmtType = GetType(IHasDbPropertyModifiedTracking) - If ihpmtType.IsAssignableFrom(type) Then - Dim rpmtMethodInfo = ihpmtType.GetMethod(NameOf(IHasDbPropertyModifiedTracking.ResetDbPropertyModifiedTracking)) - Dim rpmtCast = Expression.Convert(valueVar, ihpmtType) - Dim rpmtCall = Expression.Call(rpmtCast, rpmtMethodInfo) - - valueAssignBlock = Expression.Block(valueAssign, rpmtCall) - Else - valueAssignBlock = valueAssign - End If - - expressions.Add(Expression.IfThenElse(containsPKReaderCall, valueAssignBlock, valueAssignNull)) - Else - Dim valueTypeReaderProp = Expression.Property(customEntityInfoVar, NameOf(CustomEntityReadInfo.ValueTypeReader)) - Dim valueTypeReaderType = GetType(Func(Of, , )).MakeGenericType(GetType(IDataReader), GetType(Int32), type) - Dim valueTypeReaderCast = Expression.Convert(valueTypeReaderProp, valueTypeReaderType) - Dim valueTypeReaderInvokeMethodInfo = valueTypeReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) - Dim valueTypeReaderCall = Expression.Call(valueTypeReaderCast, valueTypeReaderInvokeMethodInfo, readerParam, readerIndexProp) - - Dim valueAssign = Expression.Assign(valueVar, valueTypeReaderCall) - expressions.Add(valueAssign) - End If - - arguments.Add(valueVar) - Next - - Select Case resultType - Case ResultType.SingleValueOrEntity - expressions.Add(variables(0)) - Case ResultType.AnonymousType - expressions.Add(Expression.[New](resultConstructorInfo, arguments)) - Case ResultType.ValueTuple, ResultType.NullableValueTuple - expressions.Add(CreateValueTupleConstructor(valueTupleTypeInfo.CtorInfos, arguments, 0, 0)) - Case Else - Throw New NotSupportedException($"Result type '{resultType}' is not supported.") - End Select - - Dim body = Expression.Block(variables, expressions) - - Dim reader = Expression.Lambda(body, parameters) - Return reader.Compile() - End Function - - ''' - ''' Creates value tuple constructor. - ''' - ''' - ''' - ''' - ''' - ''' - Private Shared Function CreateValueTupleConstructor(ctorInfos As List(Of CtorInfo), arguments As List(Of Expression), ctorIndex As Int32, argumentIndex As Int32) As Expression - Dim ctorInfo = ctorInfos(ctorIndex) - - If ctorIndex = ctorInfos.Count - 1 Then - ' last constructor - Dim args = arguments.Skip(argumentIndex).Take(ctorInfo.ParameterCount) - Return Expression.[New](ctorInfo.ConstructorInfo, args) - Else - Dim take = ctorInfo.ParameterCount - 1 - Dim args = arguments.Skip(argumentIndex).Take(take).ToList() - args.Add(CreateValueTupleConstructor(ctorInfos, arguments, ctorIndex + 1, argumentIndex + take)) - Return Expression.[New](ctorInfo.ConstructorInfo, args) - End If - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb b/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb new file mode 100644 index 0000000..04832bc --- /dev/null +++ b/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb @@ -0,0 +1,306 @@ +Imports System.Data +Imports System.Linq.Expressions +Imports System.Reflection +Imports Yamo.Internal +Imports Yamo.Internal.Helpers +Imports Yamo.Internal.Query +Imports Yamo.Internal.Query.Metadata +Imports Yamo.Metadata + +Namespace Infrastructure + + ''' + ''' SQL result reader factory.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class SqlResultReaderFactory + Inherits ReaderFactoryBase + + ''' + ''' Creates result factory.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Shared Function CreateResultFactory(sqlResult As SqlResultBase) As Object + If TypeOf sqlResult Is AnonymousTypeSqlResult Then + Return CreateResultFactory(DirectCast(sqlResult, AnonymousTypeSqlResult)) + + ElseIf TypeOf sqlResult Is ValueTupleSqlResult Then + Return CreateResultFactory(DirectCast(sqlResult, ValueTupleSqlResult)) + + ElseIf TypeOf sqlResult Is EntitySqlResult Then + Return CreateResultFactory(DirectCast(sqlResult, EntitySqlResult)) + + ElseIf TypeOf sqlResult Is ScalarValueSqlResult Then + Return CreateResultFactory(DirectCast(sqlResult, ScalarValueSqlResult)) + + Else + Throw New NotSupportedException($"SQL result of type {sqlResult.GetType()} is not supported.") + End If + End Function + + ''' + ''' Creates result factory. + ''' + ''' + ''' + Private Shared Function CreateResultFactory(sqlResult As AnonymousTypeSqlResult) As Object + Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method + Dim readerDataParam = Expression.Parameter(GetType(ReaderDataBase), "readerData") + Dim parameters = {readerParam, readerDataParam} + + Dim variables = New List(Of ParameterExpression) + Dim expressions = New List(Of Expression) + Dim ctorArguments = New List(Of Expression) + + Dim readerDataVar = AddReaderDataVar(GetType(AnonymousTypeSqlResultReaderData), "readerDataVar", readerDataParam, variables, expressions) + + For i = 0 To sqlResult.Items.Length - 1 + Dim sqlResultItem = sqlResult.Items(i) + + Dim readerDataItemVarType = GetReaderDataType(sqlResultItem) + Dim readerDataItemVarName = $"readerDataVar{i.ToString(Globalization.CultureInfo.InvariantCulture)}" + Dim readerDataItemSource = Expression.Convert(Expression.ArrayIndex(Expression.Property(readerDataVar, NameOf(AnonymousTypeSqlResultReaderData.Items)), Expression.Constant(i)), readerDataItemVarType) + Dim readerDataItemVar = AddReaderDataVar(readerDataItemVarType, readerDataItemVarName, readerDataItemSource, variables, expressions) + + Dim valueVar = Expression.Variable(sqlResultItem.ResultType, $"value{i.ToString(Globalization.CultureInfo.InvariantCulture)}") + variables.Add(valueVar) + + AddSqlResultReads(readerParam, readerDataItemVar, valueVar, sqlResultItem, expressions, ctorArguments) + Next + + Dim resultConstructorInfo = sqlResult.ResultType.GetConstructors()(0) + expressions.Add(Expression.[New](resultConstructorInfo, ctorArguments)) + + Dim body = Expression.Block(variables, expressions) + + Dim reader = Expression.Lambda(body, parameters) + Return reader.Compile() + End Function + + ''' + ''' Creates result factory. + ''' + ''' + ''' + Private Shared Function CreateResultFactory(sqlResult As ValueTupleSqlResult) As Object + Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method + Dim readerDataParam = Expression.Parameter(GetType(ReaderDataBase), "readerData") + Dim parameters = {readerParam, readerDataParam} + + Dim variables = New List(Of ParameterExpression) + Dim expressions = New List(Of Expression) + Dim ctorArguments = New List(Of Expression) + + Dim readerDataVar = AddReaderDataVar(GetType(ValueTupleSqlResultReaderData), "readerDataVar", readerDataParam, variables, expressions) + + For i = 0 To sqlResult.Items.Length - 1 + Dim sqlResultItem = sqlResult.Items(i) + + Dim readerDataItemVarType = GetReaderDataType(sqlResultItem) + Dim readerDataItemVarName = $"readerDataVar{i.ToString(Globalization.CultureInfo.InvariantCulture)}" + Dim readerDataItemSource = Expression.Convert(Expression.ArrayIndex(Expression.Property(readerDataVar, NameOf(ValueTupleSqlResultReaderData.Items)), Expression.Constant(i)), readerDataItemVarType) + Dim readerDataItemVar = AddReaderDataVar(readerDataItemVarType, readerDataItemVarName, readerDataItemSource, variables, expressions) + + Dim valueVar = Expression.Variable(sqlResultItem.ResultType, $"value{i.ToString(Globalization.CultureInfo.InvariantCulture)}") + variables.Add(valueVar) + + AddSqlResultReads(readerParam, readerDataItemVar, valueVar, sqlResultItem, expressions, ctorArguments) + Next + + Dim valueTupleTypeInfo = Helpers.Types.GetValueTupleTypeInfo(sqlResult.ResultType) + + expressions.Add(CreateValueTupleConstructor(valueTupleTypeInfo.CtorInfos, ctorArguments, 0, 0)) + + Dim body = Expression.Block(variables, expressions) + + Dim reader = Expression.Lambda(body, parameters) + Return reader.Compile() + End Function + + ''' + ''' Creates result factory. + ''' + ''' + ''' + Private Shared Function CreateResultFactory(sqlResult As EntitySqlResult) As Object + Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method + Dim readerDataParam = Expression.Parameter(GetType(ReaderDataBase), "readerData") + Dim parameters = {readerParam, readerDataParam} + + Dim variables = New List(Of ParameterExpression) + Dim expressions = New List(Of Expression) + Dim ctorArguments = New List(Of Expression) + + Dim readerDataVar = AddReaderDataVar(GetType(EntitySqlResultReaderData), "readerDataVar", readerDataParam, variables, expressions) + + Dim valueVar = Expression.Variable(sqlResult.ResultType, "value") + variables.Add(valueVar) + + AddSqlResultReads(readerParam, readerDataVar, valueVar, sqlResult, expressions, ctorArguments) + + expressions.Add(valueVar) + + Dim body = Expression.Block(variables, expressions) + + Dim reader = Expression.Lambda(body, parameters) + Return reader.Compile() + End Function + + ''' + ''' Creates result factory. + ''' + ''' + ''' + Private Shared Function CreateResultFactory(sqlResult As ScalarValueSqlResult) As Object + Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method + Dim readerDataParam = Expression.Parameter(GetType(ReaderDataBase), "readerData") + Dim parameters = {readerParam, readerDataParam} + + Dim variables = New List(Of ParameterExpression) + Dim expressions = New List(Of Expression) + Dim ctorArguments = New List(Of Expression) + + Dim readerDataVar = AddReaderDataVar(GetType(ScalarValueSqlResultReaderData), "readerDataVar", readerDataParam, variables, expressions) + + Dim valueVar = Expression.Variable(sqlResult.ResultType, "value") + variables.Add(valueVar) + + AddSqlResultReads(readerParam, readerDataVar, valueVar, sqlResult, expressions, ctorArguments) + + expressions.Add(valueVar) + + Dim body = Expression.Block(variables, expressions) + + Dim reader = Expression.Lambda(body, parameters) + Return reader.Compile() + End Function + + ''' + ''' Get type for . + ''' + ''' + ''' + Private Shared Function GetReaderDataType(sqlResult As SqlResultBase) As Type + If TypeOf sqlResult Is AnonymousTypeSqlResult Then + Return GetType(AnonymousTypeSqlResultReaderData) + + ElseIf TypeOf sqlResult Is ValueTupleSqlResult Then + Return GetType(ValueTupleSqlResultReaderData) + + ElseIf TypeOf sqlResult Is EntitySqlResult Then + Return GetType(EntitySqlResultReaderData) + + ElseIf TypeOf sqlResult Is ScalarValueSqlResult Then + Return GetType(ScalarValueSqlResultReaderData) + + Else + Throw New NotSupportedException($"SQL result of type {sqlResult.GetType()} is not supported.") + End If + End Function + + ''' + ''' Adds code for read info variable. + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function AddReaderDataVar(readerDataType As Type, variableName As String, source As Expression, variables As List(Of ParameterExpression), expressions As List(Of Expression)) As ParameterExpression + Dim readerDataVar = Expression.Variable(readerDataType, variableName) + variables.Add(readerDataVar) + expressions.Add(Expression.Assign(readerDataVar, Expression.Convert(source, readerDataType))) + Return readerDataVar + End Function + + ''' + ''' Adds code to read value. + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Sub AddSqlResultReads(readerParam As ParameterExpression, readerDataVar As ParameterExpression, valueVar As ParameterExpression, sqlResult As SqlResultBase, expressions As List(Of Expression), ctorArguments As List(Of Expression)) + Dim type = sqlResult.ResultType + + Dim readerIndexProp = Expression.Property(readerDataVar, NameOf(ReaderDataBase.ReaderIndex)) + + If TypeOf sqlResult Is EntitySqlResult Then + Dim entityProp = Expression.Property(readerDataVar, NameOf(EntitySqlResultReaderData.Entity)) + Dim includedColumnsProp = Expression.Property(entityProp, NameOf(SqlEntity.IncludedColumns)) + Dim pkOffsetsProp = Expression.Property(readerDataVar, NameOf(EntitySqlResultReaderData.PKOffsets)) + + Dim containsPKReaderProp = Expression.Property(readerDataVar, NameOf(EntitySqlResultReaderData.ContainsPKReader)) + Dim containsPKReaderType = GetType(Func(Of, , ,)).MakeGenericType(GetType(IDataReader), GetType(Int32), GetType(Int32()), GetType(Boolean)) + Dim containsPKReaderInvokeMethodInfo = containsPKReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) + Dim containsPKReaderCall = Expression.Call(containsPKReaderProp, containsPKReaderInvokeMethodInfo, readerParam, readerIndexProp, pkOffsetsProp) + + Dim entityReaderProp = Expression.Property(readerDataVar, NameOf(EntitySqlResultReaderData.Reader)) + Dim entityReaderType = GetType(Func(Of, , ,)).MakeGenericType(GetType(IDataReader), GetType(Int32), GetType(Boolean()), GetType(Object)) + Dim entityReaderInvokeMethodInfo = entityReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) + Dim entityReaderCall = Expression.Call(entityReaderProp, entityReaderInvokeMethodInfo, readerParam, readerIndexProp, includedColumnsProp) + Dim entityReaderCallCast = Expression.Convert(entityReaderCall, type) + + Dim valueAssign = Expression.Assign(valueVar, entityReaderCallCast) + Dim valueAssignNull = Expression.Assign(valueVar, Expression.Default(type)) + + Dim valueAssignBlock As Expression + + Dim ihpmtType = GetType(IHasDbPropertyModifiedTracking) + If ihpmtType.IsAssignableFrom(type) Then + Dim rpmtMethodInfo = ihpmtType.GetMethod(NameOf(IHasDbPropertyModifiedTracking.ResetDbPropertyModifiedTracking)) + Dim rpmtCast = Expression.Convert(valueVar, ihpmtType) + Dim rpmtCall = Expression.Call(rpmtCast, rpmtMethodInfo) + + valueAssignBlock = Expression.Block(valueAssign, rpmtCall) + Else + valueAssignBlock = valueAssign + End If + + expressions.Add(Expression.IfThenElse(containsPKReaderCall, valueAssignBlock, valueAssignNull)) + ElseIf TypeOf sqlResult Is ScalarValueSqlResult Then + Dim scalarValueReaderProp = Expression.Property(readerDataVar, NameOf(ScalarValueSqlResultReaderData.Reader)) + Dim scalarValueReaderType = GetType(Func(Of, , )).MakeGenericType(GetType(IDataReader), GetType(Int32), type) + Dim scalarValueReaderCast = Expression.Convert(scalarValueReaderProp, scalarValueReaderType) + Dim scalarValueReaderInvokeMethodInfo = scalarValueReaderType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) + Dim scalarValueReaderCall = Expression.Call(scalarValueReaderCast, scalarValueReaderInvokeMethodInfo, readerParam, readerIndexProp) + + Dim valueAssign = Expression.Assign(valueVar, scalarValueReaderCall) + expressions.Add(valueAssign) + Else + Throw New NotSupportedException($"SQL result of type {sqlResult.GetType()} is not supported.") + End If + + ctorArguments.Add(valueVar) + End Sub + + ''' + ''' Creates value tuple constructor. + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function CreateValueTupleConstructor(ctorInfos As List(Of CtorInfo), arguments As List(Of Expression), ctorIndex As Int32, argumentIndex As Int32) As Expression + Dim ctorInfo = ctorInfos(ctorIndex) + + If ctorIndex = ctorInfos.Count - 1 Then + ' last constructor + Dim args = arguments.Skip(argumentIndex).Take(ctorInfo.ParameterCount) + Return Expression.[New](ctorInfo.ConstructorInfo, args) + Else + Dim take = ctorInfo.ParameterCount - 1 + Dim args = arguments.Skip(argumentIndex).Take(take).ToList() + args.Add(CreateValueTupleConstructor(ctorInfos, arguments, ctorIndex + 1, argumentIndex + take)) + Return Expression.[New](ctorInfo.ConstructorInfo, args) + End If + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/CustomResultReaderCache.vb b/Source/Source/Yamo/Internal/CustomResultReaderCache.vb deleted file mode 100644 index 17cf70f..0000000 --- a/Source/Source/Yamo/Internal/CustomResultReaderCache.vb +++ /dev/null @@ -1,136 +0,0 @@ -Imports System.Data -Imports System.Linq.Expressions -Imports Yamo.Infrastructure -Imports Yamo.Internal.Query -Imports Yamo.Internal.Query.Metadata -Imports Yamo.Metadata - -Namespace Internal - - ''' - ''' Custom result reader cache.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class CustomResultReaderCache - - ''' - ''' Stores cache instances. - ''' - Private Shared m_Instances As Dictionary(Of Model, CustomResultReaderCache) - - ''' - ''' Stores result factory instances.
- ''' Instance type is actually Func(Of IDataReader, CustomEntityReadInfo(), T). - '''
- Private m_ResultFactories As Dictionary(Of Type, Object) - - ''' - ''' Initializes related static data. - ''' - Shared Sub New() - m_Instances = New Dictionary(Of Model, CustomResultReaderCache) - End Sub - - ''' - ''' Creates new instance of . - ''' - Private Sub New() - m_ResultFactories = New Dictionary(Of Type, Object) - End Sub - - ''' - ''' Get result factory.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function GetResultFactory(Of T)(model As Model, resultType As Type) As Func(Of IDataReader, CustomEntityReadInfo(), T) - Return GetInstance(model).GetOrCreateResultFactory(Of T)(model, resultType) - End Function - - ''' - ''' Create result factory if it does not exist.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - Public Shared Sub CreateResultFactoryIfNotExists(model As Model, node As Expression, customEntities As CustomSqlEntity()) - GetInstance(model).CreateResultFactory(model, node, customEntities) - End Sub - - ''' - ''' Gets cache instance. If it doesn't exist, it is created. - ''' - ''' - ''' - Private Shared Function GetInstance(model As Model) As CustomResultReaderCache - Dim instance As CustomResultReaderCache = Nothing - - SyncLock m_Instances - If Not m_Instances.TryGetValue(model, instance) Then - instance = New CustomResultReaderCache - m_Instances.Add(model, instance) - End If - End SyncLock - - Return instance - End Function - - ''' - ''' Gets or creates result factory. - ''' - ''' - ''' - ''' - ''' - Private Function GetOrCreateResultFactory(Of T)(model As Model, resultType As Type) As Func(Of IDataReader, CustomEntityReadInfo(), T) - Dim resultFactory As Func(Of IDataReader, CustomEntityReadInfo(), T) = Nothing - - SyncLock m_ResultFactories - Dim value As Object = Nothing - - If m_ResultFactories.TryGetValue(resultType, value) Then - resultFactory = DirectCast(value, Func(Of IDataReader, CustomEntityReadInfo(), T)) - End If - End SyncLock - - If resultFactory Is Nothing Then - resultFactory = DirectCast(CustomResultReaderFactory.CreateResultFactory(resultType), Func(Of IDataReader, CustomEntityReadInfo(), T)) - Else - Return resultFactory - End If - - SyncLock m_ResultFactories - m_ResultFactories(resultType) = resultFactory - End SyncLock - - Return resultFactory - End Function - - ''' - ''' Creates result factory. - ''' - ''' - ''' - ''' - Private Sub CreateResultFactory(model As Model, node As Expression, customEntities As CustomSqlEntity()) - Dim resultType = node.Type - - SyncLock m_ResultFactories - If m_ResultFactories.ContainsKey(resultType) Then - Exit Sub - End If - End SyncLock - - Dim resultFactory = CustomResultReaderFactory.CreateResultFactory(node, customEntities) - - SyncLock m_ResultFactories - m_ResultFactories(resultType) = resultFactory - End SyncLock - End Sub - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb new file mode 100644 index 0000000..a19483e --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb @@ -0,0 +1,38 @@ +Namespace Internal.Query + + ''' + ''' Represents reader data for anonymous type values.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class AnonymousTypeSqlResultReaderData + Inherits ReaderDataBase + + ''' + ''' Gets reader data of anonymous type properties.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Items As ReaderDataBase() + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(readerIndex As Int32, items As ReaderDataBase()) + MyBase.New(readerIndex) + Me.Items = items + End Sub + + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Items.Sum(Function(x) x.GetColumnCount()) + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/BaseReadInfo.vb b/Source/Source/Yamo/Internal/Query/BaseReadInfo.vb deleted file mode 100644 index d3e36a6..0000000 --- a/Source/Source/Yamo/Internal/Query/BaseReadInfo.vb +++ /dev/null @@ -1,46 +0,0 @@ -Imports System.Data -Imports Yamo.Infrastructure -Imports Yamo.Internal.Query.Metadata -Imports Yamo.Metadata - -Namespace Internal.Query - - ''' - ''' Base class for info used to read data from SQL result.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public MustInherit Class BaseReadInfo - - ''' - ''' Get primary keys offsets.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Protected Shared Function GetPKOffsets(entity As SqlEntity) As Int32() - Dim includedColumns = entity.IncludedColumns - Dim pks = entity.Entity.GetKeyProperties() - Dim pkOffsets = New Int32(pks.Count - 1) {} - - If pks.Count = 0 Then - Return pkOffsets - End If - - Dim offset = 0 - Dim currentPkIndex = 0 - - For i = 0 To pks.Last().Index - If i = pks(currentPkIndex).Index Then - pkOffsets(currentPkIndex) = offset - currentPkIndex += 1 - End If - - If includedColumns(i) Then - offset += 1 - End If - Next - - Return pkOffsets - End Function - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/CustomEntityReadInfo.vb b/Source/Source/Yamo/Internal/Query/CustomEntityReadInfo.vb deleted file mode 100644 index f925bf1..0000000 --- a/Source/Source/Yamo/Internal/Query/CustomEntityReadInfo.vb +++ /dev/null @@ -1,204 +0,0 @@ -Imports System.Data -Imports Yamo.Infrastructure -Imports Yamo.Internal.Query.Metadata -Imports Yamo.Metadata - -Namespace Internal.Query - - ''' - ''' Represents info used to read custom entity from SQL result.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class CustomEntityReadInfo - Inherits BaseReadInfo - - ''' - ''' Gets whether custom entity relates to an entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property IsEntity As Boolean - - ''' - ''' Gets index of the reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property ReaderIndex As Int32 - - ''' - ''' Gets SQL entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property Entity As SqlEntity - - ''' - ''' Gets entity reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property EntityReader As Func(Of IDataReader, Int32, Boolean(), Object) - - ''' - ''' Gets contains primary key reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property ContainsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean) - - ''' - ''' Gets primary key offsets.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property PKOffsets As Int32() - - ''' - ''' Gets value type reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property ValueTypeReader As Object - - ''' - ''' Creates new instance of . - ''' - Private Sub New() - End Sub - - ''' - ''' Creates new instances of .
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - Public Shared Function Create(dialectProvider As SqlDialectProvider, model As SqlModel) As CustomEntityReadInfo() - Dim entities = model.GetEntities() - Dim customEntities = model.GetCustomEntities() - - Dim result = New CustomEntityReadInfo(customEntities.Length - 1) {} - Dim readerIndex = 0 - - For i = 0 To customEntities.Length - 1 - Dim customEntity = customEntities(i) - - If customEntity.IsEntity Then - Dim entity = entities(customEntity.EntityIndex) - result(i) = Create(dialectProvider, model.Model, entity, readerIndex) - readerIndex += entity.GetColumnCount() - Else - result(i) = Create(dialectProvider, model.Model, customEntity.Type, readerIndex) - readerIndex += 1 - End If - Next - - Return result - End Function - - ''' - ''' Creates new instances of for (nullable) value tuple type.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function CreateForValueTupleType(dialectProvider As SqlDialectProvider, model As Model, type As Type) As CustomEntityReadInfo() - Dim underlyingNullableType = Nullable.GetUnderlyingType(type) - - If underlyingNullableType IsNot Nothing Then - type = underlyingNullableType - End If - - Dim args = Helpers.Types.GetFlattenedValueTupleGenericArguments(type) - Dim result = New CustomEntityReadInfo(args.Count - 1) {} - Dim readerIndex = 0 - - For i = 0 To args.Count - 1 - Dim argType = args(i) - - If Helpers.Types.IsProbablyModel(argType) Then - Dim entity = model.TryGetEntity(argType) - - If entity Is Nothing Then - Throw New NotSupportedException($"Type '{argType}' is not supported. Only reference types defined in model are supported.") - End If - - Dim sqlEntity = New SqlEntity(entity) - result(i) = Create(dialectProvider, model, sqlEntity, readerIndex) - readerIndex += sqlEntity.GetColumnCount() - Else - result(i) = Create(dialectProvider, model, argType, readerIndex) - readerIndex += 1 - End If - Next - - Return result - End Function - - ''' - ''' Creates new instances of for model type.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - ''' - Public Shared Function CreateForModelType(dialectProvider As SqlDialectProvider, model As Model, type As Type) As CustomEntityReadInfo() - Dim entity = model.TryGetEntity(type) - - If entity Is Nothing Then - Throw New NotSupportedException($"Type '{type}' is not supported. Only reference types defined in model are supported.") - End If - - Return {Create(dialectProvider, model, New SqlEntity(entity), 0)} - End Function - - ''' - ''' Creates new instance of . - ''' - ''' - ''' - ''' - ''' - ''' - Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, entity As SqlEntity, readerIndex As Int32) As CustomEntityReadInfo - Dim readInfo = New CustomEntityReadInfo - - readInfo._IsEntity = True - readInfo._ReaderIndex = readerIndex - readInfo._Entity = entity - readInfo._EntityReader = EntityReaderCache.GetReader(dialectProvider, model, entity.Entity.EntityType) - readInfo._ContainsPKReader = EntityReaderCache.GetContainsPKReader(dialectProvider, model, entity.Entity.EntityType) - readInfo._PKOffsets = GetPKOffsets(entity) - readInfo._ValueTypeReader = Nothing - - Return readInfo - End Function - - ''' - ''' Creates new instance of . - ''' - ''' - ''' - ''' - ''' - ''' - Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, type As Type, readerIndex As Int32) As CustomEntityReadInfo - Dim readInfo = New CustomEntityReadInfo - - readInfo._IsEntity = False - readInfo._ReaderIndex = readerIndex - readInfo._Entity = Nothing - readInfo._EntityReader = Nothing - readInfo._ContainsPKReader = Nothing - readInfo._PKOffsets = Nothing - readInfo._ValueTypeReader = ValueTypeReaderCache.GetReader(dialectProvider, model, type) - - Return readInfo - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb b/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb deleted file mode 100644 index eacc1e9..0000000 --- a/Source/Source/Yamo/Internal/Query/EntityReadInfo.vb +++ /dev/null @@ -1,197 +0,0 @@ -Imports System.Data -Imports Yamo.Infrastructure -Imports Yamo.Internal.Query.Metadata -Imports Yamo.Metadata - -Namespace Internal.Query - - ''' - ''' Represents info used to read entity from SQL result.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class EntityReadInfo - Inherits BaseReadInfo - - ''' - ''' Gets SQL entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property Entity As SqlEntity - - ''' - ''' Gets index of the reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property ReaderIndex As Int32 - - ''' - ''' Gets reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property Reader As Func(Of IDataReader, Int32, Boolean(), Object) - - ''' - ''' Gets contains primary key reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property ContainsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean) - - ''' - ''' Gets primary key reader.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property PKReader As Func(Of IDataReader, Int32, Int32(), Object) - - ''' - ''' Gets primary key offsets.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property PKOffsets As Int32() - - ''' - ''' Gets whether there are other entities to which this entity is declaring entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property HasRelatedEntities As Boolean - - ''' - ''' Gets indexes of all entities to which this entity is declaring entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property RelatedEntities As IReadOnlyList(Of Int32) - - ''' - ''' Gets whether there are other entities to which this entity is declaring entity and it is 1:N relationship.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property HasCollectionNavigation As Boolean - - ''' - ''' Gets collection initializers (might be ).
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property CollectionInitializers As IReadOnlyList(Of Action(Of Object)) ' might be nul!!! (it is wise? (allocation reasons)) - - ''' - ''' Gets relationship setter.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property RelationshipSetter As Action(Of Object, Object) ' declaring entity, related entity (this one) - - ''' - ''' Creates new instance of . - ''' - Private Sub New() - End Sub - - ''' - ''' Creates new instances of .
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - Public Shared Function Create(dialectProvider As SqlDialectProvider, model As SqlModel) As EntityReadInfo() - Dim entities = model.GetEntities() - Dim relationships = New(RelatedEntities As List(Of Int32), CollectionNavigations As List(Of CollectionNavigation))(entities.Length - 1) {} - - For index = 0 To entities.Length - 1 - Dim entity = entities(index) - - If entity.Relationship IsNot Nothing Then - Dim declaringEntityIndex = entity.Relationship.DeclaringEntity.Index - - If relationships(declaringEntityIndex).RelatedEntities Is Nothing Then - relationships(declaringEntityIndex).RelatedEntities = New List(Of Int32) - End If - - relationships(declaringEntityIndex).RelatedEntities.Add(index) - - If entity.Relationship.IsReferenceNavigation Then - ' do nothing here - ElseIf entity.Relationship.IsCollectionNavigation Then - If relationships(declaringEntityIndex).CollectionNavigations Is Nothing Then - relationships(declaringEntityIndex).CollectionNavigations = New List(Of CollectionNavigation) - End If - - relationships(declaringEntityIndex).CollectionNavigations.Add(DirectCast(entity.Relationship.RelationshipNavigation, CollectionNavigation)) - Else - Throw New NotSupportedException($"Relationship of unknown type.") - End If - End If - Next - - Dim result = New EntityReadInfo(entities.Length - 1) {} - Dim readerIndex = 0 - - For index = 0 To entities.Length - 1 - Dim entity = entities(index) - result(index) = Create(dialectProvider, model.Model, entity, readerIndex, relationships(index).RelatedEntities, relationships(index).CollectionNavigations) - readerIndex += entity.GetColumnCount() - Next - - Return result - End Function - - ''' - ''' Creates new instance of . - ''' - ''' - ''' - ''' - ''' - ''' - ''' - ''' - Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, entity As SqlEntity, readerIndex As Int32, relatedEntities As List(Of Int32), collectionNavigations As List(Of CollectionNavigation)) As EntityReadInfo - Dim readInfo = New EntityReadInfo - - readInfo._Entity = entity - readInfo._ReaderIndex = readerIndex - readInfo._Reader = EntityReaderCache.GetReader(dialectProvider, model, entity.Entity.EntityType) - readInfo._ContainsPKReader = EntityReaderCache.GetContainsPKReader(dialectProvider, model, entity.Entity.EntityType) - readInfo._PKReader = EntityReaderCache.GetPKReader(dialectProvider, model, entity.Entity.EntityType) - readInfo._PKOffsets = GetPKOffsets(entity) - - ' if relatedEntities is not null, then it contains at least one item - readInfo._HasRelatedEntities = relatedEntities IsNot Nothing - readInfo._RelatedEntities = relatedEntities - - ' if collectionNavigations is not null, then it contains at least one item - If collectionNavigations IsNot Nothing Then - readInfo._HasCollectionNavigation = True - - ' LINQ not used for performance and allocation reasons - Dim collectionInitializers = New Action(Of Object)(collectionNavigations.Count - 1) {} - - For i = 0 To collectionNavigations.Count - 1 - collectionInitializers(i) = EntityMemberSetterCache.GetCollectionInitSetter(model, entity.Entity.EntityType, collectionNavigations(i)) - Next - - readInfo._CollectionInitializers = collectionInitializers - Else - readInfo._HasCollectionNavigation = False - End If - - If entity.Relationship Is Nothing Then - readInfo._RelationshipSetter = Nothing - Else - readInfo._RelationshipSetter = EntityMemberSetterCache.GetSetter(model, entity.Relationship.DeclaringEntity.Entity.EntityType, entity.Relationship.RelationshipNavigation) - End If - - Return readInfo - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb new file mode 100644 index 0000000..1370ce5 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb @@ -0,0 +1,120 @@ +Imports System.Data +Imports Yamo.Internal.Query.Metadata + +Namespace Internal.Query + + ''' + ''' Represents reader data for entity values.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class EntitySqlResultReaderData + Inherits ReaderDataBase + + ''' + ''' Gets SQL entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Entity As SqlEntity + + ''' + ''' Gets entity reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Reader As Func(Of IDataReader, Int32, Boolean(), Object) + + ''' + ''' Gets contains primary key reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property ContainsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean) + + ''' + ''' Gets primary key offsets.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property PKOffsets As Int32() + + ''' + ''' Gets primary key reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property PKReader As Func(Of IDataReader, Int32, Int32(), Object) ' might be null! (allocation reasons) + + ''' + ''' Gets whether there are other entities to which this entity is declaring entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property HasRelatedEntities As Boolean + + ''' + ''' Gets indexes of all entities to which this entity is declaring entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property RelatedEntities As IReadOnlyList(Of Int32) ' might be null! (allocation reasons) + + ''' + ''' Gets whether there are other entities to which this entity is declaring entity and it is 1:N relationship.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property HasCollectionNavigation As Boolean + + ''' + ''' Gets collection initializers (might be ).
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property CollectionInitializers As IReadOnlyList(Of Action(Of Object)) ' might be null! (allocation reasons) + + ''' + ''' Gets relationship setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property RelationshipSetter As Action(Of Object, Object) ' declaring entity, related entity (this one); might be null! (allocation reasons) + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Public Sub New(readerIndex As Int32, entity As SqlEntity, entityReader As Func(Of IDataReader, Int32, Boolean(), Object), containsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean), pkOffsets As Int32(), Optional pkReader As Func(Of IDataReader, Int32, Int32(), Object) = Nothing, Optional relatedEntities As IReadOnlyList(Of Int32) = Nothing, Optional collectionInitializers As IReadOnlyList(Of Action(Of Object)) = Nothing, Optional relationshipSetter As Action(Of Object, Object) = Nothing) + MyBase.New(readerIndex) + Me.Entity = entity + Me.Reader = entityReader + Me.ContainsPKReader = containsPKReader + Me.PKOffsets = pkOffsets + Me.PKReader = pkReader + Me.HasRelatedEntities = relatedEntities IsNot Nothing + Me.RelatedEntities = relatedEntities + Me.HasCollectionNavigation = collectionInitializers IsNot Nothing + Me.CollectionInitializers = collectionInitializers + Me.RelationshipSetter = relationshipSetter + End Sub + + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Entity.GetColumnCount() + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/EntityReadInfoCollection.vb b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderDataCollection.vb similarity index 85% rename from Source/Source/Yamo/Internal/Query/EntityReadInfoCollection.vb rename to Source/Source/Yamo/Internal/Query/EntitySqlResultReaderDataCollection.vb index 608f1db..e763970 100644 --- a/Source/Source/Yamo/Internal/Query/EntityReadInfoCollection.vb +++ b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderDataCollection.vb @@ -1,24 +1,22 @@ Imports System.Data.Common -Imports Yamo.Infrastructure -Imports Yamo.Internal.Query.Metadata Namespace Internal.Query ''' - ''' Represents info used to read multiple entities from SQL result.
+ ''' Represents reader data for multiple entity values.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
- Public Class EntityReadInfoCollection + Public Class EntitySqlResultReaderDataCollection ''' - ''' Gets entity read info items.
+ ''' Gets reader data of entities.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property Items As EntityReadInfo() + Public ReadOnly Property Items As EntitySqlResultReaderData() ''' - ''' Gets count of entity read infos.
+ ''' Gets count of entities.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' @@ -37,32 +35,16 @@ Namespace Internal.Query Private m_ChainIndexes As List(Of Int32)() ' probably change to something like Boolean() in the future (list is used just for convenience in GetChainKey method - once better hashing API is avaliable, refactor this!) ''' - ''' Creates new instance of . - ''' - Private Sub New() - End Sub - - ''' - ''' Creates new instance of .
+ ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
- ''' - ''' - ''' - Public Shared Function Create(dialectProvider As SqlDialectProvider, model As SqlModel) As EntityReadInfoCollection - Dim entityInfos = EntityReadInfo.Create(dialectProvider, model) - Dim hasCollectionNavigation = entityInfos.Any(Function(o) o.HasCollectionNavigation) - - Dim result = New EntityReadInfoCollection - - result._Items = entityInfos - result._Count = entityInfos.Length - result._HasCollectionNavigation = hasCollectionNavigation - - result.SetChainIndexes() - - Return result - End Function + ''' + Public Sub New(items As EntitySqlResultReaderData()) + Me.Items = items + Me.Count = items.Length + Me.HasCollectionNavigation = items.Any(Function(o) o.HasCollectionNavigation) + SetChainIndexes() + End Sub ''' ''' Sets chain indexes for all entities for later computation of chain key. @@ -175,12 +157,12 @@ Namespace Internal.Query ''' Public Sub FillPks(dataReader As DbDataReader, pks As Object()) For i = 0 To Me.Count - 1 - Dim entityInfo = Me.Items(i) + Dim readerData = Me.Items(i) - If entityInfo.Entity.IsExcludedOrIgnored Then + If readerData.Entity.IsExcludedOrIgnored Then pks(i) = Nothing - ElseIf entityInfo.ContainsPKReader(dataReader, entityInfo.ReaderIndex, entityInfo.PKOffsets) Then - pks(i) = entityInfo.PKReader(dataReader, entityInfo.ReaderIndex, entityInfo.PKOffsets) + ElseIf readerData.ContainsPKReader(dataReader, readerData.ReaderIndex, readerData.PKOffsets) Then + pks(i) = readerData.PKReader(dataReader, readerData.ReaderIndex, readerData.PKOffsets) Else pks(i) = Nothing End If diff --git a/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb new file mode 100644 index 0000000..d764d02 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb @@ -0,0 +1,29 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL result of an anonymous type.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class AnonymousTypeSqlResult + Inherits SqlResultBase + + ''' + ''' Gets nested SQL results which represent anonymous object properties.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Items As SqlResultBase() + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(resultType As Type, items As SqlResultBase()) + MyBase.New(resultType) + Me.Items = items + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/CustomSqlEntity.vb b/Source/Source/Yamo/Internal/Query/Metadata/CustomSqlEntity.vb deleted file mode 100644 index 7823030..0000000 --- a/Source/Source/Yamo/Internal/Query/Metadata/CustomSqlEntity.vb +++ /dev/null @@ -1,70 +0,0 @@ -Imports Yamo.Metadata - -Namespace Internal.Query.Metadata - - ''' - ''' Represents SQL related custom entity data.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class CustomSqlEntity - - ' TODO: SIP - structure instead? - - ''' - ''' Gets custom entity index.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property Index As Int32 - - ''' - ''' Gets whether this relates to an entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property IsEntity As Boolean - - ''' - ''' Gets entity index.
- ''' Return -1 if this doesn't relate to an entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property EntityIndex As Int32 - - ''' - ''' Gets custom entity type.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public ReadOnly Property Type As Type - - ''' - ''' Creates new instance of representing simple value.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Public Sub New(index As Int32, type As Type) - Me.Index = index - Me.IsEntity = False - Me.EntityIndex = -1 - Me.Type = type - End Sub - - ''' - ''' Creates new instance of representing entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - ''' - Public Sub New(index As Int32, entityIndex As Int32, type As Type) - Me.Index = index - Me.IsEntity = True - Me.EntityIndex = entityIndex - Me.Type = type - End Sub - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/DeleteSqlModel.vb b/Source/Source/Yamo/Internal/Query/Metadata/DeleteSqlModel.vb new file mode 100644 index 0000000..1ed6785 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/DeleteSqlModel.vb @@ -0,0 +1,23 @@ +Imports Yamo.Metadata + +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL related model data for delete statement.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class DeleteSqlModel + Inherits SqlModelBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(model As Model, mainEntityType As Type) + MyBase.New(model, mainEntityType) + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb new file mode 100644 index 0000000..453af94 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb @@ -0,0 +1,28 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL result of a model entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class EntitySqlResult + Inherits SqlResultBase + + ''' + ''' Gets related SQL entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Entity As SqlEntity + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Sub New(entity As SqlEntity) + MyBase.New(entity.Entity.EntityType) + Me.Entity = entity + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb new file mode 100644 index 0000000..9a574d2 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb @@ -0,0 +1,20 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL result of a scalar value.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class ScalarValueSqlResult + Inherits SqlResultBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Sub New(resultType As Type) + MyBase.New(resultType) + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SelectSqlModel.vb b/Source/Source/Yamo/Internal/Query/Metadata/SelectSqlModel.vb new file mode 100644 index 0000000..03f18e9 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/SelectSqlModel.vb @@ -0,0 +1,61 @@ +Imports Yamo.Metadata + +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL related model data for select statement.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class SelectSqlModel + Inherits SqlModelBase + + ''' + ''' Gets or sets custom SQL result.
+ ''' Contains if the select is not custom.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Property CustomSqlResult As SqlResultBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(model As Model, mainEntityType As Type) + MyBase.New(model, mainEntityType) + End Sub + + ''' + ''' Adds joined table.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + Public Function AddJoin(Of T)(Optional relationship As SqlEntityRelationship = Nothing) As SqlEntity + Return AddEntity(GetType(T), relationship, False) + End Function + + ''' + ''' Adds ignored joined table.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Function AddIgnoredJoin(entityType As Type) As SqlEntity + Return AddEntity(entityType, Nothing, True) + End Function + + ''' + ''' Checks whether join is used.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Function ContainsJoins() As Boolean + Return 1 < Me.Entities.Count + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlModel.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlModel.vb deleted file mode 100644 index c6b1221..0000000 --- a/Source/Source/Yamo/Internal/Query/Metadata/SqlModel.vb +++ /dev/null @@ -1,191 +0,0 @@ -Imports Yamo.Metadata - -Namespace Internal.Query.Metadata - - ''' - ''' Represents SQL related model data.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public Class SqlModel - - ''' - ''' Gets model.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- Public ReadOnly Model As Model - - ''' - ''' Store SQL entities. - ''' - Private m_Entities As List(Of SqlEntity) - - ''' - ''' Store custom SQL entities. - ''' - Private m_CustomEntities As CustomSqlEntity() - - ''' - ''' Creates new instance of .
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub New(model As Model) - Me.Model = model - m_Entities = New List(Of SqlEntity) - End Sub - - ''' - ''' Sets main table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub SetMainTable(Of T)() - If Not m_Entities.Count = 0 Then - Throw New InvalidOperationException("Main table already set.") - End If - - Dim entity = Me.Model.GetEntity(GetType(T)) - Dim index = m_Entities.Count - Dim tableAlias = "T" & index.ToString(Globalization.CultureInfo.InvariantCulture) - - m_Entities.Add(New SqlEntity(entity, tableAlias, index)) - End Sub - - ''' - ''' Adds joined table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Public Sub AddJoinedTable(Of T)(Optional relationship As SqlEntityRelationship = Nothing) - If m_Entities.Count = 0 Then - Throw New InvalidOperationException("Main table isn't set yet.") - End If - - Dim entity = Me.Model.GetEntity(GetType(T)) - Dim index = m_Entities.Count - Dim tableAlias = "T" & index.ToString(Globalization.CultureInfo.InvariantCulture) - - m_Entities.Add(New SqlEntity(entity, tableAlias, index, relationship, False)) - End Sub - - ''' - ''' Adds ignored joined table.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub AddIgnoredJoinedTable(entityType As Type) - If m_Entities.Count = 0 Then - Throw New InvalidOperationException("Main table isn't set yet.") - End If - - Dim entity = Me.Model.GetEntity(entityType) - Dim index = m_Entities.Count - Dim tableAlias = "T" & index.ToString(Globalization.CultureInfo.InvariantCulture) - - m_Entities.Add(New SqlEntity(entity, tableAlias, index, Nothing, True)) - End Sub - - ''' - ''' Gets first entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetFirstEntity() As SqlEntity - Return m_Entities(0) - End Function - - ''' - ''' Gets last entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetLastEntity() As SqlEntity - Return m_Entities(m_Entities.Count - 1) - End Function - - ''' - ''' Gets entity by its index.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Public Function GetEntity(index As Int32) As SqlEntity - Return m_Entities(index) - End Function - - ''' - ''' Gets all entities.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetEntities() As SqlEntity() - Return m_Entities.ToArray() - End Function - - ''' - ''' Checks wheter join is used.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function ContainsJoins() As Boolean - Return 1 < m_Entities.Count - End Function - - ''' - ''' Gets table alias.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - ''' - Public Function GetTableAlias(index As Int32) As String - Return m_Entities(index).TableAlias - End Function - - ''' - ''' Gets table alias of the first entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetFirstTableAlias() As String - Return m_Entities(0).TableAlias - End Function - - ''' - ''' Gets table alias of the last entity.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetLastTableAlias() As String - Return m_Entities(m_Entities.Count - 1).TableAlias - End Function - - ''' - ''' Get entities count.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetEntityCount() As Int32 - Return m_Entities.Count - End Function - - ''' - ''' Sets custom entities.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Sub SetCustomEntities(entities As CustomSqlEntity()) - m_CustomEntities = entities - End Sub - - ''' - ''' Gets custom entities.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Function GetCustomEntities() As CustomSqlEntity() - Return m_CustomEntities.ToArray() - End Function - - End Class -End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlModelBase.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlModelBase.vb new file mode 100644 index 0000000..e38360a --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/SqlModelBase.vb @@ -0,0 +1,107 @@ +Imports Yamo.Metadata + +Namespace Internal.Query.Metadata + + ''' + ''' Base class for SQL related model data.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public MustInherit Class SqlModelBase + + ''' + ''' Gets model.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Model As Model + + ''' + ''' Gets main SQL entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property MainEntity As SqlEntity + Get + Return Me.Entities(0) + End Get + End Property + + ''' + ''' Gets SQL entities. + ''' + ''' + Protected ReadOnly Property Entities As List(Of SqlEntity) + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(model As Model, mainEntityType As Type) + Me.Model = model + Me.Entities = New List(Of SqlEntity) + + Dim entity = Me.Model.GetEntity(mainEntityType) + Dim tableAlias = "T0" + + Me.Entities.Add(New SqlEntity(entity, tableAlias, 0)) + End Sub + + ''' + ''' Adds SQL entity used in the query. + ''' + ''' + ''' + ''' + ''' + Protected Function AddEntity(entityType As Type, relationship As SqlEntityRelationship, isIgnored As Boolean) As SqlEntity + Dim entity = Me.Model.GetEntity(entityType) + Dim index = Me.Entities.Count + Dim tableAlias = "T" & index.ToString(Globalization.CultureInfo.InvariantCulture) + Dim sqlEntity = New SqlEntity(entity, tableAlias, index, relationship, isIgnored) + + Me.Entities.Add(sqlEntity) + + Return sqlEntity + End Function + + ''' + ''' Gets entity by its index.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Function GetEntity(index As Int32) As SqlEntity + Return Me.Entities(index) + End Function + + ''' + ''' Gets last entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Function GetLastEntity() As SqlEntity + Return Me.Entities.Last() + End Function + + ''' + ''' Gets all entities.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Function GetEntities() As SqlEntity() + Return Me.Entities.ToArray() + End Function + + ''' + ''' Get entities count.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Function GetEntityCount() As Int32 + Return Me.Entities.Count + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb new file mode 100644 index 0000000..4e7e0d9 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb @@ -0,0 +1,26 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Base class for SQL results.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public MustInherit Class SqlResultBase + + ''' + ''' Gets type of the result.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property ResultType As Type + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Sub New(resultType As Type) + Me.ResultType = resultType + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/UpdateSqlModel.vb b/Source/Source/Yamo/Internal/Query/Metadata/UpdateSqlModel.vb new file mode 100644 index 0000000..92b95aa --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/UpdateSqlModel.vb @@ -0,0 +1,23 @@ +Imports Yamo.Metadata + +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL related model data for update statement.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class UpdateSqlModel + Inherits SqlModelBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(model As Model, mainEntityType As Type) + MyBase.New(model, mainEntityType) + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb new file mode 100644 index 0000000..e3aa386 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb @@ -0,0 +1,29 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL result of a value tuple.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class ValueTupleSqlResult + Inherits SqlResultBase + + ''' + ''' Gets nested SQL results which represent value tuple elements.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Items As SqlResultBase() + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(resultType As Type, items As SqlResultBase()) + MyBase.New(resultType) + Me.Items = items + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/QueryExecutor.vb b/Source/Source/Yamo/Internal/Query/QueryExecutor.vb index 7345424..0e1623c 100644 --- a/Source/Source/Yamo/Internal/Query/QueryExecutor.vb +++ b/Source/Source/Yamo/Internal/Query/QueryExecutor.vb @@ -3,6 +3,8 @@ Imports System.Data.Common Imports Yamo Imports Yamo.Infrastructure Imports Yamo.Internal.Helpers +Imports Yamo.Internal.Query.Metadata +Imports Yamo.Metadata Namespace Internal.Query @@ -91,29 +93,20 @@ Namespace Internal.Query Public Function QueryFirstOrDefault(Of T)(query As Query) As T Dim value As T = Nothing Dim resultType = GetType(T) - Dim isValueTuple = Types.IsValueTupleOrNullableValueTuple(resultType) - Dim isModel = Not isValueTuple AndAlso Types.IsProbablyModel(resultType) - - Dim reader As Func(Of IDataReader, CustomEntityReadInfo(), T) = Nothing - Dim customEntityInfos As CustomEntityReadInfo() = Nothing + Dim sqlResult = TryCreateSqlResult(m_DbContext.Model, resultType) + Dim isValueTupleOrEntity = TypeOf sqlResult Is ValueTupleSqlResult OrElse TypeOf sqlResult Is EntitySqlResult Using command = CreateCommand(query) - If isValueTuple OrElse isModel Then + If isValueTupleOrEntity Then Using dataReader = command.ExecuteReader() If dataReader.Read() Then - reader = CustomResultReaderCache.GetResultFactory(Of T)(m_DbContext.Model, resultType) - - If isValueTuple Then - customEntityInfos = CustomEntityReadInfo.CreateForValueTupleType(m_DialectProvider, m_DbContext.Model, resultType) - Else - customEntityInfos = CustomEntityReadInfo.CreateForModelType(m_DialectProvider, m_DbContext.Model, resultType) - End If - - value = DirectCast(reader(dataReader, customEntityInfos), T) + Dim reader = SqlResultReaderCache.GetReader(Of T)(m_DbContext.Model, sqlResult) + Dim readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, sqlResult) + value = DirectCast(reader(dataReader, readerData), T) End If End Using Else - ' we could use ValueType reader to avoid (un)boxing, but creating it might take more time/resources + ' we could use ValueType reader to avoid (un)boxing, but creating it might take more time/resources (TryCreateSqlResult would need to return ScalarValueSqlResult) value = m_DialectProvider.DbValueConversion.FromDbValue(Of T)(command.ExecuteScalar()) End If End Using @@ -131,28 +124,25 @@ Namespace Internal.Query Public Function QueryList(Of T)(query As Query) As List(Of T) Dim values = New List(Of T) Dim resultType = GetType(T) - Dim isValueTuple = Types.IsValueTupleOrNullableValueTuple(resultType) - Dim isModel = Not isValueTuple AndAlso Types.IsProbablyModel(resultType) - - Dim reader As Func(Of IDataReader, CustomEntityReadInfo(), T) = Nothing - Dim customEntityInfos As CustomEntityReadInfo() = Nothing - - If isValueTuple Then - reader = CustomResultReaderCache.GetResultFactory(Of T)(m_DbContext.Model, resultType) - customEntityInfos = CustomEntityReadInfo.CreateForValueTupleType(m_DialectProvider, m_DbContext.Model, resultType) - ElseIf isModel Then - reader = CustomResultReaderCache.GetResultFactory(Of T)(m_DbContext.Model, resultType) - customEntityInfos = CustomEntityReadInfo.CreateForModelType(m_DialectProvider, m_DbContext.Model, resultType) + Dim sqlResult = TryCreateSqlResult(m_DbContext.Model, resultType) + Dim isValueTupleOrEntity = TypeOf sqlResult Is ValueTupleSqlResult OrElse TypeOf sqlResult Is EntitySqlResult + + Dim reader As Func(Of IDataReader, ReaderDataBase, T) = Nothing + Dim readerData As ReaderDataBase = Nothing + + If isValueTupleOrEntity Then + reader = SqlResultReaderCache.GetReader(Of T)(m_DbContext.Model, sqlResult) + readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, sqlResult) End If Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() While dataReader.Read() - If isValueTuple OrElse isModel Then - Dim value = DirectCast(reader(dataReader, customEntityInfos), T) + If isValueTupleOrEntity Then + Dim value = DirectCast(reader(dataReader, readerData), T) values.Add(value) Else - ' we could use ValueType reader to avoid (un)boxing, but creating it might take more time/resources + ' we could use ValueType reader to avoid (un)boxing, but creating it might take more time/resources (TryCreateSqlResult would need to return ScalarValueSqlResult) Dim value = m_DialectProvider.DbValueConversion.FromDbValue(Of T)(dataReader.GetValue(0)) values.Add(value) End If @@ -173,12 +163,12 @@ Namespace Internal.Query ''' Public Function ReadFirstOrDefault(Of T)(query As SelectQuery, behavior As CollectionNavigationFillBehavior) As T If query.Model.ContainsJoins() Then - Dim entityInfos = EntityReadInfoCollection.Create(m_DialectProvider, query.Model) + Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, query.Model.Model, query.Model.GetEntities()) - If entityInfos.HasCollectionNavigation Then - Return ReadJoinedFirstOrDefaultWithCollectionNavigation(Of T)(query, entityInfos, behavior) + If readerDataCollection.HasCollectionNavigation Then + Return ReadJoinedFirstOrDefaultWithCollectionNavigation(Of T)(query, readerDataCollection, behavior) Else - Return ReadJoinedFirstOrDefaultWithoutCollectionNavigation(Of T)(query, entityInfos) + Return ReadJoinedFirstOrDefaultWithoutCollectionNavigation(Of T)(query, readerDataCollection) End If Else Return ReadSimpleFirstOrDefault(Of T)(query) @@ -194,12 +184,12 @@ Namespace Internal.Query ''' Public Function ReadList(Of T)(query As SelectQuery) As List(Of T) If query.Model.ContainsJoins() Then - Dim entityInfos = EntityReadInfoCollection.Create(m_DialectProvider, query.Model) + Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, query.Model.Model, query.Model.GetEntities()) - If entityInfos.HasCollectionNavigation Then - Return ReadJoinedListWithCollectionNavigation(Of T)(query, entityInfos) + If readerDataCollection.HasCollectionNavigation Then + Return ReadJoinedListWithCollectionNavigation(Of T)(query, readerDataCollection) Else - Return ReadJoinedListWithoutCollectionNavigation(Of T)(query, entityInfos) + Return ReadJoinedListWithoutCollectionNavigation(Of T)(query, readerDataCollection) End If Else Return ReadSimpleList(Of T)(query) @@ -214,15 +204,15 @@ Namespace Internal.Query ''' ''' Public Function ReadCustomFirstOrDefault(Of T)(query As SelectQuery) As T - Dim reader = CustomResultReaderCache.GetResultFactory(Of T)(m_DbContext.Model, GetType(T)) - Dim customEntityInfos = CustomEntityReadInfo.Create(m_DialectProvider, query.Model) + Dim reader = SqlResultReaderCache.GetReader(Of T)(m_DbContext.Model, query.Model.CustomSqlResult) + Dim readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, query.Model.CustomSqlResult) Dim value As T = Nothing Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() If dataReader.Read() Then - value = DirectCast(reader(dataReader, customEntityInfos), T) + value = DirectCast(reader(dataReader, readerData), T) ' NOTE - ResetDbPropertyModifiedTracking is called in reader End If End Using @@ -239,7 +229,7 @@ Namespace Internal.Query ''' Private Function ReadSimpleFirstOrDefault(Of T)(query As SelectQuery) As T Dim reader = EntityReaderCache.GetReader(m_DialectProvider, m_DbContext.Model, GetType(T)) - Dim includedColumns = query.Model.GetFirstEntity().IncludedColumns + Dim includedColumns = query.Model.MainEntity.IncludedColumns Dim value As T = Nothing @@ -260,15 +250,15 @@ Namespace Internal.Query '''
''' ''' - ''' + ''' ''' - Private Function ReadJoinedFirstOrDefaultWithoutCollectionNavigation(Of T)(query As SelectQuery, entityInfos As EntityReadInfoCollection) As T + Private Function ReadJoinedFirstOrDefaultWithoutCollectionNavigation(Of T)(query As SelectQuery, readerDataCollection As EntitySqlResultReaderDataCollection) As T Dim value As T = Nothing Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() If dataReader.Read() Then - value = DirectCast(Read(entityInfos, entityInfos.Items(0), dataReader, Nothing), T) + value = DirectCast(Read(readerDataCollection, readerDataCollection.Items(0), dataReader, Nothing), T) End If End Using End Using @@ -281,34 +271,34 @@ Namespace Internal.Query '''
''' ''' - ''' + ''' ''' ''' - Private Function ReadJoinedFirstOrDefaultWithCollectionNavigation(Of T)(query As SelectQuery, entityInfos As EntityReadInfoCollection, behavior As CollectionNavigationFillBehavior) As T - Dim cache = New ReaderEntityValueCache(entityInfos.Count) - Dim pks = New Object(entityInfos.Count - 1) {} + Private Function ReadJoinedFirstOrDefaultWithCollectionNavigation(Of T)(query As SelectQuery, readerDataCollection As EntitySqlResultReaderDataCollection, behavior As CollectionNavigationFillBehavior) As T + Dim cache = New ReaderEntityValueCache(readerDataCollection.Count) + Dim pks = New Object(readerDataCollection.Count - 1) {} Dim value As T = Nothing Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() If dataReader.Read() Then - entityInfos.FillPks(dataReader, pks) - value = DirectCast(Read(entityInfos, entityInfos.Items(0), cache, pks, dataReader, Nothing), T) + readerDataCollection.FillPks(dataReader, pks) + value = DirectCast(Read(readerDataCollection, readerDataCollection.Items(0), cache, pks, dataReader, Nothing), T) End If - Dim key = entityInfos.GetChainKey(0, pks) + Dim key = readerDataCollection.GetChainKey(0, pks) If value IsNot Nothing AndAlso Not behavior = CollectionNavigationFillBehavior.ProcessOnlyFirstRow Then Dim processUntilMainEntityChange = behavior = CollectionNavigationFillBehavior.ProcessUntilMainEntityChange While dataReader.Read() - entityInfos.FillPks(dataReader, pks) + readerDataCollection.FillPks(dataReader, pks) - Dim sameMainEntity = key = entityInfos.GetChainKey(0, pks) + Dim sameMainEntity = key = readerDataCollection.GetChainKey(0, pks) If sameMainEntity Then - Read(entityInfos, entityInfos.Items(0), cache, pks, dataReader, Nothing) + Read(readerDataCollection, readerDataCollection.Items(0), cache, pks, dataReader, Nothing) ElseIf processUntilMainEntityChange Then Exit While End If @@ -328,15 +318,15 @@ Namespace Internal.Query ''' ''' Public Function ReadCustomList(Of T)(query As SelectQuery) As List(Of T) - Dim reader = CustomResultReaderCache.GetResultFactory(Of T)(m_DbContext.Model, GetType(T)) - Dim customEntityInfos = CustomEntityReadInfo.Create(m_DialectProvider, query.Model) + Dim reader = SqlResultReaderCache.GetReader(Of T)(m_DbContext.Model, query.Model.CustomSqlResult) + Dim readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, query.Model.CustomSqlResult) Dim values = New List(Of T) Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() While dataReader.Read() - Dim value = DirectCast(reader(dataReader, customEntityInfos), T) + Dim value = DirectCast(reader(dataReader, readerData), T) ' NOTE - ResetDbPropertyModifiedTracking is called in reader values.Add(value) End While @@ -354,7 +344,7 @@ Namespace Internal.Query ''' Private Function ReadSimpleList(Of T)(query As SelectQuery) As List(Of T) Dim reader = EntityReaderCache.GetReader(m_DialectProvider, m_DbContext.Model, GetType(T)) - Dim includedColumns = query.Model.GetFirstEntity().IncludedColumns + Dim includedColumns = query.Model.MainEntity.IncludedColumns Dim values = New List(Of T) @@ -376,15 +366,15 @@ Namespace Internal.Query '''
''' ''' - ''' + ''' ''' - Private Function ReadJoinedListWithoutCollectionNavigation(Of T)(query As SelectQuery, entityInfos As EntityReadInfoCollection) As List(Of T) + Private Function ReadJoinedListWithoutCollectionNavigation(Of T)(query As SelectQuery, readerDataCollection As EntitySqlResultReaderDataCollection) As List(Of T) Dim values = New List(Of T) Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() While dataReader.Read() - Dim masterValue = Read(entityInfos, entityInfos.Items(0), dataReader, Nothing) + Dim masterValue = Read(readerDataCollection, readerDataCollection.Items(0), dataReader, Nothing) If masterValue IsNot Nothing Then values.Add(DirectCast(masterValue, T)) @@ -401,20 +391,20 @@ Namespace Internal.Query '''
''' ''' - ''' + ''' ''' - Private Function ReadJoinedListWithCollectionNavigation(Of T)(query As SelectQuery, entityInfos As EntityReadInfoCollection) As List(Of T) - Dim cache = New ReaderEntityValueCache(entityInfos.Count) - Dim pks = New Object(entityInfos.Count - 1) {} + Private Function ReadJoinedListWithCollectionNavigation(Of T)(query As SelectQuery, readerDataCollection As EntitySqlResultReaderDataCollection) As List(Of T) + Dim cache = New ReaderEntityValueCache(readerDataCollection.Count) + Dim pks = New Object(readerDataCollection.Count - 1) {} Dim values = New List(Of T) Using command = CreateCommand(query) Using dataReader = command.ExecuteReader() While dataReader.Read() - entityInfos.FillPks(dataReader, pks) + readerDataCollection.FillPks(dataReader, pks) - Dim masterValue = Read(entityInfos, entityInfos.Items(0), cache, pks, dataReader, Nothing) + Dim masterValue = Read(readerDataCollection, readerDataCollection.Items(0), cache, pks, dataReader, Nothing) If masterValue IsNot Nothing Then values.Add(DirectCast(masterValue, T)) @@ -429,31 +419,31 @@ Namespace Internal.Query ''' ''' Reads entity record. Only 1:1 joins are present in the query. ''' - ''' - ''' + ''' + ''' ''' ''' ''' - Private Function Read(entityInfos As EntityReadInfoCollection, entityInfo As EntityReadInfo, dataReader As IDataReader, declaringValue As Object) As Object + Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, dataReader As IDataReader, declaringValue As Object) As Object Dim value As Object - Dim entityIndex = entityInfo.Entity.Index + Dim entityIndex = readerData.Entity.Index - If entityInfo.Entity.IsExcludedOrIgnored Then + If readerData.Entity.IsExcludedOrIgnored Then Return Nothing End If - If Not entityInfo.ContainsPKReader(dataReader, entityInfo.ReaderIndex, entityInfo.PKOffsets) Then + If Not readerData.ContainsPKReader(dataReader, readerData.ReaderIndex, readerData.PKOffsets) Then Return Nothing End If - value = entityInfo.Reader(dataReader, entityInfo.ReaderIndex, entityInfo.Entity.IncludedColumns) - FillRelationships(entityInfo, value, declaringValue) + value = readerData.Reader(dataReader, readerData.ReaderIndex, readerData.Entity.IncludedColumns) + FillRelationships(readerData, value, declaringValue) - If entityInfo.HasRelatedEntities Then - For i = 0 To entityInfo.RelatedEntities.Count - 1 - Dim index = entityInfo.RelatedEntities(i) - Dim relatedEntity = entityInfos.Items(index) - Read(entityInfos, relatedEntity, dataReader, value) + If readerData.HasRelatedEntities Then + For i = 0 To readerData.RelatedEntities.Count - 1 + Dim index = readerData.RelatedEntities(i) + Dim relatedEntityReaderData = readerDataCollection.Items(index) + Read(readerDataCollection, relatedEntityReaderData, dataReader, value) Next End If @@ -465,19 +455,19 @@ Namespace Internal.Query ''' ''' Reads entity record. 1:N joins are present in the query. ''' - ''' - ''' + ''' + ''' ''' ''' ''' ''' ''' - Private Function Read(entityInfos As EntityReadInfoCollection, entityInfo As EntityReadInfo, cache As ReaderEntityValueCache, pks As Object(), dataReader As IDataReader, declaringValue As Object) As Object + Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, cache As ReaderEntityValueCache, pks As Object(), dataReader As IDataReader, declaringValue As Object) As Object Dim value As Object = Nothing - Dim entityIndex = entityInfo.Entity.Index + Dim entityIndex = readerData.Entity.Index Dim valueFromCache = False - If entityInfo.Entity.IsExcludedOrIgnored Then + If readerData.Entity.IsExcludedOrIgnored Then Return Nothing End If @@ -485,21 +475,21 @@ Namespace Internal.Query Return Nothing End If - Dim key = entityInfos.GetChainKey(entityIndex, pks) + Dim key = readerDataCollection.GetChainKey(entityIndex, pks) If cache.TryGetValue(entityIndex, key, value) Then valueFromCache = True Else - value = entityInfo.Reader(dataReader, entityInfo.ReaderIndex, entityInfo.Entity.IncludedColumns) + value = readerData.Reader(dataReader, readerData.ReaderIndex, readerData.Entity.IncludedColumns) cache.AddValue(entityIndex, key, value) - FillRelationships(entityInfo, value, declaringValue) + FillRelationships(readerData, value, declaringValue) End If - If entityInfo.HasRelatedEntities Then - For i = 0 To entityInfo.RelatedEntities.Count - 1 - Dim index = entityInfo.RelatedEntities(i) - Dim relatedEntity = entityInfos.Items(index) - Read(entityInfos, relatedEntity, cache, pks, dataReader, value) + If readerData.HasRelatedEntities Then + For i = 0 To readerData.RelatedEntities.Count - 1 + Dim index = readerData.RelatedEntities(i) + Dim relatedEntityReaderData = readerDataCollection.Items(index) + Read(readerDataCollection, relatedEntityReaderData, cache, pks, dataReader, value) Next End If @@ -524,18 +514,18 @@ Namespace Internal.Query ''' ''' Fills relationhip properties with instances of related entities. ''' - ''' + ''' ''' ''' - Private Sub FillRelationships(entityReadInfo As EntityReadInfo, value As Object, declaringValue As Object) - If entityReadInfo.HasCollectionNavigation Then - For i = 0 To entityReadInfo.CollectionInitializers.Count - 1 - entityReadInfo.CollectionInitializers(i).Invoke(value) + Private Sub FillRelationships(readerData As EntitySqlResultReaderData, value As Object, declaringValue As Object) + If readerData.HasCollectionNavigation Then + For i = 0 To readerData.CollectionInitializers.Count - 1 + readerData.CollectionInitializers(i).Invoke(value) Next End If - If entityReadInfo.RelationshipSetter IsNot Nothing Then - entityReadInfo.RelationshipSetter.Invoke(declaringValue, value) + If readerData.RelationshipSetter IsNot Nothing Then + readerData.RelationshipSetter.Invoke(declaringValue, value) End If End Sub @@ -583,5 +573,56 @@ Namespace Internal.Query Return command End Function + ''' + ''' Creates instance of .
+ ''' Only expected result is or . + '''
+ ''' + ''' + ''' + Private Function TryCreateSqlResult(model As Model, resultType As Type) As SqlResultBase + ' NOTE: right now this should only be called from Query/QueryFirstOrDefault and only following types are supported: + ' - nullable and non-nullable ValueTuples: with basic value-types or model entities as a field value + ' - model entities + + If Helpers.Types.IsValueTupleOrNullableValueTuple(resultType) Then + ' ValueTuple + Dim valueTupleTypeInfo = Helpers.Types.GetValueTupleTypeInfo(resultType) + Dim items = valueTupleTypeInfo.FlattenedArguments.Select(Function(x) CreateSqlResult(model, x)).ToArray() + + Return New ValueTupleSqlResult(resultType, items) + ElseIf Helpers.Types.IsProbablyModel(resultType) Then + ' entity model + Dim entity = model.TryGetEntity(resultType) + + If entity Is Nothing Then + Throw New Exception($"Unable to create result factory for type '{resultType}'. Only value tuples and model entities are supported.") + End If + + Return New EntitySqlResult(New SqlEntity(entity)) + Else + Return Nothing + End If + End Function + + ''' + ''' Creates instance of .
+ ''' Only expected result is or . + '''
+ ''' + ''' + ''' + Private Function CreateSqlResult(model As Model, type As Type) As SqlResultBase + If Helpers.Types.IsProbablyModel(type) Then + Dim entity = model.TryGetEntity(type) + + If entity IsNot Nothing Then + Return New EntitySqlResult(New SqlEntity(entity)) + End If + End If + + Return New ScalarValueSqlResult(type) + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb b/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb new file mode 100644 index 0000000..161a5e2 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb @@ -0,0 +1,33 @@ +Namespace Internal.Query + + ''' + ''' Base class for reader data used to read values from SQL result.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public MustInherit Class ReaderDataBase + + ''' + ''' Gets index of the reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property ReaderIndex As Int32 + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Protected Sub New(readerIndex As Int32) + Me.ReaderIndex = readerIndex + End Sub + + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public MustOverride Function GetColumnCount() As Int32 + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb b/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb new file mode 100644 index 0000000..9df38ab --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb @@ -0,0 +1,239 @@ +Imports Yamo.Infrastructure +Imports Yamo.Internal.Query.Metadata +Imports Yamo.Metadata + +Namespace Internal.Query + + ''' + ''' Reader data factory.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class ReaderDataFactory + + ''' + ''' Creates new instance of . + ''' + Private Sub New() + End Sub + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, entities As SqlEntity()) As EntitySqlResultReaderDataCollection + Dim relationships = New(RelatedEntities As List(Of Int32), CollectionNavigations As List(Of CollectionNavigation))(entities.Length - 1) {} + + For index = 0 To entities.Length - 1 + Dim entity = entities(index) + + If entity.Relationship IsNot Nothing Then + Dim declaringEntityIndex = entity.Relationship.DeclaringEntity.Index + + If relationships(declaringEntityIndex).RelatedEntities Is Nothing Then + relationships(declaringEntityIndex).RelatedEntities = New List(Of Int32) + End If + + relationships(declaringEntityIndex).RelatedEntities.Add(index) + + If entity.Relationship.IsReferenceNavigation Then + ' do nothing here + ElseIf entity.Relationship.IsCollectionNavigation Then + If relationships(declaringEntityIndex).CollectionNavigations Is Nothing Then + relationships(declaringEntityIndex).CollectionNavigations = New List(Of CollectionNavigation) + End If + + relationships(declaringEntityIndex).CollectionNavigations.Add(DirectCast(entity.Relationship.RelationshipNavigation, CollectionNavigation)) + Else + Throw New NotSupportedException($"Relationship of unknown type.") + End If + End If + Next + + Dim readerDataItems = New EntitySqlResultReaderData(entities.Length - 1) {} + Dim readerIndex = 0 + + For index = 0 To entities.Length - 1 + Dim entity = entities(index) + Dim entityType = entity.Entity.EntityType + + Dim entityReader = EntityReaderCache.GetReader(dialectProvider, model, entityType) + Dim containsPKReader = EntityReaderCache.GetContainsPKReader(dialectProvider, model, entityType) + Dim pkOffsets = GetPKOffsets(entity) + Dim pkReader = EntityReaderCache.GetPKReader(dialectProvider, model, entityType) + Dim relatedEntities = relationships(index).RelatedEntities + + Dim collectionNavigations = relationships(index).CollectionNavigations + Dim collectionInitializers As Action(Of Object)() = Nothing + + If collectionNavigations IsNot Nothing Then + ' LINQ not used for performance and allocation reasons + collectionInitializers = New Action(Of Object)(collectionNavigations.Count - 1) {} + + For i = 0 To collectionNavigations.Count - 1 + collectionInitializers(i) = EntityMemberSetterCache.GetCollectionInitSetter(model, entityType, collectionNavigations(i)) + Next + End If + + Dim relationshipSetter As Action(Of Object, Object) = Nothing + + If entity.Relationship IsNot Nothing Then + relationshipSetter = EntityMemberSetterCache.GetSetter(model, entity.Relationship.DeclaringEntity.Entity.EntityType, entity.Relationship.RelationshipNavigation) + End If + + Dim readerData = New EntitySqlResultReaderData(readerIndex, entity, entityReader, containsPKReader, pkOffsets, pkReader, relatedEntities, collectionInitializers, relationshipSetter) + readerDataItems(index) = readerData + + readerIndex += entity.GetColumnCount() + Next + + Return New EntitySqlResultReaderDataCollection(readerDataItems) + End Function + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As SqlResultBase) As ReaderDataBase + Return Create(dialectProvider, model, sqlResult, 0) + End Function + + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As SqlResultBase, readerIndex As Int32) As ReaderDataBase + If TypeOf sqlResult Is AnonymousTypeSqlResult Then + Return Create(dialectProvider, model, DirectCast(sqlResult, AnonymousTypeSqlResult), readerIndex) + ElseIf TypeOf sqlResult Is ValueTupleSqlResult Then + Return Create(dialectProvider, model, DirectCast(sqlResult, ValueTupleSqlResult), readerIndex) + ElseIf TypeOf sqlResult Is EntitySqlResult Then + Return Create(dialectProvider, model, DirectCast(sqlResult, EntitySqlResult), readerIndex) + ElseIf TypeOf sqlResult Is ScalarValueSqlResult Then + Return Create(dialectProvider, model, DirectCast(sqlResult, ScalarValueSqlResult), readerIndex) + Else + Throw New NotSupportedException($"SQL result of type {sqlResult.GetType()} is not supported.") + End If + End Function + + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As AnonymousTypeSqlResult, readerIndex As Int32) As AnonymousTypeSqlResultReaderData + Dim items = Create(dialectProvider, model, sqlResult.Items, readerIndex) + Return New AnonymousTypeSqlResultReaderData(readerIndex, items) + End Function + + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As ValueTupleSqlResult, readerIndex As Int32) As ValueTupleSqlResultReaderData + Dim items = Create(dialectProvider, model, sqlResult.Items, readerIndex) + Return New ValueTupleSqlResultReaderData(readerIndex, items) + End Function + + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As EntitySqlResult, readerIndex As Int32) As EntitySqlResultReaderData + Dim entity = sqlResult.Entity + Dim entityReader = EntityReaderCache.GetReader(dialectProvider, model, entity.Entity.EntityType) + Dim containsPKReader = EntityReaderCache.GetContainsPKReader(dialectProvider, model, entity.Entity.EntityType) + Dim pkOffsets = GetPKOffsets(entity) + + Return New EntitySqlResultReaderData(readerIndex, entity, entityReader, containsPKReader, pkOffsets) + End Function + + ''' + ''' Creates new instance of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As ScalarValueSqlResult, readerIndex As Int32) As ScalarValueSqlResultReaderData + Dim reader = ValueTypeReaderCache.GetReader(dialectProvider, model, sqlResult.ResultType) + Return New ScalarValueSqlResultReaderData(readerIndex, reader) + End Function + + ''' + ''' Creates new instances of . + ''' + ''' + ''' + ''' + ''' + ''' + Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResults As SqlResultBase(), readerIndex As Int32) As ReaderDataBase() + Dim sqlResultReaderIndex = 0 + Dim result = New ReaderDataBase(sqlResults.Length - 1) {} + + For i = 0 To sqlResults.Length - 1 + Dim readerData = Create(dialectProvider, model, sqlResults(i), sqlResultReaderIndex) + result(i) = readerData + sqlResultReaderIndex += readerData.GetColumnCount() + Next + + Return result + End Function + + ''' + ''' Get primary keys offsets. + ''' + ''' + ''' + Private Shared Function GetPKOffsets(entity As SqlEntity) As Int32() + Dim includedColumns = entity.IncludedColumns + Dim pks = entity.Entity.GetKeyProperties() + Dim pkOffsets = New Int32(pks.Count - 1) {} + + If pks.Count = 0 Then + Return pkOffsets + End If + + Dim offset = 0 + Dim currentPkIndex = 0 + + For i = 0 To pks.Last().Index + If i = pks(currentPkIndex).Index Then + pkOffsets(currentPkIndex) = offset + currentPkIndex += 1 + End If + + If includedColumns(i) Then + offset += 1 + End If + Next + + Return pkOffsets + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb new file mode 100644 index 0000000..56edeef --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb @@ -0,0 +1,38 @@ +Namespace Internal.Query + + ''' + ''' Represents reader data for scalar values.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class ScalarValueSqlResultReaderData + Inherits ReaderDataBase + + ''' + ''' Gets reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Reader As Object + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(readerIndex As Int32, reader As Object) + MyBase.New(readerIndex) + Me.Reader = reader + End Sub + + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return 1 + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/SelectQuery.vb b/Source/Source/Yamo/Internal/Query/SelectQuery.vb index c804236..0e2a791 100644 --- a/Source/Source/Yamo/Internal/Query/SelectQuery.vb +++ b/Source/Source/Yamo/Internal/Query/SelectQuery.vb @@ -14,7 +14,7 @@ Namespace Internal.Query ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property Model As SqlModel + Public ReadOnly Property Model As SelectSqlModel ''' ''' Creates new instance of
@@ -22,7 +22,7 @@ Namespace Internal.Query '''
''' ''' - Sub New(sql As SqlString, model As SqlModel) + Sub New(sql As SqlString, model As SelectSqlModel) MyBase.New(sql) Me.Model = model End Sub @@ -34,7 +34,7 @@ Namespace Internal.Query ''' ''' ''' - Sub New(sql As String, parameters As IReadOnlyList(Of SqlParameter), model As SqlModel) + Sub New(sql As String, parameters As IReadOnlyList(Of SqlParameter), model As SelectSqlModel) MyBase.New(sql, parameters) Me.Model = model End Sub diff --git a/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb new file mode 100644 index 0000000..4492802 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb @@ -0,0 +1,38 @@ +Namespace Internal.Query + + ''' + ''' Represents reader data for value tuple values.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class ValueTupleSqlResultReaderData + Inherits ReaderDataBase + + ''' + ''' Gets reader data of value tuple elements.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Items As ReaderDataBase() + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(readerIndex As Int32, items As ReaderDataBase()) + MyBase.New(readerIndex) + Me.Items = items + End Sub + + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Items.Sum(Function(x) x.GetColumnCount()) + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb index 9e9d70a..7a14774 100644 --- a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb +++ b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb @@ -25,7 +25,7 @@ Namespace Internal ''' ''' Stores SQL model. ''' - Private m_Model As SqlModel + Private m_Model As SqlModelBase ''' ''' Stores expression translate mode. @@ -63,7 +63,7 @@ Namespace Internal Private m_ParameterIndex As Int32 ''' - ''' Stores wheter aliases should be used. + ''' Stores whether aliases should be used. ''' Private m_UseAliases As Boolean @@ -78,14 +78,14 @@ Namespace Internal Private m_CurrentLikeParameterFormat As String ''' - ''' Stores custom entities. + ''' Stores custom SQL result. ''' - Private m_CustomEntities As CustomSqlEntity() + Private m_CustomSqlResult As SqlResultBase ''' - ''' Stores custom entity index. + ''' Stores index of custom SQL result item. ''' - Private m_CustomEntityIndex As Int32 + Private m_CustomSqlResultItemIndex As Int32 ''' ''' Stores stack of nodes. @@ -98,7 +98,7 @@ Namespace Internal ''' ''' ''' - Public Sub New(builder As SqlExpressionBuilderBase, model As SqlModel) + Public Sub New(builder As SqlExpressionBuilderBase, model As SqlModelBase) m_Builder = builder m_Model = model m_Stack = New Stack(Of NodeInfo) @@ -124,8 +124,8 @@ Namespace Internal m_UseAliases = useAliases m_UseTableNamesOrAliases = useTableNamesOrAliases m_CurrentLikeParameterFormat = Nothing - m_CustomEntities = Nothing - m_CustomEntityIndex = 0 + m_CustomSqlResult = Nothing + m_CustomSqlResultItemIndex = 0 m_Stack.Clear() End Sub @@ -164,7 +164,7 @@ Namespace Internal ''' for and not for . ''' ''' - Public Function TranslateCustomSelect(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, CustomEntities As CustomSqlEntity()) + Public Function TranslateCustomSelect(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, SqlResult As SqlResultBase) If TypeOf expression IsNot LambdaExpression Then Throw New ArgumentException("Expression must be of type LambdaExpression.") End If @@ -177,9 +177,7 @@ Namespace Internal m_ExpressionParameters = Nothing - CustomResultReaderCache.CreateResultFactoryIfNotExists(m_Model.Model, lambda.Body, m_CustomEntities) - - Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomEntities) + Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomSqlResult) End Function 'Public Function TranslateInclude(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, CustomEntities As CustomSqlEntity()) @@ -931,31 +929,32 @@ Namespace Internal End If Dim count = args.Count + Dim items As SqlResultBase() = Nothing If m_Mode = ExpressionTranslateMode.CustomSelect Then - m_CustomEntities = New CustomSqlEntity(count - 1) {} + items = New SqlResultBase(count - 1) {} End If - Dim entities = m_Model.GetEntities().Select(Function(x) x.Entity.EntityType).ToArray() + Dim entities = m_Model.GetEntities() For i = 0 To count - 1 Dim arg = args(i) Dim type = arg.Type - Dim entityIndex = Array.IndexOf(Of Type)(entities, type) - Dim isEntity = Not entityIndex = -1 + Dim entity = entities.FirstOrDefault(Function(x) x.Entity.EntityType = type) + Dim isEntity = entity IsNot Nothing - m_CustomEntityIndex = i + m_CustomSqlResultItemIndex = i Visit(arg) If m_Mode = ExpressionTranslateMode.CustomSelect Then If isEntity Then - m_CustomEntities(i) = New CustomSqlEntity(i, entityIndex, type) + items(i) = New EntitySqlResult(entity) Else Dim columnAlias = CreateColumnAlias(i) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) - m_CustomEntities(i) = New CustomSqlEntity(i, type) + items(i) = New ScalarValueSqlResult(type) End If End If @@ -964,6 +963,12 @@ Namespace Internal End If Next + If isValueTuple Then + m_CustomSqlResult = New ValueTupleSqlResult(node.Type, items) + Else + m_CustomSqlResult = New AnonymousTypeSqlResult(node.Type, items) + End If + Return node End Function @@ -1010,27 +1015,25 @@ Namespace Internal ''' Private Function VisitInCustomSelectMode(node As Expression) As Expression If node.NodeType = ExpressionType.New Then + ' anonymous type, value tuple Return Visit(node) Else - m_CustomEntities = New CustomSqlEntity(0) {} - - Dim entities = m_Model.GetEntities().Select(Function(x) x.Entity.EntityType).ToArray() + Visit(node) Dim type = node.Type - Dim entityIndex = Array.IndexOf(Of Type)(entities, type) - Dim isEntity = Not entityIndex = -1 - - m_CustomEntityIndex = 0 + Dim entity = m_Model.GetEntities().FirstOrDefault(Function(x) x.Entity.EntityType = type) - Visit(node) + If entity Is Nothing Then + ' simple scalar value - If isEntity Then - m_CustomEntities(0) = New CustomSqlEntity(0, entityIndex, type) - Else Dim columnAlias = CreateColumnAlias(0) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) - m_CustomEntities(0) = New CustomSqlEntity(0, type) + m_CustomSqlResult = New ScalarValueSqlResult(type) + + Else + ' whole entity + m_CustomSqlResult = New EntitySqlResult(entity) End If Return node @@ -1206,7 +1209,7 @@ Namespace Internal ElseIf Not m_UseTableNamesOrAliases Then m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, prop.ColumnName) ElseIf m_UseAliases Then - Dim tableAlias = m_Model.GetTableAlias(entity.Index) + Dim tableAlias = m_Model.GetEntity(entity.Index).TableAlias m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, tableAlias) m_Sql.Append(".") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, prop.ColumnName) @@ -1249,7 +1252,7 @@ Namespace Internal End If If m_Mode = ExpressionTranslateMode.CustomSelect Then - Dim columnAlias = CreateColumnAlias(m_CustomEntityIndex, columnIndex) + Dim columnAlias = CreateColumnAlias(m_CustomSqlResultItemIndex, columnIndex) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) End If diff --git a/Source/Source/Yamo/Internal/SqlResultReaderCache.vb b/Source/Source/Yamo/Internal/SqlResultReaderCache.vb new file mode 100644 index 0000000..f3233e6 --- /dev/null +++ b/Source/Source/Yamo/Internal/SqlResultReaderCache.vb @@ -0,0 +1,104 @@ +Imports System.Data +Imports System.Linq.Expressions +Imports Yamo.Infrastructure +Imports Yamo.Internal.Query +Imports Yamo.Internal.Query.Metadata +Imports Yamo.Metadata + +Namespace Internal + + ''' + ''' SQL result reader cache.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class SqlResultReaderCache + + ''' + ''' Stores cache instances. + ''' + Private Shared m_Instances As Dictionary(Of Model, SqlResultReaderCache) + + ''' + ''' Stores cached reader instances.
+ ''' Instance type is actually Func(Of IDataReader, ReaderDataBase, T). + '''
+ Private m_Readers As Dictionary(Of Type, Object) + + ''' + ''' Initializes related static data. + ''' + Shared Sub New() + m_Instances = New Dictionary(Of Model, SqlResultReaderCache) + End Sub + + ''' + ''' Creates new instance of . + ''' + Private Sub New() + m_Readers = New Dictionary(Of Type, Object) + End Sub + + ''' + ''' Gets reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function GetReader(Of T)(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, T) + Return GetInstance(model).GetOrCreateReader(Of T)(model, sqlResult) + End Function + + ''' + ''' Gets cache instance. If it doesn't exist, it is created. + ''' + ''' + ''' + Private Shared Function GetInstance(model As Model) As SqlResultReaderCache + Dim instance As SqlResultReaderCache = Nothing + + SyncLock m_Instances + If Not m_Instances.TryGetValue(model, instance) Then + instance = New SqlResultReaderCache + m_Instances.Add(model, instance) + End If + End SyncLock + + Return instance + End Function + + ''' + ''' Gets or creates result factory. + ''' + ''' + ''' + ''' + ''' + Private Function GetOrCreateReader(Of T)(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, T) + Dim reader As Func(Of IDataReader, ReaderDataBase, T) = Nothing + Dim resultType = sqlResult.ResultType + + SyncLock m_Readers + Dim value As Object = Nothing + + If m_Readers.TryGetValue(resultType, value) Then + reader = DirectCast(value, Func(Of IDataReader, ReaderDataBase, T)) + End If + End SyncLock + + If reader Is Nothing Then + reader = DirectCast(SqlResultReaderFactory.CreateResultFactory(sqlResult), Func(Of IDataReader, ReaderDataBase, T)) + Else + Return reader + End If + + SyncLock m_Readers + m_Readers(resultType) = reader + End SyncLock + + Return reader + End Function + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Test/Yamo.Test.VB/Tests/ExpressionTests.vb b/Source/Test/Yamo.Test.VB/Tests/ExpressionTests.vb index 54505fa..1bdf4fa 100644 --- a/Source/Test/Yamo.Test.VB/Tests/ExpressionTests.vb +++ b/Source/Test/Yamo.Test.VB/Tests/ExpressionTests.vb @@ -1108,8 +1108,7 @@ Namespace Tests End Sub Private Function CreateSqlExpressionVisitor(db As DbContext) As SqlExpressionVisitor - Dim builder = New SelectSqlExpressionBuilder(db) - builder.SetMainTable(Of ItemWithAllSupportedValues)() + Dim builder = New SelectSqlExpressionBuilder(db, GetType(ItemWithAllSupportedValues)) Dim fi = builder.GetType().GetField("m_Visitor", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic) Return DirectCast(fi.GetValue(builder), SqlExpressionVisitor) From 855c0a9019831e4283b71be3fdf70ea70f763824 Mon Sep 17 00:00:00 2001 From: esentio Date: Wed, 26 May 2021 20:25:54 +0200 Subject: [PATCH 3/4] Support for filling properties not defined in model in SELECT queries --- .../Yamo.Playground.CS/Model/Article.cs | 3 + .../Yamo.Playground.CS/Model/Category.cs | 2 + Source/Samples/Yamo.Playground.CS/Program.cs | 45 ++- .../Builders/SelectSqlExpressionBuilder.vb | 208 +++++++----- .../EntityMemberSetterFactory.vb | 3 +- .../Infrastructure/SqlResultReaderFactory.vb | 31 ++ .../Query/AnonymousTypeSqlResultReaderData.vb | 18 +- .../Query/EntitySqlResultReaderData.vb | 110 ++++++- .../Query/IncludedSqlResultReaderData.vb | 37 +++ .../Query/Metadata/AnonymousTypeSqlResult.vb | 9 + .../Query/Metadata/EntitySqlResult.vb | 9 + .../Query/Metadata/ScalarValueSqlResult.vb | 9 + .../Yamo/Internal/Query/Metadata/SqlEntity.vb | 29 +- .../Query/Metadata/SqlEntityIncludedResult.vb | 46 +++ .../Internal/Query/Metadata/SqlResultBase.vb | 7 + .../Query/Metadata/ValueTupleSqlResult.vb | 9 + .../Yamo/Internal/Query/QueryExecutor.vb | 66 +++- .../Yamo/Internal/Query/ReaderDataBase.vb | 19 +- .../Yamo/Internal/Query/ReaderDataFactory.vb | 77 ++++- .../Query/ScalarValueSqlResultReaderData.vb | 18 +- .../Query/ValueTupleSqlResultReaderData.vb | 18 +- .../Yamo/Internal/SqlExpressionVisitor.vb | 167 +++++++--- .../Yamo/Internal/SqlResultReaderCache.vb | 64 +++- Source/Test/Yamo.Test/Model/Article.vb | 2 + .../Yamo.Test/Tests/SelectWithIncludeTests.vb | 309 +++++++++++++++++- 25 files changed, 1080 insertions(+), 235 deletions(-) create mode 100644 Source/Source/Yamo/Internal/Query/IncludedSqlResultReaderData.vb create mode 100644 Source/Source/Yamo/Internal/Query/Metadata/SqlEntityIncludedResult.vb diff --git a/Source/Samples/Yamo.Playground.CS/Model/Article.cs b/Source/Samples/Yamo.Playground.CS/Model/Article.cs index 2131494..8647019 100644 --- a/Source/Samples/Yamo.Playground.CS/Model/Article.cs +++ b/Source/Samples/Yamo.Playground.CS/Model/Article.cs @@ -15,5 +15,8 @@ class Article public Label Label { get; set; } public List Parts { get; set; } public List Categories { get; set; } + public decimal PriceWithDiscount { get; set; } + public string LabelDescription { get; set; } + } } diff --git a/Source/Samples/Yamo.Playground.CS/Model/Category.cs b/Source/Samples/Yamo.Playground.CS/Model/Category.cs index d018f88..e4fa5c0 100644 --- a/Source/Samples/Yamo.Playground.CS/Model/Category.cs +++ b/Source/Samples/Yamo.Playground.CS/Model/Category.cs @@ -10,5 +10,7 @@ class Category { public int Id { get; set; } public Label Label { get; set; } + + public int ArticleCount { get; set; } } } diff --git a/Source/Samples/Yamo.Playground.CS/Program.cs b/Source/Samples/Yamo.Playground.CS/Program.cs index f825fe9..2f3afcf 100644 --- a/Source/Samples/Yamo.Playground.CS/Program.cs +++ b/Source/Samples/Yamo.Playground.CS/Program.cs @@ -67,7 +67,10 @@ static void Main(string[] args) //Test44(); //Test45(); //Test46(); - Test47(); + //Test47(); + Test48(); + Test49(); + Test50(); } public static MyContext CreateContext() @@ -847,7 +850,7 @@ public static void Test46() { using (var db = CreateContext()) { - var blog = new Blog() { Title = "Lorem ipsum", Content = ""}; + var blog = new Blog() { Title = "Lorem ipsum", Content = "" }; db.Insert().WithHints("WITH (TABLOCK)").Execute(blog); @@ -920,6 +923,42 @@ public static void Test47() //Assert.AreEqual(15, result3.Parts[2].Price); } } - } + public static void Test48() + { + using (var db = CreateContext()) + { + var list = db.From() + .LeftJoin(j => j.T1.Id == j.T2.CategoryId) + .GroupBy(j => j.T1) + .SelectAll() + .ExcludeT2() + .Include(j => j.T1.ArticleCount, j => Yamo.Sql.Aggregate.Count(j.T2.ArticleId)) + .ToList(); + } + } + public static void Test49() + { + using (var db = CreateContext()) + { + var list = db.From
() + .SelectAll() + .Include(x => x.PriceWithDiscount, x => x.Price * 0.9m) + .ToList(); + } + } + + public static void Test50() + { + using (var db = CreateContext()) + { + var list = db.From
() + .LeftJoin
Private m_SelectExpression As String + ''' + ''' Stores included select expressions counter. + ''' + Private m_IncludedExpressionsCount As Int32 + ''' ''' Stores whether distincs is used. ''' @@ -118,6 +123,7 @@ Namespace Expressions.Builders m_LimitExpression = Nothing m_UseTopForLimit = False m_SelectExpression = Nothing + m_IncludedExpressionsCount = 0 m_UseDistinct = False m_Parameters = New List(Of SqlParameter) End Sub @@ -383,57 +389,36 @@ Namespace Expressions.Builders '''
''' Lambda expression with one parameter is expected. Public Sub SetLastJoinRelationship(relationship As Expression) - Dim lambda = DirectCast(relationship, LambdaExpression) - Dim parameterType = lambda.Parameters(0).Type - - Dim index As Int32 - Dim propertyName As String = Nothing + Dim result = GetEntityAndProperty(relationship, True) - If GetType(IJoin).IsAssignableFrom(parameterType) Then - If lambda.Body.NodeType = ExpressionType.MemberAccess Then - Dim node = DirectCast(lambda.Body, MemberExpression) - - If node.Expression.NodeType = ExpressionType.MemberAccess Then - index = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Expression, MemberExpression).Member.Name) - propertyName = node.Member.Name - End If - End If - - Else - Dim entities = m_Model.GetEntities() - Dim possibleDeclaringEntities = entities.Take(entities.Length - 1).Where(Function(x) x.Entity.EntityType Is parameterType).ToArray() - - If possibleDeclaringEntities.Length = 0 Then - Throw New Exception($"Cannot infer relationship, because there are no joined entities of type '{parameterType}'.") - ElseIf possibleDeclaringEntities.Length = 1 Then - index = possibleDeclaringEntities(0).Index + If result.NotFound Then + Throw New Exception($"Cannot infer relationship, because there are no joined entities of type '{result.EntityType}'.") + End If - If lambda.Body.NodeType = ExpressionType.MemberAccess Then - propertyName = DirectCast(lambda.Body, MemberExpression).Member.Name - End If - Else - Throw New Exception($"Cannot infer relationship, because there are multiple joined entities of type '{parameterType}'. Use {NameOf(IJoin)} in relationship predicate to avoid unambiguous match.") - End If + If result.MultipleResults Then + Throw New Exception($"Cannot infer relationship, because there are multiple joined entities of type '{result.EntityType}'. Use {NameOf(IJoin)} in relationship predicate to avoid unambiguous match.") End If - If propertyName Is Nothing Then + If result.PropertyName Is Nothing Then Throw New Exception("Cannot infer relationship. Use expression that contains relationship property only.") End If - Dim declaringSqlEntity = m_Model.GetEntity(index) + Dim declaringSqlEntity = result.Entity + Dim propertyType = result.PropertyType + Dim propertyName = result.PropertyName - If GetType(IList).IsAssignableFrom(lambda.ReturnType) Then - Dim genericTypes = lambda.ReturnType.GetGenericArguments() + If GetType(IList).IsAssignableFrom(propertyType) Then + Dim genericTypes = propertyType.GetGenericArguments() If Not genericTypes.Count = 1 Then - Throw New Exception($"Unable to infer item type from '{lambda.ReturnType}'.") + Throw New Exception($"Unable to infer item type from '{propertyType}'.") End If ' there is still small possibility that item type is not genericTypes(0) type, but in most cases like List(Of) we should be ok - m_Model.GetLastEntity().SetRelationship(New SqlEntityRelationship(declaringSqlEntity, New CollectionNavigation(propertyName, genericTypes(0), lambda.ReturnType))) + m_Model.GetLastEntity().SetRelationship(New SqlEntityRelationship(declaringSqlEntity, New CollectionNavigation(propertyName, genericTypes(0), propertyType))) Else - m_Model.GetLastEntity().SetRelationship(New SqlEntityRelationship(declaringSqlEntity, New ReferenceNavigation(propertyName, lambda.ReturnType))) + m_Model.GetLastEntity().SetRelationship(New SqlEntityRelationship(declaringSqlEntity, New ReferenceNavigation(propertyName, propertyType))) End If End Sub @@ -597,46 +582,22 @@ Namespace Expressions.Builders ''' ''' Lambda expression with one parameter is expected. Public Sub ExcludeSelected(propertyExpression As Expression) - ' TODO: SIP - refactor and combine with SetLastJoinRelationship + Dim result = GetEntityAndProperty(propertyExpression) - Dim lambda = DirectCast(propertyExpression, LambdaExpression) - Dim parameterType = lambda.Parameters(0).Type - - Dim index As Int32 - Dim propertyName As String = Nothing - - If GetType(IJoin).IsAssignableFrom(parameterType) Then - If lambda.Body.NodeType = ExpressionType.MemberAccess Then - Dim node = DirectCast(lambda.Body, MemberExpression) - - If node.Expression.NodeType = ExpressionType.MemberAccess Then - index = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Expression, MemberExpression).Member.Name) - propertyName = node.Member.Name - End If - End If - - Else - Dim possibleEntities = m_Model.GetEntities().Where(Function(x) x.Entity.EntityType Is parameterType).ToArray() - - If possibleEntities.Length = 0 Then - Throw New Exception($"Cannot infer entity for column exclude, because there are no joined entities of type '{parameterType}'.") - ElseIf possibleEntities.Length = 1 Then - index = possibleEntities(0).Index + If result.NotFound Then + Throw New Exception($"Cannot infer entity for column exclude, because there are no joined entities of type '{result.EntityType}'.") + End If - If lambda.Body.NodeType = ExpressionType.MemberAccess Then - propertyName = DirectCast(lambda.Body, MemberExpression).Member.Name - End If - Else - Throw New Exception($"Cannot infer entity for column exclude, because there are multiple joined entities of type '{parameterType}'. Use {NameOf(IJoin)} in exclude expression to avoid unambiguous match.") - End If + If result.MultipleResults Then + Throw New Exception($"Cannot infer entity for column exclude, because there are multiple joined entities of type '{result.EntityType}'. Use {NameOf(IJoin)} in exclude expression to avoid unambiguous match.") End If - If propertyName Is Nothing Then + If result.PropertyName Is Nothing Then Throw New Exception("Cannot infer excluded column. Use expression that contains entity property only.") End If - Dim entity = m_Model.GetEntity(index) - Dim prop = entity.Entity.GetProperty(propertyName) + Dim entity = result.Entity + Dim prop = entity.Entity.GetProperty(result.PropertyName) If prop.IsKey Then Throw New ArgumentException("Primary key columns cannot be excluded from the query.") @@ -661,11 +622,12 @@ Namespace Expressions.Builders ''' ''' Public Sub IncludeToSelected(action As Expression, entityIndexHints As Int32()) - ' TODO: SIP - implement - 'Dim result = m_Visitor.TranslateInclude(action, entityIndexHints, m_Parameters.Count) - 'm_SelectExpression = result.SqlString.Sql - 'm_Parameters.AddRange(result.SqlString.Parameters) - 'm_Model.SetCustomEntities(result.CustomEntities) + Dim result = m_Visitor.TranslateIncludeAction(action, entityIndexHints, m_Parameters.Count, m_IncludedExpressionsCount) + m_Parameters.AddRange(result.SqlString.Parameters) + m_IncludedExpressionsCount += 1 + + Dim sqlEntity = m_Model.GetEntity(result.EntityIndex) + sqlEntity.AddIncludedSqlResult(New SqlEntityIncludedResult(result.SqlString.Sql, result.PropertyName, result.Result)) End Sub ''' @@ -677,7 +639,26 @@ Namespace Expressions.Builders ''' ''' Public Sub IncludeToSelected(keySelector As Expression, valueSelector As Expression, keySelectorEntityIndexHints As Int32(), valueSelectorEntityIndexHints As Int32()) - ' TODO: SIP - implement + Dim keyResult = GetEntityAndProperty(keySelector) + + If keyResult.NotFound Then + Throw New Exception($"Cannot infer entity for column include, because there are no joined entities of type '{keyResult.EntityType}'.") + End If + + If keyResult.MultipleResults Then + Throw New Exception($"Cannot infer entity for column include, because there are multiple joined entities of type '{keyResult.EntityType}'. Use {NameOf(IJoin)} in include expression to avoid unambiguous match.") + End If + + If keyResult.PropertyName Is Nothing Then + Throw New Exception("Cannot infer included column. Use expression that contains entity property only.") + End If + + Dim valueResult = m_Visitor.TranslateIncludeValueSelector(valueSelector, valueSelectorEntityIndexHints, m_Parameters.Count, m_IncludedExpressionsCount) + m_Parameters.AddRange(valueResult.SqlString.Parameters) + m_IncludedExpressionsCount += 1 + + Dim sqlEntity = keyResult.Entity + sqlEntity.AddIncludedSqlResult(New SqlEntityIncludedResult(valueResult.SqlString.Sql, keyResult.PropertyName, valueResult.Result)) End Sub ''' @@ -736,6 +717,15 @@ Namespace Expressions.Builders Me.DialectProvider.Formatter.AppendIdentifier(sql, properties(j).ColumnName) End If Next + + Dim includedSqlResults = entity.IncludedSqlResults + + If includedSqlResults IsNot Nothing Then + For j = 0 To includedSqlResults.Count - 1 + sql.Append(", ") ' there should be at least one column already + sql.Append(includedSqlResults(j).Sql) + Next + End If End If Next End Sub @@ -815,5 +805,73 @@ Namespace Expressions.Builders Return New SelectQuery(sql.ToString(), m_Parameters, m_Model) End Function + ''' + ''' Gets entity and property name from an expression. + ''' + ''' + ''' + ''' + Private Function GetEntityAndProperty(propertyExpression As Expression, Optional excludeLastModelEntity As Boolean = False) As (EntityType As Type, Entity As SqlEntity, PropertyType As Type, PropertyName As String, NotFound As Boolean, MultipleResults As Boolean) + If TypeOf propertyExpression IsNot LambdaExpression Then + Throw New ArgumentException("Expression must be of type LambdaExpression.") + End If + + Dim lambda = DirectCast(propertyExpression, LambdaExpression) + + Dim parameterType = lambda.Parameters(0).Type + Dim returnType = lambda.ReturnType + Dim entity As SqlEntity = Nothing + Dim propertyName As String = Nothing + Dim notFound = False + Dim multipleResults = False + + If GetType(IJoin).IsAssignableFrom(parameterType) Then + If lambda.Body.NodeType = ExpressionType.MemberAccess Then + Dim node = DirectCast(lambda.Body, MemberExpression) + + If node.Expression.NodeType = ExpressionType.MemberAccess Then + Dim index = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Expression, MemberExpression).Member.Name) + + If excludeLastModelEntity AndAlso index = m_Model.GetEntityCount() - 1 Then + ' this should never happen, because we only use excludeLastModelEntity in SetLastJoinRelationship and there the IJoin doesn't contain the last entity + Else + entity = m_Model.GetEntity(index) + propertyName = node.Member.Name + End If + End If + End If + + Else + ' LINQ not used for performance and allocation reasons + + Dim entities = m_Model.GetEntities() + Dim count = entities.Length - If(excludeLastModelEntity, 1, 0) + + For i = 0 To count - 1 + Dim item = entities(i) + + If item.Entity.EntityType Is parameterType Then + If entity Is Nothing Then + entity = item + + If lambda.Body.NodeType = ExpressionType.MemberAccess Then + propertyName = DirectCast(lambda.Body, MemberExpression).Member.Name + End If + Else + entity = Nothing + multipleResults = True + Exit For + End If + End If + Next + + If entity Is Nothing AndAlso Not multipleResults Then + notFound = True + End If + End If + + Return (parameterType, entity, returnType, propertyName, notFound, multipleResults) + End Function + End Class End Namespace diff --git a/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb b/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb index 3fb8b7e..ab879de 100644 --- a/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb +++ b/Source/Source/Yamo/Infrastructure/EntityMemberSetterFactory.vb @@ -24,9 +24,8 @@ Namespace Infrastructure Dim parameters = {entityParam, valueParam} Dim entityCasted = Expression.Convert(entityParam, entityType) - Dim valueCasted = Expression.Convert(valueParam, valueType) - Dim prop = Expression.PropertyOrField(entityCasted, propertyOrFieldName) + Dim valueCasted = Expression.Convert(valueParam, prop.Type) Dim propAssign = Expression.Assign(prop, valueCasted) Dim body = Expression.Block(propAssign) diff --git a/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb b/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb index 04832bc..2a0305d 100644 --- a/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb +++ b/Source/Source/Yamo/Infrastructure/SqlResultReaderFactory.vb @@ -40,6 +40,37 @@ Namespace Infrastructure End If End Function + ''' + ''' Creates wrapper for result factory that converts value type result to an object.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + Public Shared Function CreateValueTypeToObjectResultFactoryWrapper(resultType As Type, readerFunction As Object) As Func(Of IDataReader, ReaderDataBase, Object) + Dim readerParam = Expression.Parameter(GetType(IDataReader), "reader") ' this has to be IDataRecord, otherwise Expression.Call() cannot find the method + Dim readerDataParam = Expression.Parameter(GetType(ReaderDataBase), "readerData") + Dim parameters = {readerParam, readerDataParam} + + Dim variables = New List(Of ParameterExpression) + Dim expressions = New List(Of Expression) + + Dim readerFunctionType = GetType(Func(Of , , )).MakeGenericType(GetType(IDataReader), GetType(ReaderDataBase), resultType) + Dim readerFuncVar = Expression.Variable(readerFunctionType, "readerFunc") + variables.Add(readerFuncVar) + expressions.Add(Expression.Assign(readerFuncVar, Expression.Convert(Expression.Constant(readerFunction), readerFunctionType))) + + Dim invokeMethodInfo = readerFunctionType.GetMethod("Invoke", BindingFlags.Public Or BindingFlags.Instance) + Dim invokeCall = Expression.Call(readerFuncVar, invokeMethodInfo, readerParam, readerDataParam) + + expressions.Add(Expression.Convert(invokeCall, GetType(Object))) + + Dim body = Expression.Block(variables, expressions) + + Dim wrapper = Expression.Lambda(body, parameters) + Return DirectCast(wrapper.Compile(), Func(Of IDataReader, ReaderDataBase, Object)) + End Function + ''' ''' Creates result factory. ''' diff --git a/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb index a19483e..6d85ab0 100644 --- a/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb +++ b/Source/Source/Yamo/Internal/Query/AnonymousTypeSqlResultReaderData.vb @@ -1,4 +1,6 @@ -Namespace Internal.Query +Imports Yamo.Internal.Query.Metadata + +Namespace Internal.Query ''' ''' Represents reader data for anonymous type values.
@@ -18,21 +20,13 @@ ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' ''' ''' - Public Sub New(readerIndex As Int32, items As ReaderDataBase()) - MyBase.New(readerIndex) + Public Sub New(sqlResult As AnonymousTypeSqlResult, readerIndex As Int32, items As ReaderDataBase()) + MyBase.New(sqlResult, readerIndex) Me.Items = items End Sub - ''' - ''' Gets count of columns in the resultset.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Overrides Function GetColumnCount() As Int32 - Return Me.Items.Sum(Function(x) x.GetColumnCount()) - End Function - End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb index 1370ce5..85ad7b4 100644 --- a/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb +++ b/Source/Source/Yamo/Internal/Query/EntitySqlResultReaderData.vb @@ -26,6 +26,7 @@ Namespace Internal.Query ''' ''' Gets contains primary key reader.
+ ''' Might be in scenarios when reading primary key is not necessary.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' @@ -33,6 +34,7 @@ Namespace Internal.Query ''' ''' Gets primary key offsets.
+ ''' Might be in scenarios when reading primary key is not necessary.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' @@ -40,10 +42,11 @@ Namespace Internal.Query ''' ''' Gets primary key reader.
+ ''' Might be in scenarios when reading primary key is not necessary.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property PKReader As Func(Of IDataReader, Int32, Int32(), Object) ' might be null! (allocation reasons) + Public ReadOnly Property PKReader As Func(Of IDataReader, Int32, Int32(), Object) ''' ''' Gets whether there are other entities to which this entity is declaring entity.
@@ -54,10 +57,11 @@ Namespace Internal.Query ''' ''' Gets indexes of all entities to which this entity is declaring entity.
+ ''' Contains if is .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property RelatedEntities As IReadOnlyList(Of Int32) ' might be null! (allocation reasons) + Public ReadOnly Property RelatedEntities As IReadOnlyList(Of Int32) ''' ''' Gets whether there are other entities to which this entity is declaring entity and it is 1:N relationship.
@@ -67,25 +71,100 @@ Namespace Internal.Query Public ReadOnly Property HasCollectionNavigation As Boolean ''' - ''' Gets collection initializers (might be ).
+ ''' Gets collection initializers.
+ ''' Contains if is .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property CollectionInitializers As IReadOnlyList(Of Action(Of Object)) ' might be null! (allocation reasons) + Public ReadOnly Property CollectionInitializers As IReadOnlyList(Of Action(Of Object)) + + ''' + ''' Gets whether there is a relationship setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property HasRelationshipSetter As Boolean ''' ''' Gets relationship setter.
+ ''' Contains if is .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public ReadOnly Property RelationshipSetter As Action(Of Object, Object) ' declaring entity, related entity (this one); might be null! (allocation reasons) + Public ReadOnly Property RelationshipSetter As Action(Of Object, Object) ' declaring entity, related entity (this one); + + ''' + ''' Gets whether there are included results.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property HasIncludedSqlResults As Boolean + + ''' + ''' Gets reader data for included results.
+ ''' Contains if is .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property IncludedSqlResultsReaderData As IReadOnlyList(Of IncludedSqlResultReaderData) + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Sub New(sqlResult As EntitySqlResult, readerIndex As Int32, entityReader As Func(Of IDataReader, Int32, Boolean(), Object), includedSqlResultsReaderData As IReadOnlyList(Of IncludedSqlResultReaderData)) + MyBase.New(sqlResult, readerIndex) + Me.Entity = sqlResult.Entity + Me.Reader = entityReader + Me.ContainsPKReader = Nothing + Me.PKOffsets = Nothing + Me.PKReader = Nothing + Me.HasRelatedEntities = False + Me.RelatedEntities = Nothing + Me.HasCollectionNavigation = False + Me.CollectionInitializers = Nothing + Me.HasRelationshipSetter = False + Me.RelationshipSetter = Nothing + Me.HasIncludedSqlResults = includedSqlResultsReaderData IsNot Nothing + Me.IncludedSqlResultsReaderData = includedSqlResultsReaderData + End Sub ''' ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' + ''' + ''' + ''' + ''' + Public Sub New(sqlResult As EntitySqlResult, readerIndex As Int32, entityReader As Func(Of IDataReader, Int32, Boolean(), Object), containsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean), pkOffsets As Int32()) + MyBase.New(sqlResult, readerIndex) + Me.Entity = sqlResult.Entity + Me.Reader = entityReader + Me.ContainsPKReader = containsPKReader + Me.PKOffsets = pkOffsets + Me.PKReader = Nothing + Me.HasRelatedEntities = False + Me.RelatedEntities = Nothing + Me.HasCollectionNavigation = False + Me.CollectionInitializers = Nothing + Me.HasRelationshipSetter = False + Me.RelationshipSetter = Nothing + Me.HasIncludedSqlResults = False + Me.IncludedSqlResultsReaderData = Nothing + End Sub + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' ''' - ''' ''' ''' ''' @@ -93,9 +172,10 @@ Namespace Internal.Query ''' ''' ''' - Public Sub New(readerIndex As Int32, entity As SqlEntity, entityReader As Func(Of IDataReader, Int32, Boolean(), Object), containsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean), pkOffsets As Int32(), Optional pkReader As Func(Of IDataReader, Int32, Int32(), Object) = Nothing, Optional relatedEntities As IReadOnlyList(Of Int32) = Nothing, Optional collectionInitializers As IReadOnlyList(Of Action(Of Object)) = Nothing, Optional relationshipSetter As Action(Of Object, Object) = Nothing) - MyBase.New(readerIndex) - Me.Entity = entity + ''' + Public Sub New(sqlResult As EntitySqlResult, readerIndex As Int32, entityReader As Func(Of IDataReader, Int32, Boolean(), Object), containsPKReader As Func(Of IDataReader, Int32, Int32(), Boolean), pkOffsets As Int32(), pkReader As Func(Of IDataReader, Int32, Int32(), Object), relatedEntities As IReadOnlyList(Of Int32), collectionInitializers As IReadOnlyList(Of Action(Of Object)), relationshipSetter As Action(Of Object, Object), includedSqlResultsReaderData As IReadOnlyList(Of IncludedSqlResultReaderData)) + MyBase.New(sqlResult, readerIndex) + Me.Entity = sqlResult.Entity Me.Reader = entityReader Me.ContainsPKReader = containsPKReader Me.PKOffsets = pkOffsets @@ -104,17 +184,11 @@ Namespace Internal.Query Me.RelatedEntities = relatedEntities Me.HasCollectionNavigation = collectionInitializers IsNot Nothing Me.CollectionInitializers = collectionInitializers + Me.HasRelationshipSetter = relationshipSetter IsNot Nothing Me.RelationshipSetter = relationshipSetter + Me.HasIncludedSqlResults = includedSqlResultsReaderData IsNot Nothing + Me.IncludedSqlResultsReaderData = includedSqlResultsReaderData End Sub - ''' - ''' Gets count of columns in the resultset.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Overrides Function GetColumnCount() As Int32 - Return Me.Entity.GetColumnCount() - End Function - End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/IncludedSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/IncludedSqlResultReaderData.vb new file mode 100644 index 0000000..51b3562 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/IncludedSqlResultReaderData.vb @@ -0,0 +1,37 @@ +Namespace Internal.Query + + ''' + ''' Represents reader data for included entity result values.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class IncludedSqlResultReaderData + + ' TODO: SIP - structure instead? + + ''' + ''' Gets property setter.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Setter As Action(Of Object, Object) ' declaring entity, value + + ''' + ''' Reader data.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property ReaderData As ReaderDataBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + Public Sub New(setter As Action(Of Object, Object), readerData As ReaderDataBase) + Me.Setter = setter + Me.ReaderData = readerData + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb index d764d02..0480cc6 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/AnonymousTypeSqlResult.vb @@ -25,5 +25,14 @@ Me.Items = items End Sub + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Items.Sum(Function(x) x.GetColumnCount()) + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb index 453af94..7ac1baf 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/EntitySqlResult.vb @@ -24,5 +24,14 @@ Me.Entity = entity End Sub + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Entity.GetColumnCount() + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb index 9a574d2..d59deec 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/ScalarValueSqlResult.vb @@ -16,5 +16,14 @@ MyBase.New(resultType) End Sub + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return 1 + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlEntity.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlEntity.vb index 1225637..01e028e 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/SqlEntity.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/SqlEntity.vb @@ -67,6 +67,13 @@ Namespace Internal.Query.Metadata ''' Public ReadOnly Property IncludedColumns As Boolean() + ''' + ''' Gets included SQL results.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' List of included results or if no additional results are included. + Public ReadOnly Property IncludedSqlResults As List(Of SqlEntityIncludedResult) + ''' ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -99,6 +106,7 @@ Namespace Internal.Query.Metadata Next Me.IncludedColumns = includedColumns + Me.IncludedSqlResults = Nothing End Sub ''' @@ -134,12 +142,14 @@ Namespace Internal.Query.Metadata End Sub ''' - ''' Gets count of included columns. This returns non-zero count, even if table is ignored (unless the whole table is excluded).
+ ''' Gets count of included columns. Only columns representing entity properties are counted, not columns representing included result(s).
+ ''' This returns non-zero count, even if table is ignored (unless the whole table is excluded).
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' ''' - Public Function GetColumnCount() As Int32 - If Me.IsExcluded Then + Public Function GetColumnCount(Optional ignoreExclusion As Boolean = False) As Int32 + If Me.IsExcluded AndAlso Not ignoreExclusion Then Return 0 End If @@ -154,5 +164,18 @@ Namespace Internal.Query.Metadata Return count End Function + ''' + ''' Adds included SQL result.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Sub AddIncludedSqlResult(includedSqlResult As SqlEntityIncludedResult) + If Me.IncludedSqlResults Is Nothing Then + Me._IncludedSqlResults = New List(Of SqlEntityIncludedResult) + End If + + Me.IncludedSqlResults.Add(includedSqlResult) + End Sub + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlEntityIncludedResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlEntityIncludedResult.vb new file mode 100644 index 0000000..79a6b69 --- /dev/null +++ b/Source/Source/Yamo/Internal/Query/Metadata/SqlEntityIncludedResult.vb @@ -0,0 +1,46 @@ +Namespace Internal.Query.Metadata + + ''' + ''' Represents SQL related data of a result included to an entity.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ Public Class SqlEntityIncludedResult + + ' TODO: SIP - structure instead? + + ''' + ''' Gets SQL select expression of included column(s).
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Sql As String + + ''' + ''' Gets property name of an entity, that should be filled with the result value.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property PropertyName As String + + ''' + ''' Gets SQL result.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property Result As SqlResultBase + + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + Public Sub New(sql As String, propertyName As String, result As SqlResultBase) + Me.Sql = sql + Me.PropertyName = propertyName + Me.Result = result + End Sub + + End Class +End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb b/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb index 4e7e0d9..44280c6 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/SqlResultBase.vb @@ -22,5 +22,12 @@ Me.ResultType = resultType End Sub + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public MustOverride Function GetColumnCount() As Int32 + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb b/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb index e3aa386..38bae5b 100644 --- a/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb +++ b/Source/Source/Yamo/Internal/Query/Metadata/ValueTupleSqlResult.vb @@ -25,5 +25,14 @@ Me.Items = items End Sub + ''' + ''' Gets count of columns in the resultset.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public Overrides Function GetColumnCount() As Int32 + Return Me.Items.Sum(Function(x) x.GetColumnCount()) + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/QueryExecutor.vb b/Source/Source/Yamo/Internal/Query/QueryExecutor.vb index 0e1623c..2275f6c 100644 --- a/Source/Source/Yamo/Internal/Query/QueryExecutor.vb +++ b/Source/Source/Yamo/Internal/Query/QueryExecutor.vb @@ -163,7 +163,7 @@ Namespace Internal.Query ''' Public Function ReadFirstOrDefault(Of T)(query As SelectQuery, behavior As CollectionNavigationFillBehavior) As T If query.Model.ContainsJoins() Then - Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, query.Model.Model, query.Model.GetEntities()) + Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, query.Model.GetEntities()) If readerDataCollection.HasCollectionNavigation Then Return ReadJoinedFirstOrDefaultWithCollectionNavigation(Of T)(query, readerDataCollection, behavior) @@ -184,7 +184,7 @@ Namespace Internal.Query ''' Public Function ReadList(Of T)(query As SelectQuery) As List(Of T) If query.Model.ContainsJoins() Then - Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, query.Model.Model, query.Model.GetEntities()) + Dim readerDataCollection = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, query.Model.GetEntities()) If readerDataCollection.HasCollectionNavigation Then Return ReadJoinedListWithCollectionNavigation(Of T)(query, readerDataCollection) @@ -228,8 +228,10 @@ Namespace Internal.Query ''' ''' Private Function ReadSimpleFirstOrDefault(Of T)(query As SelectQuery) As T - Dim reader = EntityReaderCache.GetReader(m_DialectProvider, m_DbContext.Model, GetType(T)) - Dim includedColumns = query.Model.MainEntity.IncludedColumns + Dim entity = query.Model.MainEntity + Dim readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, entity) + Dim reader = readerData.Reader + Dim includedColumns = entity.IncludedColumns Dim value As T = Nothing @@ -237,6 +239,7 @@ Namespace Internal.Query Using dataReader = command.ExecuteReader() If dataReader.Read() Then value = DirectCast(reader(dataReader, 0, includedColumns), T) + FillIncluded(readerData, dataReader, value) ResetDbPropertyModifiedTracking(value) End If End Using @@ -343,8 +346,10 @@ Namespace Internal.Query ''' ''' Private Function ReadSimpleList(Of T)(query As SelectQuery) As List(Of T) - Dim reader = EntityReaderCache.GetReader(m_DialectProvider, m_DbContext.Model, GetType(T)) - Dim includedColumns = query.Model.MainEntity.IncludedColumns + Dim entity = query.Model.MainEntity + Dim readerData = ReaderDataFactory.Create(m_DialectProvider, m_DbContext.Model, entity) + Dim reader = readerData.Reader + Dim includedColumns = entity.IncludedColumns Dim values = New List(Of T) @@ -352,6 +357,7 @@ Namespace Internal.Query Using dataReader = command.ExecuteReader() While dataReader.Read() Dim value = DirectCast(reader(dataReader, 0, includedColumns), T) + FillIncluded(readerData, dataReader, value) ResetDbPropertyModifiedTracking(value) values.Add(value) End While @@ -422,9 +428,9 @@ Namespace Internal.Query ''' ''' ''' - ''' + ''' ''' - Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, dataReader As IDataReader, declaringValue As Object) As Object + Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, dataReader As IDataReader, declaringEntity As Object) As Object Dim value As Object Dim entityIndex = readerData.Entity.Index @@ -437,7 +443,8 @@ Namespace Internal.Query End If value = readerData.Reader(dataReader, readerData.ReaderIndex, readerData.Entity.IncludedColumns) - FillRelationships(readerData, value, declaringValue) + FillRelationships(readerData, declaringEntity, value) + FillIncluded(readerData, dataReader, value) If readerData.HasRelatedEntities Then For i = 0 To readerData.RelatedEntities.Count - 1 @@ -460,9 +467,9 @@ Namespace Internal.Query ''' ''' ''' - ''' + ''' ''' - Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, cache As ReaderEntityValueCache, pks As Object(), dataReader As IDataReader, declaringValue As Object) As Object + Private Function Read(readerDataCollection As EntitySqlResultReaderDataCollection, readerData As EntitySqlResultReaderData, cache As ReaderEntityValueCache, pks As Object(), dataReader As IDataReader, declaringEntity As Object) As Object Dim value As Object = Nothing Dim entityIndex = readerData.Entity.Index Dim valueFromCache = False @@ -482,7 +489,8 @@ Namespace Internal.Query Else value = readerData.Reader(dataReader, readerData.ReaderIndex, readerData.Entity.IncludedColumns) cache.AddValue(entityIndex, key, value) - FillRelationships(readerData, value, declaringValue) + FillRelationships(readerData, declaringEntity, value) + FillIncluded(readerData, dataReader, value) End If If readerData.HasRelatedEntities Then @@ -515,17 +523,39 @@ Namespace Internal.Query ''' Fills relationhip properties with instances of related entities. '''
''' - ''' - ''' - Private Sub FillRelationships(readerData As EntitySqlResultReaderData, value As Object, declaringValue As Object) + ''' + ''' + Private Sub FillRelationships(readerData As EntitySqlResultReaderData, declaringEntity As Object, relatedEntity As Object) If readerData.HasCollectionNavigation Then For i = 0 To readerData.CollectionInitializers.Count - 1 - readerData.CollectionInitializers(i).Invoke(value) + readerData.CollectionInitializers(i).Invoke(relatedEntity) Next End If - If readerData.RelationshipSetter IsNot Nothing Then - readerData.RelationshipSetter.Invoke(declaringValue, value) + If readerData.HasRelationshipSetter Then + readerData.RelationshipSetter.Invoke(declaringEntity, relatedEntity) + End If + End Sub + + ''' + ''' Fills included properties with read values. + ''' + ''' + ''' + ''' + Private Sub FillIncluded(entityReaderData As EntitySqlResultReaderData, dataReader As IDataReader, declaringEntity As Object) + If entityReaderData.HasIncludedSqlResults Then + For i = 0 To entityReaderData.IncludedSqlResultsReaderData.Count - 1 + Dim includedSqlResultsReaderData = entityReaderData.IncludedSqlResultsReaderData(i) + + Dim sqlResult = includedSqlResultsReaderData.ReaderData.SqlResult + Dim reader = SqlResultReaderCache.GetReader(m_DbContext.Model, sqlResult) + Dim readerData = includedSqlResultsReaderData.ReaderData + + Dim value = reader(dataReader, readerData) + + includedSqlResultsReaderData.Setter.Invoke(declaringEntity, value) + Next End If End Sub diff --git a/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb b/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb index 161a5e2..aed5288 100644 --- a/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb +++ b/Source/Source/Yamo/Internal/Query/ReaderDataBase.vb @@ -1,4 +1,6 @@ -Namespace Internal.Query +Imports Yamo.Internal.Query.Metadata + +Namespace Internal.Query ''' ''' Base class for reader data used to read values from SQL result.
@@ -6,6 +8,13 @@ '''
Public MustInherit Class ReaderDataBase + ''' + ''' Gets SQL result.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + Public ReadOnly Property SqlResult As SqlResultBase + ''' ''' Gets index of the reader.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -17,8 +26,10 @@ ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' ''' - Protected Sub New(readerIndex As Int32) + Protected Sub New(sqlResult As SqlResultBase, readerIndex As Int32) + Me.SqlResult = sqlResult Me.ReaderIndex = readerIndex End Sub @@ -27,7 +38,9 @@ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' - Public MustOverride Function GetColumnCount() As Int32 + Public Overridable Function GetColumnCount() As Int32 + Return Me.SqlResult.GetColumnCount() + End Function End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb b/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb index 9df38ab..cc7246a 100644 --- a/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb +++ b/Source/Source/Yamo/Internal/Query/ReaderDataFactory.vb @@ -57,6 +57,7 @@ Namespace Internal.Query Dim readerIndex = 0 For index = 0 To entities.Length - 1 + Dim entityReaderIndex = readerIndex Dim entity = entities(index) Dim entityType = entity.Entity.EntityType @@ -84,15 +85,67 @@ Namespace Internal.Query relationshipSetter = EntityMemberSetterCache.GetSetter(model, entity.Relationship.DeclaringEntity.Entity.EntityType, entity.Relationship.RelationshipNavigation) End If - Dim readerData = New EntitySqlResultReaderData(readerIndex, entity, entityReader, containsPKReader, pkOffsets, pkReader, relatedEntities, collectionInitializers, relationshipSetter) - readerDataItems(index) = readerData - readerIndex += entity.GetColumnCount() + + Dim includedSqlResultsReaderData As IncludedSqlResultReaderData() = Nothing + Dim entityIncludedSqlResults = entity.IncludedSqlResults + + If entityIncludedSqlResults IsNot Nothing Then + includedSqlResultsReaderData = New IncludedSqlResultReaderData(entityIncludedSqlResults.Count - 1) {} + + ' LINQ not used for performance and allocation reasons + For j = 0 To entityIncludedSqlResults.Count - 1 + Dim entityIncludedSqlResult = entityIncludedSqlResults(j) + Dim setter = EntityMemberSetterCache.GetSetter(model, entityType, entityIncludedSqlResult.PropertyName, entityIncludedSqlResult.Result.ResultType) + Dim readData = Create(dialectProvider, model, entityIncludedSqlResult.Result, readerIndex) + includedSqlResultsReaderData(j) = New IncludedSqlResultReaderData(setter, readData) + readerIndex += readData.GetColumnCount() + Next + End If + + Dim readerData = New EntitySqlResultReaderData(New EntitySqlResult(entity), entityReaderIndex, entityReader, containsPKReader, pkOffsets, pkReader, relatedEntities, collectionInitializers, relationshipSetter, includedSqlResultsReaderData) + readerDataItems(index) = readerData Next Return New EntitySqlResultReaderDataCollection(readerDataItems) End Function + ''' + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + Public Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, entity As SqlEntity) As EntitySqlResultReaderData + Dim readerIndex = 0 + Dim entityReaderIndex = 0 + Dim entityType = entity.Entity.EntityType + + Dim entityReader = EntityReaderCache.GetReader(dialectProvider, model, entityType) + + readerIndex += entity.GetColumnCount() + + Dim includedSqlResultsReaderData As IncludedSqlResultReaderData() = Nothing + Dim entityIncludedSqlResults = entity.IncludedSqlResults + + If entityIncludedSqlResults IsNot Nothing Then + includedSqlResultsReaderData = New IncludedSqlResultReaderData(entityIncludedSqlResults.Count - 1) {} + + ' LINQ not used for performance and allocation reasons + For j = 0 To entityIncludedSqlResults.Count - 1 + Dim entityIncludedSqlResult = entityIncludedSqlResults(j) + Dim setter = EntityMemberSetterCache.GetSetter(model, entityType, entityIncludedSqlResult.PropertyName, entityIncludedSqlResult.Result.ResultType) + Dim readData = Create(dialectProvider, model, entityIncludedSqlResult.Result, readerIndex) + includedSqlResultsReaderData(j) = New IncludedSqlResultReaderData(setter, readData) + readerIndex += readData.GetColumnCount() + Next + End If + + Return New EntitySqlResultReaderData(New EntitySqlResult(entity), entityReaderIndex, entityReader, includedSqlResultsReaderData) + End Function + ''' ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -106,14 +159,15 @@ Namespace Internal.Query End Function ''' - ''' Creates new instance of . + ''' Creates new instance of .
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' ''' ''' ''' ''' - Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As SqlResultBase, readerIndex As Int32) As ReaderDataBase + Public Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As SqlResultBase, readerIndex As Int32) As ReaderDataBase If TypeOf sqlResult Is AnonymousTypeSqlResult Then Return Create(dialectProvider, model, DirectCast(sqlResult, AnonymousTypeSqlResult), readerIndex) ElseIf TypeOf sqlResult Is ValueTupleSqlResult Then @@ -137,7 +191,7 @@ Namespace Internal.Query ''' Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As AnonymousTypeSqlResult, readerIndex As Int32) As AnonymousTypeSqlResultReaderData Dim items = Create(dialectProvider, model, sqlResult.Items, readerIndex) - Return New AnonymousTypeSqlResultReaderData(readerIndex, items) + Return New AnonymousTypeSqlResultReaderData(sqlResult, readerIndex, items) End Function ''' @@ -150,7 +204,7 @@ Namespace Internal.Query ''' Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As ValueTupleSqlResult, readerIndex As Int32) As ValueTupleSqlResultReaderData Dim items = Create(dialectProvider, model, sqlResult.Items, readerIndex) - Return New ValueTupleSqlResultReaderData(readerIndex, items) + Return New ValueTupleSqlResultReaderData(sqlResult, readerIndex, items) End Function ''' @@ -167,7 +221,7 @@ Namespace Internal.Query Dim containsPKReader = EntityReaderCache.GetContainsPKReader(dialectProvider, model, entity.Entity.EntityType) Dim pkOffsets = GetPKOffsets(entity) - Return New EntitySqlResultReaderData(readerIndex, entity, entityReader, containsPKReader, pkOffsets) + Return New EntitySqlResultReaderData(sqlResult, readerIndex, entityReader, containsPKReader, pkOffsets) End Function ''' @@ -180,7 +234,7 @@ Namespace Internal.Query ''' Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResult As ScalarValueSqlResult, readerIndex As Int32) As ScalarValueSqlResultReaderData Dim reader = ValueTypeReaderCache.GetReader(dialectProvider, model, sqlResult.ResultType) - Return New ScalarValueSqlResultReaderData(readerIndex, reader) + Return New ScalarValueSqlResultReaderData(sqlResult, readerIndex, reader) End Function ''' @@ -192,13 +246,12 @@ Namespace Internal.Query ''' ''' Private Shared Function Create(dialectProvider As SqlDialectProvider, model As Model, sqlResults As SqlResultBase(), readerIndex As Int32) As ReaderDataBase() - Dim sqlResultReaderIndex = 0 Dim result = New ReaderDataBase(sqlResults.Length - 1) {} For i = 0 To sqlResults.Length - 1 - Dim readerData = Create(dialectProvider, model, sqlResults(i), sqlResultReaderIndex) + Dim readerData = Create(dialectProvider, model, sqlResults(i), readerIndex) result(i) = readerData - sqlResultReaderIndex += readerData.GetColumnCount() + readerIndex += readerData.GetColumnCount() Next Return result diff --git a/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb index 56edeef..20bec1d 100644 --- a/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb +++ b/Source/Source/Yamo/Internal/Query/ScalarValueSqlResultReaderData.vb @@ -1,4 +1,6 @@ -Namespace Internal.Query +Imports Yamo.Internal.Query.Metadata + +Namespace Internal.Query ''' ''' Represents reader data for scalar values.
@@ -18,21 +20,13 @@ ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' ''' ''' - Public Sub New(readerIndex As Int32, reader As Object) - MyBase.New(readerIndex) + Public Sub New(sqlResult As ScalarValueSqlResult, readerIndex As Int32, reader As Object) + MyBase.New(sqlResult, readerIndex) Me.Reader = reader End Sub - ''' - ''' Gets count of columns in the resultset.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Overrides Function GetColumnCount() As Int32 - Return 1 - End Function - End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb b/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb index 4492802..3576125 100644 --- a/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb +++ b/Source/Source/Yamo/Internal/Query/ValueTupleSqlResultReaderData.vb @@ -1,4 +1,6 @@ -Namespace Internal.Query +Imports Yamo.Internal.Query.Metadata + +Namespace Internal.Query ''' ''' Represents reader data for value tuple values.
@@ -18,21 +20,13 @@ ''' Creates new instance of .
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
+ ''' ''' ''' - Public Sub New(readerIndex As Int32, items As ReaderDataBase()) - MyBase.New(readerIndex) + Public Sub New(sqlResult As ValueTupleSqlResult, readerIndex As Int32, items As ReaderDataBase()) + MyBase.New(sqlResult, readerIndex) Me.Items = items End Sub - ''' - ''' Gets count of columns in the resultset.
- ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. - '''
- ''' - Public Overrides Function GetColumnCount() As Int32 - Return Me.Items.Sum(Function(x) x.GetColumnCount()) - End Function - End Class End Namespace \ No newline at end of file diff --git a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb index 7a14774..c8b2450 100644 --- a/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb +++ b/Source/Source/Yamo/Internal/SqlExpressionVisitor.vb @@ -62,6 +62,11 @@ Namespace Internal '''
Private m_ParameterIndex As Int32 + ''' + ''' Stores include index. + ''' + Private m_IncludeIndex As Int32 + ''' ''' Stores whether aliases should be used. ''' @@ -111,9 +116,10 @@ Namespace Internal ''' ''' ''' + ''' ''' ''' - Private Sub Initialize(lambda As LambdaExpression, mode As ExpressionTranslateMode, entityIndexHints As Int32(), parameterIndex As Int32, useAliases As Boolean, useTableNamesOrAliases As Boolean) + Private Sub Initialize(lambda As LambdaExpression, mode As ExpressionTranslateMode, entityIndexHints As Int32(), parameterIndex As Int32, includeIndex As Int32, useAliases As Boolean, useTableNamesOrAliases As Boolean) m_Mode = mode m_ExpressionParameters = lambda.Parameters m_ExpressionParametersType = If(entityIndexHints Is Nothing, ExpressionParametersType.IJoin, ExpressionParametersType.Entities) @@ -121,6 +127,7 @@ Namespace Internal m_Sql = New StringBuilder() m_Parameters = New List(Of SqlParameter) m_ParameterIndex = parameterIndex + m_IncludeIndex = includeIndex m_UseAliases = useAliases m_UseTableNamesOrAliases = useTableNamesOrAliases m_CurrentLikeParameterFormat = Nothing @@ -147,7 +154,7 @@ Namespace Internal Dim lambda = DirectCast(expression, LambdaExpression) - Initialize(lambda, mode, entityIndexHints, parameterIndex, useAliases, useTableNamesOrAliases) + Initialize(lambda, mode, entityIndexHints, parameterIndex, -1, useAliases, useTableNamesOrAliases) Visit(lambda.Body) @@ -157,7 +164,7 @@ Namespace Internal End Function ''' - ''' Translates custom select.
+ ''' Translates custom select expression.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. '''
''' @@ -171,65 +178,103 @@ Namespace Internal Dim lambda = DirectCast(expression, LambdaExpression) - Initialize(lambda, ExpressionTranslateMode.CustomSelect, entityIndexHints, parameterIndex, True, True) + Initialize(lambda, ExpressionTranslateMode.CustomSelect, entityIndexHints, parameterIndex, -1, True, True) - VisitInCustomSelectMode(lambda.Body) + VisitInCustomSelectOrIncludeMode(lambda.Body) m_ExpressionParameters = Nothing Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomSqlResult) End Function - 'Public Function TranslateInclude(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32) As (SqlString As SqlString, CustomEntities As CustomSqlEntity()) - ' If TypeOf expression IsNot LambdaExpression Then - ' Throw New ArgumentException("Expression must be of type LambdaExpression.") - ' End If + ''' + ''' Translates custom column(s) include action expression.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + ''' + Public Function TranslateIncludeAction(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32, includeIndex As Int32) As (SqlString As SqlString, EntityIndex As Int32, PropertyName As String, Result As SqlResultBase) + If TypeOf expression IsNot LambdaExpression Then + Throw New ArgumentException("Expression must be of type LambdaExpression.") + End If - ' Dim lambda = DirectCast(expression, LambdaExpression) + Dim lambda = DirectCast(expression, LambdaExpression) - ' Initialize(lambda, ExpressionTranslateMode.Include, entityIndexHints, parameterIndex, True, True) + Initialize(lambda, ExpressionTranslateMode.Include, entityIndexHints, parameterIndex, includeIndex, True, True) - ' If Not lambda.Body.NodeType = ExpressionType.Call Then - ' Throw New Exception($"Cannot process the expression. Body NodeType {lambda.Body.NodeType} is not allowed.") - ' End If + If Not lambda.Body.NodeType = ExpressionType.Call Then + Throw New Exception($"Cannot process the expression. Body NodeType {lambda.Body.NodeType} is not allowed.") + End If - ' Dim node = DirectCast(lambda.Body, MethodCallExpression) - ' Dim isEntity = False - ' Dim isJoinedEntity = False + Dim node = DirectCast(lambda.Body, MethodCallExpression) + Dim isEntity = False + Dim isJoinedEntity = False - ' If node.Method.IsSpecialName AndAlso node.Object IsNot Nothing AndAlso node.Method.Name.StartsWith("set_") Then - ' isEntity = Me.IsEntity(node.Object) - ' isJoinedEntity = Me.IsJoinedEntity(node.Object) - ' End If + If node.Method.IsSpecialName AndAlso node.Object IsNot Nothing AndAlso node.Method.Name.StartsWith("set_") Then + isEntity = Me.IsEntity(node.Object) + isJoinedEntity = Me.IsJoinedEntity(node.Object) + End If - ' Dim entityIndex As Int32 + Dim entityIndex As Int32 - ' If isEntity Then - ' entityIndex = GetEntityIndex(DirectCast(node.Object, ParameterExpression)) - ' ElseIf isJoinedEntity Then - ' entityIndex = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Object, MemberExpression).Member.Name) - ' Else - ' Throw New Exception("Cannot process the expression.") - ' End If + If isEntity Then + entityIndex = GetEntityIndex(DirectCast(node.Object, ParameterExpression)) + ElseIf isJoinedEntity Then + entityIndex = Helpers.Common.GetEntityIndexFromJoinMemberName(DirectCast(node.Object, MemberExpression).Member.Name) + Else + Throw New Exception("Cannot process the expression.") + End If - ' Dim propertyName = node.Method.Name.Substring(4) ' trim "set_" + Dim propertyName = node.Method.Name.Substring(4) ' trim "set_" + Dim valueNode = node.Arguments(0) + If valueNode.Type Is GetType(Object) AndAlso (valueNode.NodeType = ExpressionType.Convert OrElse valueNode.NodeType = ExpressionType.ConvertChecked) Then + ' get correct type in cases of assigning to property of type Object (ignore implicit cast) + valueNode = DirectCast(valueNode, UnaryExpression).Operand + End If - ' Dim arg = node.Arguments(0) + VisitInCustomSelectOrIncludeMode(valueNode) + m_ExpressionParameters = Nothing + Return (New SqlString(m_Sql.ToString(), m_Parameters), entityIndex, propertyName, m_CustomSqlResult) + End Function + ''' + ''' Translates custom column(s) include value selector expression.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + ''' + ''' + Public Function TranslateIncludeValueSelector(expression As Expression, entityIndexHints As Int32(), parameterIndex As Int32, includeIndex As Int32) As (SqlString As SqlString, Result As SqlResultBase) + If TypeOf expression IsNot LambdaExpression Then + Throw New ArgumentException("Expression must be of type LambdaExpression.") + End If + + Dim lambda = DirectCast(expression, LambdaExpression) + Initialize(lambda, ExpressionTranslateMode.Include, entityIndexHints, parameterIndex, includeIndex, True, True) - ' VisitInCustomSelectMode(lambda.Body) + Dim node = lambda.Body - ' m_ExpressionParameters = Nothing + If node.Type Is GetType(Object) AndAlso (node.NodeType = ExpressionType.Convert OrElse node.NodeType = ExpressionType.ConvertChecked) Then + ' get correct type in cases of assigning to property of type Object (ignore implicit cast) + node = DirectCast(node, UnaryExpression).Operand + End If - ' CustomResultReaderCache.CreateResultFactoryIfNotExists(m_Model.Model, lambda.Body, m_CustomEntities) + VisitInCustomSelectOrIncludeMode(node) - ' Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomEntities) - 'End Function + m_ExpressionParameters = Nothing + + Return (New SqlString(m_Sql.ToString(), m_Parameters), m_CustomSqlResult) + End Function ''' ''' Visits expression.
@@ -931,7 +976,10 @@ Namespace Internal Dim count = args.Count Dim items As SqlResultBase() = Nothing - If m_Mode = ExpressionTranslateMode.CustomSelect Then + Dim isInCustomSelectMode = m_Mode = ExpressionTranslateMode.CustomSelect + Dim isInIncludeMode = m_Mode = ExpressionTranslateMode.Include + + If isInCustomSelectMode OrElse isInIncludeMode Then items = New SqlResultBase(count - 1) {} End If @@ -947,11 +995,11 @@ Namespace Internal Visit(arg) - If m_Mode = ExpressionTranslateMode.CustomSelect Then + If isInCustomSelectMode OrElse isInIncludeMode Then If isEntity Then items(i) = New EntitySqlResult(entity) Else - Dim columnAlias = CreateColumnAlias(i) + Dim columnAlias = If(isInCustomSelectMode, CreateColumnAlias(i), CreateIncludeColumnAlias(i)) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) items(i) = New ScalarValueSqlResult(type) @@ -1009,11 +1057,11 @@ Namespace Internal End Sub ''' - ''' Visits in custom select mode. + ''' Visits in custom select or include mode. ''' ''' ''' - Private Function VisitInCustomSelectMode(node As Expression) As Expression + Private Function VisitInCustomSelectOrIncludeMode(node As Expression) As Expression If node.NodeType = ExpressionType.New Then ' anonymous type, value tuple Return Visit(node) @@ -1023,14 +1071,14 @@ Namespace Internal Dim type = node.Type Dim entity = m_Model.GetEntities().FirstOrDefault(Function(x) x.Entity.EntityType = type) + Dim isInCustomSelectMode = m_Mode = ExpressionTranslateMode.CustomSelect + If entity Is Nothing Then ' simple scalar value - - Dim columnAlias = CreateColumnAlias(0) + Dim columnAlias = If(isInCustomSelectMode, CreateColumnAlias(0), CreateIncludeColumnAlias(0)) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) m_CustomSqlResult = New ScalarValueSqlResult(type) - Else ' whole entity m_CustomSqlResult = New EntitySqlResult(entity) @@ -1171,6 +1219,15 @@ Namespace Internal Return "C" & index.ToString(Globalization.CultureInfo.InvariantCulture) End Function + ''' + ''' Creates column alias for included column. + ''' + ''' + ''' + Private Function CreateIncludeColumnAlias(index As Int32) As String + Return "CI_" & m_IncludeIndex.ToString(Globalization.CultureInfo.InvariantCulture) & "_" & index.ToString(Globalization.CultureInfo.InvariantCulture) + End Function + ''' ''' Creates column alias. ''' @@ -1181,6 +1238,16 @@ Namespace Internal Return "C" & index1.ToString(Globalization.CultureInfo.InvariantCulture) & "_" & index2.ToString(Globalization.CultureInfo.InvariantCulture) End Function + ''' + ''' Creates column alias for included column. + ''' + ''' + ''' + ''' + Private Function CreateIncludeColumnAlias(index1 As Int32, index2 As Int32) As String + Return "CI_" & m_IncludeIndex.ToString(Globalization.CultureInfo.InvariantCulture) & "_" & index1.ToString(Globalization.CultureInfo.InvariantCulture) & "_" & index2.ToString(Globalization.CultureInfo.InvariantCulture) + End Function + ''' ''' Appends new parameter. ''' @@ -1233,10 +1300,14 @@ Namespace Internal ' NOTE: excluding columns is not (yet) supported in this scenario, but column enumeration belows already supports it. ' In case exclusion is added, test this! Also, if whole table is excluded, entity.GetColumnCount() returns 0 (and we'll ' most likely get exception later). In this case we propably shouldn't support excluding whole table (it doesn't make sense anyway)! + ' Also, think about supporting included results here. + + Dim isInCustomSelectMode = m_Mode = ExpressionTranslateMode.CustomSelect + Dim isInIncludeMode = m_Mode = ExpressionTranslateMode.Include Dim isIgnored = entity.IsIgnored Dim properties = entity.Entity.GetProperties() - Dim columnCount = entity.GetColumnCount() + Dim columnCount = entity.GetColumnCount(isInIncludeMode) Dim columnIndex = 0 For propertyIndex = 0 To properties.Count - 1 @@ -1251,10 +1322,14 @@ Namespace Internal m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, properties(propertyIndex).ColumnName) End If - If m_Mode = ExpressionTranslateMode.CustomSelect Then + If isInCustomSelectMode Then Dim columnAlias = CreateColumnAlias(m_CustomSqlResultItemIndex, columnIndex) m_Sql.Append(" ") m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) + ElseIf isInIncludeMode Then + Dim columnAlias = CreateIncludeColumnAlias(m_CustomSqlResultItemIndex, columnIndex) + m_Sql.Append(" ") + m_Builder.DialectProvider.Formatter.AppendIdentifier(m_Sql, columnAlias) End If columnIndex += 1 diff --git a/Source/Source/Yamo/Internal/SqlResultReaderCache.vb b/Source/Source/Yamo/Internal/SqlResultReaderCache.vb index f3233e6..dc2ee24 100644 --- a/Source/Source/Yamo/Internal/SqlResultReaderCache.vb +++ b/Source/Source/Yamo/Internal/SqlResultReaderCache.vb @@ -24,6 +24,12 @@ Namespace Internal '''
Private m_Readers As Dictionary(Of Type, Object) + ''' + ''' Stores cached reader instances that are wrapped as Func(Of IDataReader, ReaderDataBase, Object).
+ ''' Instance type is actually Func(Of IDataReader, ReaderDataBase, Object). + '''
+ Private m_ValueTypeWrappedReaders As Dictionary(Of Type, Func(Of IDataReader, ReaderDataBase, Object)) + ''' ''' Initializes related static data. ''' @@ -36,8 +42,24 @@ Namespace Internal '''
Private Sub New() m_Readers = New Dictionary(Of Type, Object) + m_ValueTypeWrappedReaders = New Dictionary(Of Type, Func(Of IDataReader, ReaderDataBase, Object)) End Sub + ''' + ''' Gets reader.
+ ''' This API supports Yamo infrastructure and is not intended to be used directly from your code. + '''
+ ''' + ''' + ''' + Public Shared Function GetReader(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, Object) + If sqlResult.ResultType.IsValueType Then + Return GetInstance(model).GetOrCreateValueTypeToObjectWrappedReader(model, sqlResult) + Else + Return DirectCast(GetInstance(model).GetOrCreateReader(model, sqlResult), Func(Of IDataReader, ReaderDataBase, Object)) + End If + End Function + ''' ''' Gets reader.
''' This API supports Yamo infrastructure and is not intended to be used directly from your code. @@ -47,7 +69,7 @@ Namespace Internal ''' ''' Public Shared Function GetReader(Of T)(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, T) - Return GetInstance(model).GetOrCreateReader(Of T)(model, sqlResult) + Return DirectCast(GetInstance(model).GetOrCreateReader(model, sqlResult), Func(Of IDataReader, ReaderDataBase, T)) End Function ''' @@ -71,24 +93,23 @@ Namespace Internal ''' ''' Gets or creates result factory. ''' - ''' ''' ''' ''' - Private Function GetOrCreateReader(Of T)(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, T) - Dim reader As Func(Of IDataReader, ReaderDataBase, T) = Nothing + Private Function GetOrCreateReader(model As Model, sqlResult As SqlResultBase) As Object + Dim reader As Object = Nothing Dim resultType = sqlResult.ResultType SyncLock m_Readers Dim value As Object = Nothing If m_Readers.TryGetValue(resultType, value) Then - reader = DirectCast(value, Func(Of IDataReader, ReaderDataBase, T)) + reader = value End If End SyncLock If reader Is Nothing Then - reader = DirectCast(SqlResultReaderFactory.CreateResultFactory(sqlResult), Func(Of IDataReader, ReaderDataBase, T)) + reader = SqlResultReaderFactory.CreateResultFactory(sqlResult) Else Return reader End If @@ -100,5 +121,36 @@ Namespace Internal Return reader End Function + ''' + ''' Gets or creates result factory that return value type as an object. + ''' + ''' + ''' + ''' + Private Function GetOrCreateValueTypeToObjectWrappedReader(model As Model, sqlResult As SqlResultBase) As Func(Of IDataReader, ReaderDataBase, Object) + Dim reader As Func(Of IDataReader, ReaderDataBase, Object) = Nothing + Dim resultType = sqlResult.ResultType + + SyncLock m_ValueTypeWrappedReaders + Dim value As Func(Of IDataReader, ReaderDataBase, Object) = Nothing + + If m_ValueTypeWrappedReaders.TryGetValue(resultType, value) Then + reader = value + End If + End SyncLock + + If reader Is Nothing Then + reader = SqlResultReaderFactory.CreateValueTypeToObjectResultFactoryWrapper(sqlResult.ResultType, GetOrCreateReader(model, sqlResult)) + Else + Return reader + End If + + SyncLock m_ValueTypeWrappedReaders + m_ValueTypeWrappedReaders(resultType) = reader + End SyncLock + + Return reader + End Function + End Class End Namespace \ No newline at end of file diff --git a/Source/Test/Yamo.Test/Model/Article.vb b/Source/Test/Yamo.Test/Model/Article.vb index 887eb99..72892ab 100644 --- a/Source/Test/Yamo.Test/Model/Article.vb +++ b/Source/Test/Yamo.Test/Model/Article.vb @@ -18,6 +18,8 @@ Public Property PriceWithDiscount As Decimal + Public Property NullablePriceWithDiscount As Decimal? + Public Property LabelDescription As String Public Property Tag As Object diff --git a/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb b/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb index 30ea72f..cdc6fb3 100644 --- a/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb +++ b/Source/Test/Yamo.Test/Tests/SelectWithIncludeTests.vb @@ -21,6 +21,30 @@ Namespace Tests InsertItems(article1, article2, article3, label1En, label2En, label3En) + ' no join + Using db = CreateDbContext() + Dim result = db.From(Of Article). + OrderBy(Function(a) a.Id). + SelectAll(). + Include(Sub(a) a.PriceWithDiscount = a.Price * 0.9D). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + End Using + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + OrderBy(Function(a) a.Id). + SelectAll(). + Include(Sub(a) a.PriceWithDiscount = a.Price * 0.9D). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + End Using + + ' join Using db = CreateDbContext() Dim result = db.From(Of Article). Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). @@ -29,11 +53,13 @@ Namespace Tests ExcludeT2(). Include(Sub(a) a.PriceWithDiscount = a.Price * 0.9D). Include(Sub(a) a.LabelDescription = "foo"). + Include(Sub(a) a.Tag = a.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) Assert.AreEqual("foo", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.IsNull(result.Label) End Using @@ -46,11 +72,13 @@ Namespace Tests ExcludeT2(). Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + Include(Sub(j) j.T1.Tag = j.T1.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) - Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual("Article 1", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.IsNull(result.Label) End Using @@ -62,11 +90,13 @@ Namespace Tests SelectAll(). Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + Include(Sub(j) j.T1.Tag = j.T1.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) - Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual("Article 1", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.AreEqual(label1En, result.Label) End Using @@ -78,11 +108,13 @@ Namespace Tests ExcludeT2(). Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + Include(Sub(j) j.T1.Tag = j.T1.Price). ToList() CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) - CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({"Article 1", "Article 2", "Article 3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({article1.Price, article2.Price, article3.Price}, result.Select(Function(x) x.Tag).ToArray()) Assert.IsTrue(result.All(Function(x) x.Label Is Nothing)) End Using @@ -92,14 +124,15 @@ Namespace Tests Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). OrderBy(Function(j) j.T1.Id). SelectAll(). - ExcludeT2(). Include(Sub(j) j.T1.PriceWithDiscount = j.T1.Price * 0.9D). Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + Include(Sub(j) j.T1.Tag = j.T1.Price). ToList() CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) - CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({"Article 1", "Article 2", "Article 3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({article1.Price, article2.Price, article3.Price}, result.Select(Function(x) x.Tag).ToArray()) CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) End Using End Sub @@ -118,6 +151,29 @@ Namespace Tests InsertItems(article1, article2, article3, label1En, label2En, label3En) + ' no join + Using db = CreateDbContext() + Dim result = db.From(Of Article). + OrderBy(Function(a) a.Id). + SelectAll(). + Include(Function(a) a.PriceWithDiscount, Function(a) a.Price * 0.9D). + FirstOrDefault() + + Assert.AreEqual(article1, result) ' this only checks "model" properties + Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) + End Using + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + OrderBy(Function(a) a.Id). + SelectAll(). + Include(Function(a) a.PriceWithDiscount, Function(a) a.Price * 0.9D). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) + End Using + Using db = CreateDbContext() Dim result = db.From(Of Article). Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). @@ -126,11 +182,13 @@ Namespace Tests ExcludeT2(). Include(Function(a) a.PriceWithDiscount, Function(a) a.Price * 0.9D). Include(Function(a) a.LabelDescription, Function(l) l.Description). + Include(Function(a As Article) a.Tag, Function(a) a.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) - Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual("Article 1", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.IsNull(result.Label) End Using @@ -143,11 +201,13 @@ Namespace Tests ExcludeT2(). Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + Include(Function(j) j.T1.Tag, Function(j) j.T1.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) - Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual("Article 1", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.IsNull(result.Label) End Using @@ -159,11 +219,13 @@ Namespace Tests SelectAll(). Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + Include(Function(j) j.T1.Tag, Function(j) j.T1.Price). FirstOrDefault() Assert.AreEqual(article1, result) ' this only checks "model" properties Assert.AreEqual(article1.Price * 0.9D, result.PriceWithDiscount) - Assert.AreEqual("Article1", result.LabelDescription) + Assert.AreEqual("Article 1", result.LabelDescription) + Assert.AreEqual(article1.Price, result.Tag) Assert.AreEqual(label1En, result.Label) End Using @@ -175,11 +237,13 @@ Namespace Tests ExcludeT2(). Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + Include(Function(j) j.T1.Tag, Function(j) j.T1.Price). ToList() CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) - CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({"Article 1", "Article 2", "Article 3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({article1.Price, article2.Price, article3.Price}, result.Select(Function(x) x.Tag).ToArray()) Assert.IsTrue(result.All(Function(x) x.Label Is Nothing)) End Using @@ -189,14 +253,15 @@ Namespace Tests Join(Of Label)(Function(j) j.T1.Id = j.T2.Id). OrderBy(Function(j) j.T1.Id). SelectAll(). - ExcludeT2(). Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T1.Price * 0.9D). Include(Function(j) j.T1.LabelDescription, Function(j) j.T2.Description). + Include(Function(j) j.T1.Tag, Function(j) j.T1.Price). ToList() CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) - CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({"Article 1", "Article 2", "Article 3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({article1.Price, article2.Price, article3.Price}, result.Select(Function(x) x.Tag).ToArray()) CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) End Using End Sub @@ -237,7 +302,7 @@ Namespace Tests CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({article1.Price * 0.9D, article2.Price * 0.9D, article3.Price * 0.9D}, result.Select(Function(x) x.PriceWithDiscount).ToArray()) - CollectionAssert.AreEqual({"Article1", "Article2", "Article3"}, result.Select(Function(x) x.LabelDescription).ToArray()) + CollectionAssert.AreEqual({"Article 1", "Article 2", "Article 3"}, result.Select(Function(x) x.LabelDescription).ToArray()) CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) CollectionAssert.AreEqual({label1En.Id, label2En.Id, label3En.Id}, result.Select(Function(x) x.Label.Tag).ToArray()) CollectionAssert.AreEqual({article1Part1, article1Part2, article1Part3}, result(0).Parts) @@ -284,8 +349,226 @@ Namespace Tests CollectionAssert.AreEqual({article1, article2, article3}, result) ' this only checks "model" properties CollectionAssert.AreEqual({label1En, label2En, label3En}, result.Select(Function(x) x.Label).ToArray()) - CollectionAssert.AreEqual({New With {.LabelId = label1En.Id, .LabelDescription = label1En.Description}, New With {.LabelId = label2En.Id, .LabelDescription = label2En.Description}, New With {.LabelId = label3En.Id, .LabelDescription = label3En.Description}}, result.Select(Function(x) x.Tag).ToArray()) + ' NOTE: ToString() is probably easiest way how to get values from anonymous type casted to an Object + CollectionAssert.AreEqual({New With {.LabelId = label1En.Id, .LabelDescription = label1En.Description}.ToString(), New With {.LabelId = label2En.Id, .LabelDescription = label2En.Description}.ToString(), New With {.LabelId = label3En.Id, .LabelDescription = label3En.Description}.ToString()}, result.Select(Function(x) x.Tag.ToString()).ToArray()) + End Using + End Sub + + + Public Overridable Sub SelectWithIncludeOfEntity() + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + Dim article3 = Me.ModelFactory.CreateArticle(3, 300D) + Dim article4 = Me.ModelFactory.CreateArticle(4, 400D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + Dim label2En = Me.ModelFactory.CreateLabel("", 2, English, "Article 2") + Dim label3En = Me.ModelFactory.CreateLabel("", 3, English, "Article 3") + + InsertItems(article1, article2, article3, article4, label1En, label2En, label3En) + + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.Tag = j.T2). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3, article4}, result) ' this only checks "model" properties + Assert.IsTrue(result.All(Function(x) x.Label Is Nothing)) + CollectionAssert.AreEqual({label1En, label2En, label3En, Nothing}, result.Select(Function(x) x.Tag).ToArray()) + End Using + + ' same as above, but don't exclude label + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + Include(Sub(j) j.T1.Tag = j.T2). + ToList() + + CollectionAssert.AreEqual({article1, article2, article3, article4}, result) ' this only checks "model" properties + CollectionAssert.AreEqual({label1En, label2En, label3En, Nothing}, result.Select(Function(x) x.Label).ToArray()) + CollectionAssert.AreEqual({label1En, label2En, label3En, Nothing}, result.Select(Function(x) x.Tag).ToArray()) + End Using + End Sub + + + Public Overridable Sub SelectWithIncludeMissingValues() + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + + InsertItems(article1, article2, label1En) + + ' scalar and string values + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T2.Id * 0.9D). + Include(Sub(j) j.T1.NullablePriceWithDiscount = j.T2.Id * 0.9D). + Include(Sub(j) j.T1.LabelDescription = j.T2.Description). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(label1En.Id * 0.9D, result(0).PriceWithDiscount) + Assert.AreEqual(New Decimal?(label1En.Id * 0.9D), result(0).NullablePriceWithDiscount) + Assert.AreEqual(label1En.Description, result(0).LabelDescription) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(0D, result(1).PriceWithDiscount) + Assert.AreEqual(New Decimal?, result(1).NullablePriceWithDiscount) + Assert.AreEqual(Nothing, result(1).LabelDescription) + End Using + + ' entity + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.Tag = j.T2). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(label1En, result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(Nothing, result(1).Tag) + End Using + + ' value tuple elements + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.Tag = (LabelId:=j.T2.Id, NullableLabelId:=CType(j.T2.Id, Int32?), LabelDescription:=j.T2.Description, Label:=j.T2)). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual((label1En.Id, New Int32?(label1En.Id), label1En.Description, label1En), result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual((0, New Int32?(), CType(Nothing, String), CType(Nothing, Label)), result(1).Tag) + End Using + + ' value tuple elements + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.Tag = New With {.LabelId = j.T2.Id, .NullableLabelId = CType(j.T2.Id, Int32?), .LabelDescription = j.T2.Description, .Label = j.T2}). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + ' NOTE: ToString() is probably easiest way how to get values from anonymous type casted to an Object + Assert.AreEqual(New With {.LabelId = label1En.Id, .NullableLabelId = New Int32?(label1En.Id), .LabelDescription = label1En.Description, .Label = label1En}.ToString(), result(0).Tag.ToString()) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + ' NOTE: ToString() is probably easiest way how to get values from anonymous type casted to an Object + Assert.AreEqual(New With {.LabelId = 0, .NullableLabelId = New Int32?(), .LabelDescription = CType(Nothing, String), .Label = CType(Nothing, Label)}.ToString(), result(1).Tag.ToString()) End Using End Sub + + + Public Overridable Sub SelectWithIncludeCasting() + Dim article1 = Me.ModelFactory.CreateArticle(1, 100D) + Dim article2 = Me.ModelFactory.CreateArticle(2, 200D) + + Dim label1En = Me.ModelFactory.CreateLabel("", 1, English, "Article 1") + + InsertItems(article1, article2, label1En) + + ' Int32 to Decimal, Int32 to object + ' NOTE: There is a " * 1.5D / 1.5D" workaround to force returning decimal value. + ' Otherwise, there is following exception in SqlDataReader.GetDecimal(Int32 i) method for SQL Server: + ' System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Decimal'. + ' Other option would be to use CAST/CONVERT in raw SQL. + ' This is "deeper" issue in Yamo and not the only use case when it could happen. + ' For now, Yamo doesn't solve this automatically. + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.PriceWithDiscount = j.T2.Id * 1.5D / 1.5D). + Include(Sub(j) j.T1.NullablePriceWithDiscount = j.T2.Id * 1.5D / 1.5D). + Include(Sub(j) j.T1.Tag = j.T2.Id). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(CType(label1En.Id, Decimal), result(0).PriceWithDiscount) + Assert.AreEqual(CType(label1En.Id, Decimal?), result(0).NullablePriceWithDiscount) + Assert.AreEqual(label1En.Id, result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(0D, result(1).PriceWithDiscount) + Assert.AreEqual(New Decimal?, result(1).NullablePriceWithDiscount) + Assert.AreEqual(0, result(1).Tag) + End Using + + ' same as above using key value selectors + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(j) j.T1.PriceWithDiscount, Function(j) j.T2.Id * 1.5D / 1.5D). + Include(Function(j) j.T1.NullablePriceWithDiscount, Function(j) j.T2.Id * 1.5D / 1.5D). + Include(Function(j) j.T1.Tag, Function(j) j.T2.Id). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(CType(label1En.Id, Decimal), result(0).PriceWithDiscount) + Assert.AreEqual(CType(label1En.Id, Decimal?), result(0).NullablePriceWithDiscount) + Assert.AreEqual(label1En.Id, result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(0D, result(1).PriceWithDiscount) + Assert.AreEqual(New Decimal?, result(1).NullablePriceWithDiscount) + Assert.AreEqual(0, result(1).Tag) + End Using + + ' entity to object + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Sub(j) j.T1.Tag = j.T2). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(label1En, result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(Nothing, result(1).Tag) + End Using + + ' same as above using key value selectors + Using db = CreateDbContext() + Dim result = db.From(Of Article). + LeftJoin(Of Label)(Function(j) j.T1.Id = j.T2.Id). + OrderBy(Function(j) j.T1.Id). + SelectAll(). + ExcludeT2(). + Include(Function(j) j.T1.Tag, Function(j) j.T2). + ToList() + + Assert.AreEqual(article1, result(0)) ' this only checks "model" properties + Assert.AreEqual(label1En, result(0).Tag) + Assert.AreEqual(article2, result(1)) ' this only checks "model" properties + Assert.AreEqual(Nothing, result(1).Tag) + End Using + End Sub + End Class End Namespace From 5997ad714de2f6cb0ff18c8fb39b50b1514fb6b9 Mon Sep 17 00:00:00 2001 From: esentio Date: Wed, 26 May 2021 21:37:36 +0200 Subject: [PATCH 4/4] Updated samples in C# playground project --- Source/Samples/Yamo.Playground.CS/Model/Article.cs | 2 +- Source/Samples/Yamo.Playground.CS/Program.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Source/Samples/Yamo.Playground.CS/Model/Article.cs b/Source/Samples/Yamo.Playground.CS/Model/Article.cs index 8647019..90cde8e 100644 --- a/Source/Samples/Yamo.Playground.CS/Model/Article.cs +++ b/Source/Samples/Yamo.Playground.CS/Model/Article.cs @@ -17,6 +17,6 @@ class Article public List Categories { get; set; } public decimal PriceWithDiscount { get; set; } public string LabelDescription { get; set; } - + public object Tag { get; set; } } } diff --git a/Source/Samples/Yamo.Playground.CS/Program.cs b/Source/Samples/Yamo.Playground.CS/Program.cs index 2f3afcf..2d5e49f 100644 --- a/Source/Samples/Yamo.Playground.CS/Program.cs +++ b/Source/Samples/Yamo.Playground.CS/Program.cs @@ -71,6 +71,7 @@ static void Main(string[] args) Test48(); Test49(); Test50(); + Test51(); } public static MyContext CreateContext() @@ -960,5 +961,18 @@ public static void Test50() .ToList(); } } + + public static void Test51() + { + using (var db = CreateContext()) + { + var list = db.From
() + .LeftJoin