1 //--------------------------------------------------------------------- 2 // <copyright file="FunctionImportMappingComposable.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner willa 8 //--------------------------------------------------------------------- 9 10 using System.Collections.Generic; 11 using System.Data.Common; 12 using System.Data.Common.CommandTrees; 13 using System.Data.Common.CommandTrees.ExpressionBuilder; 14 using System.Data.Common.Utils; 15 using System.Data.Mapping.ViewGeneration; 16 using System.Data.Metadata.Edm; 17 using System.Data.Query.InternalTrees; 18 using System.Data.Query.PlanCompiler; 19 using System.Diagnostics; 20 using System.Linq; 21 22 namespace System.Data.Mapping 23 { 24 /// <summary> 25 /// Represents a mapping from a model function import to a store composable function. 26 /// </summary> 27 internal class FunctionImportMappingComposable : FunctionImportMapping 28 { 29 #region Constructors FunctionImportMappingComposable( EdmFunction functionImport, EdmFunction targetFunction, List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> structuralTypeMappings, EdmProperty[] targetFunctionKeys, StorageMappingItemCollection mappingItemCollection, string sourceLocation, LineInfo lineInfo)30 internal FunctionImportMappingComposable( 31 EdmFunction functionImport, 32 EdmFunction targetFunction, 33 List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> structuralTypeMappings, 34 EdmProperty[] targetFunctionKeys, 35 StorageMappingItemCollection mappingItemCollection, 36 string sourceLocation, 37 LineInfo lineInfo) 38 : base(functionImport, targetFunction) 39 { 40 EntityUtil.CheckArgumentNull(mappingItemCollection, "mappingItemCollection"); 41 Debug.Assert(functionImport.IsComposableAttribute, "functionImport.IsComposableAttribute"); 42 Debug.Assert(targetFunction.IsComposableAttribute, "targetFunction.IsComposableAttribute"); 43 Debug.Assert(functionImport.EntitySet == null || structuralTypeMappings != null, "Function import returning entities must have structuralTypeMappings."); 44 Debug.Assert(structuralTypeMappings == null || structuralTypeMappings.Count > 0, "Non-null structuralTypeMappings must not be empty."); 45 EdmType resultType; 46 Debug.Assert( 47 structuralTypeMappings != null || 48 MetadataHelper.TryGetFunctionImportReturnType<EdmType>(functionImport, 0, out resultType) && TypeSemantics.IsScalarType(resultType), 49 "Either type mappings should be specified or the function import should be Collection(Scalar)."); 50 Debug.Assert(functionImport.EntitySet == null || targetFunctionKeys != null, "Keys must be inferred for a function import returning entities."); 51 Debug.Assert(targetFunctionKeys == null || targetFunctionKeys.Length > 0, "Keys must be null or non-empty."); 52 53 m_mappingItemCollection = mappingItemCollection; 54 // We will use these parameters to target s-space function calls in the generated command tree. 55 // Since enums don't exist in s-space we need to use the underlying type. 56 m_commandParameters = functionImport.Parameters.Select(p => TypeHelpers.GetPrimitiveTypeUsageForScalar(p.TypeUsage).Parameter(p.Name)).ToArray(); 57 m_structuralTypeMappings = structuralTypeMappings; 58 m_targetFunctionKeys = targetFunctionKeys; 59 m_sourceLocation = sourceLocation; 60 m_lineInfo = lineInfo; 61 } 62 #endregion 63 64 #region Fields 65 private readonly StorageMappingItemCollection m_mappingItemCollection; 66 /// <summary> 67 /// Command parameter refs created from m_edmFunction parameters. 68 /// Used as arguments to target (s-space) function calls in the generated command tree. 69 /// </summary> 70 private readonly DbParameterReferenceExpression[] m_commandParameters; 71 /// <summary> 72 /// Result mapping as entity type hierarchy. 73 /// </summary> 74 private readonly List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> m_structuralTypeMappings; 75 /// <summary> 76 /// Keys inside the result set of the target function. Inferred based on the mapping (using c-space entity type keys). 77 /// </summary> 78 private readonly EdmProperty[] m_targetFunctionKeys; 79 /// <summary> 80 /// ITree template. Requires function argument substitution during function view expansion. 81 /// </summary> 82 private Node m_internalTreeNode; 83 private readonly string m_sourceLocation; 84 private readonly LineInfo m_lineInfo; 85 #endregion 86 87 #region Properties/Methods 88 internal EdmProperty[] TvfKeys 89 { 90 get { return m_targetFunctionKeys; } 91 } 92 93 #region GetInternalTree(...) implementation GetInternalTree(Command targetIqtCommand, IList<Node> targetIqtArguments)94 internal Node GetInternalTree(Command targetIqtCommand, IList<Node> targetIqtArguments) 95 { 96 if (m_internalTreeNode == null) 97 { 98 var viewGenErrors = new List<EdmSchemaError>(); 99 DiscriminatorMap discriminatorMap; 100 DbQueryCommandTree tree = GenerateFunctionView(viewGenErrors, out discriminatorMap); 101 if (viewGenErrors.Count > 0) 102 { 103 throw new MappingException(Helper.CombineErrorMessage(viewGenErrors)); 104 } 105 Debug.Assert(tree != null, "tree != null"); 106 107 // Convert this into an ITree first 108 Command itree = ITreeGenerator.Generate(tree, discriminatorMap); 109 var rootProject = itree.Root; // PhysicalProject(RelInput) 110 PlanCompiler.Assert(rootProject.Op.OpType == OpType.PhysicalProject, "Expected a physical projectOp at the root of the tree - found " + rootProject.Op.OpType); 111 var rootProjectOp = (PhysicalProjectOp)rootProject.Op; 112 Debug.Assert(rootProjectOp.Outputs.Count == 1, "rootProjectOp.Outputs.Count == 1"); 113 var rootInput = rootProject.Child0; // the RelInput in PhysicalProject(RelInput) 114 115 // #554756: VarVec enumerators are not cached on the shared Command instance. 116 itree.DisableVarVecEnumCaching(); 117 118 // Function import returns a collection, so convert it to a scalar by wrapping into CollectOp. 119 Node relNode = rootInput; 120 Var relVar = rootProjectOp.Outputs[0]; 121 // ProjectOp does not implement Type property, so get the type from the column map. 122 TypeUsage functionViewType = rootProjectOp.ColumnMap.Type; 123 if (!Command.EqualTypes(functionViewType, this.FunctionImport.ReturnParameter.TypeUsage)) 124 { 125 Debug.Assert(TypeSemantics.IsPromotableTo(functionViewType, this.FunctionImport.ReturnParameter.TypeUsage), "Mapping expression result type must be promotable to the c-space function return type."); 126 127 // Build "relNode = Project(relNode, SoftCast(relVar))" 128 CollectionType expectedCollectionType = (CollectionType)this.FunctionImport.ReturnParameter.TypeUsage.EdmType; 129 var expectedElementType = expectedCollectionType.TypeUsage; 130 131 Node varRefNode = itree.CreateNode(itree.CreateVarRefOp(relVar)); 132 Node castNode = itree.CreateNode(itree.CreateSoftCastOp(expectedElementType), varRefNode); 133 Node varDefListNode = itree.CreateVarDefListNode(castNode, out relVar); 134 135 ProjectOp projectOp = itree.CreateProjectOp(relVar); 136 relNode = itree.CreateNode(projectOp, relNode, varDefListNode); 137 } 138 139 // Build "Collect(PhysicalProject(relNode)) 140 m_internalTreeNode = itree.BuildCollect(relNode, relVar); 141 } 142 Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null"); 143 144 // Prepare argument replacement dictionary 145 Debug.Assert(m_commandParameters.Length == targetIqtArguments.Count, "m_commandParameters.Length == targetIqtArguments.Count"); 146 Dictionary<string, Node> viewArguments = new Dictionary<string, Node>(m_commandParameters.Length); 147 for (int i = 0; i < m_commandParameters.Length; ++i) 148 { 149 var commandParam = (DbParameterReferenceExpression)m_commandParameters[i]; 150 var argumentNode = targetIqtArguments[i]; 151 152 // If function import parameter is of enum type, the argument value for it will be of enum type. We however have 153 // converted enum types to underlying types for m_commandParameters. So we now need to softcast the argument 154 // expression to the underlying type as well. 155 if (TypeSemantics.IsEnumerationType(argumentNode.Op.Type)) 156 { 157 argumentNode = targetIqtCommand.CreateNode( 158 targetIqtCommand.CreateSoftCastOp(TypeHelpers.CreateEnumUnderlyingTypeUsage(argumentNode.Op.Type)), 159 argumentNode); 160 } 161 162 Debug.Assert(TypeSemantics.IsPromotableTo(argumentNode.Op.Type, commandParam.ResultType), "Argument type must be promotable to parameter type."); 163 164 viewArguments.Add(commandParam.ParameterName, argumentNode); 165 } 166 167 return FunctionViewOpCopier.Copy(targetIqtCommand, m_internalTreeNode, viewArguments); 168 } 169 170 private sealed class FunctionViewOpCopier : OpCopier 171 { 172 private Dictionary<string, Node> m_viewArguments; 173 FunctionViewOpCopier(Command cmd, Dictionary<string, Node> viewArguments)174 private FunctionViewOpCopier(Command cmd, Dictionary<string, Node> viewArguments) 175 : base(cmd) 176 { 177 m_viewArguments = viewArguments; 178 } 179 Copy(Command cmd, Node viewNode, Dictionary<string, Node> viewArguments)180 internal static Node Copy(Command cmd, Node viewNode, Dictionary<string, Node> viewArguments) 181 { 182 return new FunctionViewOpCopier(cmd, viewArguments).CopyNode(viewNode); 183 } 184 185 #region Visitor Members Visit(VarRefOp op, Node n)186 public override Node Visit(VarRefOp op, Node n) 187 { 188 // The original function view has store function calls with arguments represented as command parameter refs. 189 // We are now replacing command parameter refs with the real argument nodes from the calling tree. 190 // The replacement is performed in the function view subtree and we search for parameter refs with names 191 // matching the FunctionImportMapping.FunctionImport parameter names (this is how the command parameters 192 // have been created in the first place, see m_commandParameters and GetCommandTree(...) for more info). 193 // The search and replace is not performed on the argument nodes themselves. This is important because it guarantees 194 // that we are not replacing unrelated (possibly user-defined) parameter refs that accidentally have the matching names. 195 Node argNode; 196 if (op.Var.VarType == VarType.Parameter && m_viewArguments.TryGetValue(((ParameterVar)op.Var).ParameterName, out argNode)) 197 { 198 // Just copy the argNode, do not reapply this visitor. We do not want search and replace inside the argNode. See comment above. 199 return OpCopier.Copy(m_destCmd, argNode); 200 } 201 else 202 { 203 return base.Visit(op, n); 204 } 205 } 206 #endregion 207 } 208 #endregion 209 210 #region GenerateFunctionView(...) implementation 211 #region GenerateFunctionView GenerateFunctionView(IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap)212 internal DbQueryCommandTree GenerateFunctionView(IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap) 213 { 214 Debug.Assert(errors != null, "errors != null"); 215 216 discriminatorMap = null; 217 218 // Prepare the direct call of the store function as StoreFunction(@EdmFunc_p1, ..., @EdmFunc_pN). 219 // Note that function call arguments are command parameters created from the m_edmFunction parameters. 220 Debug.Assert(this.TargetFunction != null, "this.TargetFunction != null"); 221 DbExpression storeFunctionInvoke = this.TargetFunction.Invoke(GetParametersForTargetFunctionCall()); 222 223 // Generate the query expression producing c-space result from s-space function call(s). 224 DbExpression queryExpression; 225 if (m_structuralTypeMappings != null) 226 { 227 queryExpression = GenerateStructuralTypeResultMappingView(storeFunctionInvoke, errors, out discriminatorMap); 228 Debug.Assert(queryExpression == null || 229 TypeSemantics.IsPromotableTo(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage), 230 "TypeSemantics.IsPromotableTo(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)"); 231 } 232 else 233 { 234 queryExpression = GenerateScalarResultMappingView(storeFunctionInvoke); 235 Debug.Assert(queryExpression == null || 236 TypeSemantics.IsEqual(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage), 237 "TypeSemantics.IsEqual(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)"); 238 } 239 240 if (queryExpression == null) 241 { 242 // In case of errors during view generation, return. 243 return null; 244 } 245 246 // Generate parameterized command, where command parameters are semantically the c-space function parameters. 247 return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, queryExpression); 248 } 249 GetParametersForTargetFunctionCall()250 private IEnumerable<DbExpression> GetParametersForTargetFunctionCall() 251 { 252 Debug.Assert(this.FunctionImport.Parameters.Count == m_commandParameters.Length, "this.FunctionImport.Parameters.Count == m_commandParameters.Length"); 253 Debug.Assert(this.TargetFunction.Parameters.Count == m_commandParameters.Length, "this.TargetFunction.Parameters.Count == m_commandParameters.Length"); 254 foreach (var targetParameter in this.TargetFunction.Parameters) 255 { 256 Debug.Assert(this.FunctionImport.Parameters.Contains(targetParameter.Name), "this.FunctionImport.Parameters.Contains(targetParameter.Name)"); 257 var functionImportParameter = this.FunctionImport.Parameters.Single(p => p.Name == targetParameter.Name); 258 yield return m_commandParameters[this.FunctionImport.Parameters.IndexOf(functionImportParameter)]; 259 } 260 } 261 262 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // referenced by System.Data.Entity.Design.dll ValidateFunctionView(IList<EdmSchemaError> errors)263 internal void ValidateFunctionView(IList<EdmSchemaError> errors) 264 { 265 DiscriminatorMap dm; 266 GenerateFunctionView(errors, out dm); 267 } 268 #endregion 269 270 #region GenerateStructuralTypeResultMappingView GenerateStructuralTypeResultMappingView(DbExpression storeFunctionInvoke, IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap)271 private DbExpression GenerateStructuralTypeResultMappingView(DbExpression storeFunctionInvoke, IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap) 272 { 273 Debug.Assert(m_structuralTypeMappings != null && m_structuralTypeMappings.Count > 0, "m_structuralTypeMappings != null && m_structuralTypeMappings.Count > 0"); 274 275 discriminatorMap = null; 276 277 // Process explicit structural type mappings. The mapping is based on the direct call of the store function 278 // wrapped into a projection constructing the mapped structural types. 279 280 DbExpression queryExpression = storeFunctionInvoke; 281 282 if (m_structuralTypeMappings.Count == 1) 283 { 284 var mapping = m_structuralTypeMappings[0]; 285 286 var type = mapping.Item1; 287 var conditions = mapping.Item2; 288 var propertyMappings = mapping.Item3; 289 290 if (conditions.Count > 0) 291 { 292 queryExpression = queryExpression.Where((row) => GenerateStructuralTypeConditionsPredicate(conditions, row)); 293 } 294 295 var binding = queryExpression.BindAs("row"); 296 var entityTypeMappingView = GenerateStructuralTypeMappingView(type, propertyMappings, binding.Variable, errors); 297 if (entityTypeMappingView == null) 298 { 299 return null; 300 } 301 302 queryExpression = binding.Project(entityTypeMappingView); 303 } 304 else 305 { 306 var binding = queryExpression.BindAs("row"); 307 308 // Make sure type projection is performed over a closed set where each row is guaranteed to produce a known type. 309 // To do this, filter the store function output using the type conditions. 310 Debug.Assert(m_structuralTypeMappings.All(m => m.Item2.Count > 0), "In multi-type mapping each type must have conditions."); 311 List<DbExpression> structuralTypePredicates = m_structuralTypeMappings.Select(m => GenerateStructuralTypeConditionsPredicate(m.Item2, binding.Variable)).ToList(); 312 queryExpression = binding.Filter(Helpers.BuildBalancedTreeInPlace( 313 structuralTypePredicates.ToArray(), // clone, otherwise BuildBalancedTreeInPlace will change it 314 (prev, next) => prev.Or(next))); 315 binding = queryExpression.BindAs("row"); 316 317 List<DbExpression> structuralTypeMappingViews = new List<DbExpression>(m_structuralTypeMappings.Count); 318 foreach (var mapping in m_structuralTypeMappings) 319 { 320 var type = mapping.Item1; 321 var propertyMappings = mapping.Item3; 322 323 var structuralTypeMappingView = GenerateStructuralTypeMappingView(type, propertyMappings, binding.Variable, errors); 324 if (structuralTypeMappingView == null) 325 { 326 continue; 327 } 328 else 329 { 330 structuralTypeMappingViews.Add(structuralTypeMappingView); 331 } 332 } 333 Debug.Assert(structuralTypeMappingViews.Count == structuralTypePredicates.Count, "structuralTypeMappingViews.Count == structuralTypePredicates.Count"); 334 if (structuralTypeMappingViews.Count != m_structuralTypeMappings.Count) 335 { 336 Debug.Assert(errors.Count > 0, "errors.Count > 0"); 337 return null; 338 } 339 340 // Because we are projecting over the closed set, we can convert the last WHEN THEN into ELSE. 341 DbExpression typeConstructors = DbExpressionBuilder.Case( 342 structuralTypePredicates.Take(m_structuralTypeMappings.Count - 1), 343 structuralTypeMappingViews.Take(m_structuralTypeMappings.Count - 1), 344 structuralTypeMappingViews[m_structuralTypeMappings.Count - 1]); 345 346 queryExpression = binding.Project(typeConstructors); 347 348 if (DiscriminatorMap.TryCreateDiscriminatorMap(this.FunctionImport.EntitySet, queryExpression, out discriminatorMap)) 349 { 350 Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created"); 351 } 352 } 353 354 return queryExpression; 355 } 356 GenerateStructuralTypeMappingView(StructuralType structuralType, List<StoragePropertyMapping> propertyMappings, DbExpression row, IList<EdmSchemaError> errors)357 private DbExpression GenerateStructuralTypeMappingView(StructuralType structuralType, List<StoragePropertyMapping> propertyMappings, DbExpression row, IList<EdmSchemaError> errors) 358 { 359 // Generate property views. 360 var properties = TypeHelpers.GetAllStructuralMembers(structuralType); 361 Debug.Assert(properties.Count == propertyMappings.Count, "properties.Count == propertyMappings.Count"); 362 var constructorArgs = new List<DbExpression>(properties.Count); 363 for (int i = 0; i < propertyMappings.Count; ++i) 364 { 365 var propertyMapping = propertyMappings[i]; 366 Debug.Assert(properties[i].EdmEquals(propertyMapping.EdmProperty), "properties[i].EdmEquals(propertyMapping.EdmProperty)"); 367 var propertyMappingView = GeneratePropertyMappingView(propertyMapping, row, new List<string>() { propertyMapping.EdmProperty.Name }, errors); 368 if (propertyMappingView != null) 369 { 370 constructorArgs.Add(propertyMappingView); 371 } 372 } 373 if (constructorArgs.Count != propertyMappings.Count) 374 { 375 Debug.Assert(errors.Count > 0, "errors.Count > 0"); 376 return null; 377 } 378 else 379 { 380 // Return the structural type constructor. 381 return TypeUsage.Create(structuralType).New(constructorArgs); 382 } 383 } 384 GenerateStructuralTypeConditionsPredicate(List<StorageConditionPropertyMapping> conditions, DbExpression row)385 private DbExpression GenerateStructuralTypeConditionsPredicate(List<StorageConditionPropertyMapping> conditions, DbExpression row) 386 { 387 Debug.Assert(conditions.Count > 0, "conditions.Count > 0"); 388 DbExpression predicate = Helpers.BuildBalancedTreeInPlace(conditions.Select(c => GeneratePredicate(c, row)).ToArray(), (prev, next) => prev.And(next)); 389 return predicate; 390 } 391 GeneratePredicate(StorageConditionPropertyMapping condition, DbExpression row)392 private DbExpression GeneratePredicate(StorageConditionPropertyMapping condition, DbExpression row) 393 { 394 Debug.Assert(condition.EdmProperty == null, "C-side conditions are not supported in function mappings."); 395 DbExpression columnRef = GenerateColumnRef(row, condition.ColumnProperty); 396 397 if (condition.IsNull.HasValue) 398 { 399 return condition.IsNull.Value ? (DbExpression)columnRef.IsNull() : (DbExpression)columnRef.IsNull().Not(); 400 } 401 else 402 { 403 return columnRef.Equal(columnRef.ResultType.Constant(condition.Value)); 404 } 405 } 406 GeneratePropertyMappingView(StoragePropertyMapping mapping, DbExpression row, List<string> context, IList<EdmSchemaError> errors)407 private DbExpression GeneratePropertyMappingView(StoragePropertyMapping mapping, DbExpression row, List<string> context, IList<EdmSchemaError> errors) 408 { 409 Debug.Assert(mapping is StorageScalarPropertyMapping, "Complex property mapping is not supported in function imports."); 410 var scalarPropertyMapping = (StorageScalarPropertyMapping)mapping; 411 return GenerateScalarPropertyMappingView(scalarPropertyMapping.EdmProperty, scalarPropertyMapping.ColumnProperty, row); 412 } 413 GenerateScalarPropertyMappingView(EdmProperty edmProperty, EdmProperty columnProperty, DbExpression row)414 private DbExpression GenerateScalarPropertyMappingView(EdmProperty edmProperty, EdmProperty columnProperty, DbExpression row) 415 { 416 DbExpression accessorExpr = GenerateColumnRef(row, columnProperty); 417 if (!TypeSemantics.IsEqual(accessorExpr.ResultType, edmProperty.TypeUsage)) 418 { 419 accessorExpr = accessorExpr.CastTo(edmProperty.TypeUsage); 420 } 421 return accessorExpr; 422 } 423 GenerateColumnRef(DbExpression row, EdmProperty column)424 private DbExpression GenerateColumnRef(DbExpression row, EdmProperty column) 425 { 426 Debug.Assert(row.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.RowType, "Input type is expected to be a row type."); 427 var rowType = (RowType)row.ResultType.EdmType; 428 Debug.Assert(rowType.Properties.Contains(column.Name), "Column name must be resolvable in the TVF result type."); 429 return row.Property(column.Name); 430 } 431 #endregion 432 433 #region GenerateScalarResultMappingView GenerateScalarResultMappingView(DbExpression storeFunctionInvoke)434 private DbExpression GenerateScalarResultMappingView(DbExpression storeFunctionInvoke) 435 { 436 DbExpression queryExpression = storeFunctionInvoke; 437 438 CollectionType functionImportReturnType; 439 if (!MetadataHelper.TryGetFunctionImportReturnCollectionType(this.FunctionImport, 0, out functionImportReturnType)) 440 { 441 Debug.Fail("Failed to get the result type of the function import."); 442 } 443 444 Debug.Assert(TypeSemantics.IsCollectionType(queryExpression.ResultType), "Store function must be TVF (collection expected)."); 445 var collectionType = (CollectionType)queryExpression.ResultType.EdmType; 446 Debug.Assert(TypeSemantics.IsRowType(collectionType.TypeUsage), "Store function must be TVF (collection of rows expected)."); 447 var rowType = (RowType)collectionType.TypeUsage.EdmType; 448 var column = rowType.Properties[0]; 449 450 Func<DbExpression, DbExpression> scalarView = (DbExpression row) => 451 { 452 var propertyAccess = row.Property(column); 453 if (TypeSemantics.IsEqual(functionImportReturnType.TypeUsage, column.TypeUsage)) 454 { 455 return propertyAccess; 456 } 457 else 458 { 459 return propertyAccess.CastTo(functionImportReturnType.TypeUsage); 460 } 461 }; 462 463 queryExpression = queryExpression.Select(row => scalarView(row)); 464 return queryExpression; 465 } 466 #endregion 467 #endregion 468 #endregion 469 } 470 } 471