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