471 lines
25 KiB
C#
471 lines
25 KiB
C#
|
//---------------------------------------------------------------------
|
||
|
// <copyright file="FunctionImportMappingComposable.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//
|
||
|
// @owner [....]
|
||
|
// @backupOwner willa
|
||
|
//---------------------------------------------------------------------
|
||
|
|
||
|
using System.Collections.Generic;
|
||
|
using System.Data.Common;
|
||
|
using System.Data.Common.CommandTrees;
|
||
|
using System.Data.Common.CommandTrees.ExpressionBuilder;
|
||
|
using System.Data.Common.Utils;
|
||
|
using System.Data.Mapping.ViewGeneration;
|
||
|
using System.Data.Metadata.Edm;
|
||
|
using System.Data.Query.InternalTrees;
|
||
|
using System.Data.Query.PlanCompiler;
|
||
|
using System.Diagnostics;
|
||
|
using System.Linq;
|
||
|
|
||
|
namespace System.Data.Mapping
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Represents a mapping from a model function import to a store composable function.
|
||
|
/// </summary>
|
||
|
internal class FunctionImportMappingComposable : FunctionImportMapping
|
||
|
{
|
||
|
#region Constructors
|
||
|
internal FunctionImportMappingComposable(
|
||
|
EdmFunction functionImport,
|
||
|
EdmFunction targetFunction,
|
||
|
List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> structuralTypeMappings,
|
||
|
EdmProperty[] targetFunctionKeys,
|
||
|
StorageMappingItemCollection mappingItemCollection,
|
||
|
string sourceLocation,
|
||
|
LineInfo lineInfo)
|
||
|
: base(functionImport, targetFunction)
|
||
|
{
|
||
|
EntityUtil.CheckArgumentNull(mappingItemCollection, "mappingItemCollection");
|
||
|
Debug.Assert(functionImport.IsComposableAttribute, "functionImport.IsComposableAttribute");
|
||
|
Debug.Assert(targetFunction.IsComposableAttribute, "targetFunction.IsComposableAttribute");
|
||
|
Debug.Assert(functionImport.EntitySet == null || structuralTypeMappings != null, "Function import returning entities must have structuralTypeMappings.");
|
||
|
Debug.Assert(structuralTypeMappings == null || structuralTypeMappings.Count > 0, "Non-null structuralTypeMappings must not be empty.");
|
||
|
EdmType resultType;
|
||
|
Debug.Assert(
|
||
|
structuralTypeMappings != null ||
|
||
|
MetadataHelper.TryGetFunctionImportReturnType<EdmType>(functionImport, 0, out resultType) && TypeSemantics.IsScalarType(resultType),
|
||
|
"Either type mappings should be specified or the function import should be Collection(Scalar).");
|
||
|
Debug.Assert(functionImport.EntitySet == null || targetFunctionKeys != null, "Keys must be inferred for a function import returning entities.");
|
||
|
Debug.Assert(targetFunctionKeys == null || targetFunctionKeys.Length > 0, "Keys must be null or non-empty.");
|
||
|
|
||
|
m_mappingItemCollection = mappingItemCollection;
|
||
|
// We will use these parameters to target s-space function calls in the generated command tree.
|
||
|
// Since enums don't exist in s-space we need to use the underlying type.
|
||
|
m_commandParameters = functionImport.Parameters.Select(p => TypeHelpers.GetPrimitiveTypeUsageForScalar(p.TypeUsage).Parameter(p.Name)).ToArray();
|
||
|
m_structuralTypeMappings = structuralTypeMappings;
|
||
|
m_targetFunctionKeys = targetFunctionKeys;
|
||
|
m_sourceLocation = sourceLocation;
|
||
|
m_lineInfo = lineInfo;
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region Fields
|
||
|
private readonly StorageMappingItemCollection m_mappingItemCollection;
|
||
|
/// <summary>
|
||
|
/// Command parameter refs created from m_edmFunction parameters.
|
||
|
/// Used as arguments to target (s-space) function calls in the generated command tree.
|
||
|
/// </summary>
|
||
|
private readonly DbParameterReferenceExpression[] m_commandParameters;
|
||
|
/// <summary>
|
||
|
/// Result mapping as entity type hierarchy.
|
||
|
/// </summary>
|
||
|
private readonly List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> m_structuralTypeMappings;
|
||
|
/// <summary>
|
||
|
/// Keys inside the result set of the target function. Inferred based on the mapping (using c-space entity type keys).
|
||
|
/// </summary>
|
||
|
private readonly EdmProperty[] m_targetFunctionKeys;
|
||
|
/// <summary>
|
||
|
/// ITree template. Requires function argument substitution during function view expansion.
|
||
|
/// </summary>
|
||
|
private Node m_internalTreeNode;
|
||
|
private readonly string m_sourceLocation;
|
||
|
private readonly LineInfo m_lineInfo;
|
||
|
#endregion
|
||
|
|
||
|
#region Properties/Methods
|
||
|
internal EdmProperty[] TvfKeys
|
||
|
{
|
||
|
get { return m_targetFunctionKeys; }
|
||
|
}
|
||
|
|
||
|
#region GetInternalTree(...) implementation
|
||
|
internal Node GetInternalTree(Command targetIqtCommand, IList<Node> targetIqtArguments)
|
||
|
{
|
||
|
if (m_internalTreeNode == null)
|
||
|
{
|
||
|
var viewGenErrors = new List<EdmSchemaError>();
|
||
|
DiscriminatorMap discriminatorMap;
|
||
|
DbQueryCommandTree tree = GenerateFunctionView(viewGenErrors, out discriminatorMap);
|
||
|
if (viewGenErrors.Count > 0)
|
||
|
{
|
||
|
throw new MappingException(Helper.CombineErrorMessage(viewGenErrors));
|
||
|
}
|
||
|
Debug.Assert(tree != null, "tree != null");
|
||
|
|
||
|
// Convert this into an ITree first
|
||
|
Command itree = ITreeGenerator.Generate(tree, discriminatorMap);
|
||
|
var rootProject = itree.Root; // PhysicalProject(RelInput)
|
||
|
PlanCompiler.Assert(rootProject.Op.OpType == OpType.PhysicalProject, "Expected a physical projectOp at the root of the tree - found " + rootProject.Op.OpType);
|
||
|
var rootProjectOp = (PhysicalProjectOp)rootProject.Op;
|
||
|
Debug.Assert(rootProjectOp.Outputs.Count == 1, "rootProjectOp.Outputs.Count == 1");
|
||
|
var rootInput = rootProject.Child0; // the RelInput in PhysicalProject(RelInput)
|
||
|
|
||
|
// #554756: VarVec enumerators are not cached on the shared Command instance.
|
||
|
itree.DisableVarVecEnumCaching();
|
||
|
|
||
|
// Function import returns a collection, so convert it to a scalar by wrapping into CollectOp.
|
||
|
Node relNode = rootInput;
|
||
|
Var relVar = rootProjectOp.Outputs[0];
|
||
|
// ProjectOp does not implement Type property, so get the type from the column map.
|
||
|
TypeUsage functionViewType = rootProjectOp.ColumnMap.Type;
|
||
|
if (!Command.EqualTypes(functionViewType, this.FunctionImport.ReturnParameter.TypeUsage))
|
||
|
{
|
||
|
Debug.Assert(TypeSemantics.IsPromotableTo(functionViewType, this.FunctionImport.ReturnParameter.TypeUsage), "Mapping expression result type must be promotable to the c-space function return type.");
|
||
|
|
||
|
// Build "relNode = Project(relNode, SoftCast(relVar))"
|
||
|
CollectionType expectedCollectionType = (CollectionType)this.FunctionImport.ReturnParameter.TypeUsage.EdmType;
|
||
|
var expectedElementType = expectedCollectionType.TypeUsage;
|
||
|
|
||
|
Node varRefNode = itree.CreateNode(itree.CreateVarRefOp(relVar));
|
||
|
Node castNode = itree.CreateNode(itree.CreateSoftCastOp(expectedElementType), varRefNode);
|
||
|
Node varDefListNode = itree.CreateVarDefListNode(castNode, out relVar);
|
||
|
|
||
|
ProjectOp projectOp = itree.CreateProjectOp(relVar);
|
||
|
relNode = itree.CreateNode(projectOp, relNode, varDefListNode);
|
||
|
}
|
||
|
|
||
|
// Build "Collect(PhysicalProject(relNode))
|
||
|
m_internalTreeNode = itree.BuildCollect(relNode, relVar);
|
||
|
}
|
||
|
Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null");
|
||
|
|
||
|
// Prepare argument replacement dictionary
|
||
|
Debug.Assert(m_commandParameters.Length == targetIqtArguments.Count, "m_commandParameters.Length == targetIqtArguments.Count");
|
||
|
Dictionary<string, Node> viewArguments = new Dictionary<string, Node>(m_commandParameters.Length);
|
||
|
for (int i = 0; i < m_commandParameters.Length; ++i)
|
||
|
{
|
||
|
var commandParam = (DbParameterReferenceExpression)m_commandParameters[i];
|
||
|
var argumentNode = targetIqtArguments[i];
|
||
|
|
||
|
// If function import parameter is of enum type, the argument value for it will be of enum type. We however have
|
||
|
// converted enum types to underlying types for m_commandParameters. So we now need to softcast the argument
|
||
|
// expression to the underlying type as well.
|
||
|
if (TypeSemantics.IsEnumerationType(argumentNode.Op.Type))
|
||
|
{
|
||
|
argumentNode = targetIqtCommand.CreateNode(
|
||
|
targetIqtCommand.CreateSoftCastOp(TypeHelpers.CreateEnumUnderlyingTypeUsage(argumentNode.Op.Type)),
|
||
|
argumentNode);
|
||
|
}
|
||
|
|
||
|
Debug.Assert(TypeSemantics.IsPromotableTo(argumentNode.Op.Type, commandParam.ResultType), "Argument type must be promotable to parameter type.");
|
||
|
|
||
|
viewArguments.Add(commandParam.ParameterName, argumentNode);
|
||
|
}
|
||
|
|
||
|
return FunctionViewOpCopier.Copy(targetIqtCommand, m_internalTreeNode, viewArguments);
|
||
|
}
|
||
|
|
||
|
private sealed class FunctionViewOpCopier : OpCopier
|
||
|
{
|
||
|
private Dictionary<string, Node> m_viewArguments;
|
||
|
|
||
|
private FunctionViewOpCopier(Command cmd, Dictionary<string, Node> viewArguments)
|
||
|
: base(cmd)
|
||
|
{
|
||
|
m_viewArguments = viewArguments;
|
||
|
}
|
||
|
|
||
|
internal static Node Copy(Command cmd, Node viewNode, Dictionary<string, Node> viewArguments)
|
||
|
{
|
||
|
return new FunctionViewOpCopier(cmd, viewArguments).CopyNode(viewNode);
|
||
|
}
|
||
|
|
||
|
#region Visitor Members
|
||
|
public override Node Visit(VarRefOp op, Node n)
|
||
|
{
|
||
|
// The original function view has store function calls with arguments represented as command parameter refs.
|
||
|
// We are now replacing command parameter refs with the real argument nodes from the calling tree.
|
||
|
// The replacement is performed in the function view subtree and we search for parameter refs with names
|
||
|
// matching the FunctionImportMapping.FunctionImport parameter names (this is how the command parameters
|
||
|
// have been created in the first place, see m_commandParameters and GetCommandTree(...) for more info).
|
||
|
// The search and replace is not performed on the argument nodes themselves. This is important because it guarantees
|
||
|
// that we are not replacing unrelated (possibly user-defined) parameter refs that accidentally have the matching names.
|
||
|
Node argNode;
|
||
|
if (op.Var.VarType == VarType.Parameter && m_viewArguments.TryGetValue(((ParameterVar)op.Var).ParameterName, out argNode))
|
||
|
{
|
||
|
// Just copy the argNode, do not reapply this visitor. We do not want search and replace inside the argNode. See comment above.
|
||
|
return OpCopier.Copy(m_destCmd, argNode);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return base.Visit(op, n);
|
||
|
}
|
||
|
}
|
||
|
#endregion
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region GenerateFunctionView(...) implementation
|
||
|
#region GenerateFunctionView
|
||
|
internal DbQueryCommandTree GenerateFunctionView(IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap)
|
||
|
{
|
||
|
Debug.Assert(errors != null, "errors != null");
|
||
|
|
||
|
discriminatorMap = null;
|
||
|
|
||
|
// Prepare the direct call of the store function as StoreFunction(@EdmFunc_p1, ..., @EdmFunc_pN).
|
||
|
// Note that function call arguments are command parameters created from the m_edmFunction parameters.
|
||
|
Debug.Assert(this.TargetFunction != null, "this.TargetFunction != null");
|
||
|
DbExpression storeFunctionInvoke = this.TargetFunction.Invoke(GetParametersForTargetFunctionCall());
|
||
|
|
||
|
// Generate the query expression producing c-space result from s-space function call(s).
|
||
|
DbExpression queryExpression;
|
||
|
if (m_structuralTypeMappings != null)
|
||
|
{
|
||
|
queryExpression = GenerateStructuralTypeResultMappingView(storeFunctionInvoke, errors, out discriminatorMap);
|
||
|
Debug.Assert(queryExpression == null ||
|
||
|
TypeSemantics.IsPromotableTo(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage),
|
||
|
"TypeSemantics.IsPromotableTo(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
queryExpression = GenerateScalarResultMappingView(storeFunctionInvoke);
|
||
|
Debug.Assert(queryExpression == null ||
|
||
|
TypeSemantics.IsEqual(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage),
|
||
|
"TypeSemantics.IsEqual(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)");
|
||
|
}
|
||
|
|
||
|
if (queryExpression == null)
|
||
|
{
|
||
|
// In case of errors during view generation, return.
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Generate parameterized command, where command parameters are semantically the c-space function parameters.
|
||
|
return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, queryExpression);
|
||
|
}
|
||
|
|
||
|
private IEnumerable<DbExpression> GetParametersForTargetFunctionCall()
|
||
|
{
|
||
|
Debug.Assert(this.FunctionImport.Parameters.Count == m_commandParameters.Length, "this.FunctionImport.Parameters.Count == m_commandParameters.Length");
|
||
|
Debug.Assert(this.TargetFunction.Parameters.Count == m_commandParameters.Length, "this.TargetFunction.Parameters.Count == m_commandParameters.Length");
|
||
|
foreach (var targetParameter in this.TargetFunction.Parameters)
|
||
|
{
|
||
|
Debug.Assert(this.FunctionImport.Parameters.Contains(targetParameter.Name), "this.FunctionImport.Parameters.Contains(targetParameter.Name)");
|
||
|
var functionImportParameter = this.FunctionImport.Parameters.Single(p => p.Name == targetParameter.Name);
|
||
|
yield return m_commandParameters[this.FunctionImport.Parameters.IndexOf(functionImportParameter)];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // referenced by System.Data.Entity.Design.dll
|
||
|
internal void ValidateFunctionView(IList<EdmSchemaError> errors)
|
||
|
{
|
||
|
DiscriminatorMap dm;
|
||
|
GenerateFunctionView(errors, out dm);
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region GenerateStructuralTypeResultMappingView
|
||
|
private DbExpression GenerateStructuralTypeResultMappingView(DbExpression storeFunctionInvoke, IList<EdmSchemaError> errors, out DiscriminatorMap discriminatorMap)
|
||
|
{
|
||
|
Debug.Assert(m_structuralTypeMappings != null && m_structuralTypeMappings.Count > 0, "m_structuralTypeMappings != null && m_structuralTypeMappings.Count > 0");
|
||
|
|
||
|
discriminatorMap = null;
|
||
|
|
||
|
// Process explicit structural type mappings. The mapping is based on the direct call of the store function
|
||
|
// wrapped into a projection constructing the mapped structural types.
|
||
|
|
||
|
DbExpression queryExpression = storeFunctionInvoke;
|
||
|
|
||
|
if (m_structuralTypeMappings.Count == 1)
|
||
|
{
|
||
|
var mapping = m_structuralTypeMappings[0];
|
||
|
|
||
|
var type = mapping.Item1;
|
||
|
var conditions = mapping.Item2;
|
||
|
var propertyMappings = mapping.Item3;
|
||
|
|
||
|
if (conditions.Count > 0)
|
||
|
{
|
||
|
queryExpression = queryExpression.Where((row) => GenerateStructuralTypeConditionsPredicate(conditions, row));
|
||
|
}
|
||
|
|
||
|
var binding = queryExpression.BindAs("row");
|
||
|
var entityTypeMappingView = GenerateStructuralTypeMappingView(type, propertyMappings, binding.Variable, errors);
|
||
|
if (entityTypeMappingView == null)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
queryExpression = binding.Project(entityTypeMappingView);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var binding = queryExpression.BindAs("row");
|
||
|
|
||
|
// Make sure type projection is performed over a closed set where each row is guaranteed to produce a known type.
|
||
|
// To do this, filter the store function output using the type conditions.
|
||
|
Debug.Assert(m_structuralTypeMappings.All(m => m.Item2.Count > 0), "In multi-type mapping each type must have conditions.");
|
||
|
List<DbExpression> structuralTypePredicates = m_structuralTypeMappings.Select(m => GenerateStructuralTypeConditionsPredicate(m.Item2, binding.Variable)).ToList();
|
||
|
queryExpression = binding.Filter(Helpers.BuildBalancedTreeInPlace(
|
||
|
structuralTypePredicates.ToArray(), // clone, otherwise BuildBalancedTreeInPlace will change it
|
||
|
(prev, next) => prev.Or(next)));
|
||
|
binding = queryExpression.BindAs("row");
|
||
|
|
||
|
List<DbExpression> structuralTypeMappingViews = new List<DbExpression>(m_structuralTypeMappings.Count);
|
||
|
foreach (var mapping in m_structuralTypeMappings)
|
||
|
{
|
||
|
var type = mapping.Item1;
|
||
|
var propertyMappings = mapping.Item3;
|
||
|
|
||
|
var structuralTypeMappingView = GenerateStructuralTypeMappingView(type, propertyMappings, binding.Variable, errors);
|
||
|
if (structuralTypeMappingView == null)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
structuralTypeMappingViews.Add(structuralTypeMappingView);
|
||
|
}
|
||
|
}
|
||
|
Debug.Assert(structuralTypeMappingViews.Count == structuralTypePredicates.Count, "structuralTypeMappingViews.Count == structuralTypePredicates.Count");
|
||
|
if (structuralTypeMappingViews.Count != m_structuralTypeMappings.Count)
|
||
|
{
|
||
|
Debug.Assert(errors.Count > 0, "errors.Count > 0");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Because we are projecting over the closed set, we can convert the last WHEN THEN into ELSE.
|
||
|
DbExpression typeConstructors = DbExpressionBuilder.Case(
|
||
|
structuralTypePredicates.Take(m_structuralTypeMappings.Count - 1),
|
||
|
structuralTypeMappingViews.Take(m_structuralTypeMappings.Count - 1),
|
||
|
structuralTypeMappingViews[m_structuralTypeMappings.Count - 1]);
|
||
|
|
||
|
queryExpression = binding.Project(typeConstructors);
|
||
|
|
||
|
if (DiscriminatorMap.TryCreateDiscriminatorMap(this.FunctionImport.EntitySet, queryExpression, out discriminatorMap))
|
||
|
{
|
||
|
Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return queryExpression;
|
||
|
}
|
||
|
|
||
|
private DbExpression GenerateStructuralTypeMappingView(StructuralType structuralType, List<StoragePropertyMapping> propertyMappings, DbExpression row, IList<EdmSchemaError> errors)
|
||
|
{
|
||
|
// Generate property views.
|
||
|
var properties = TypeHelpers.GetAllStructuralMembers(structuralType);
|
||
|
Debug.Assert(properties.Count == propertyMappings.Count, "properties.Count == propertyMappings.Count");
|
||
|
var constructorArgs = new List<DbExpression>(properties.Count);
|
||
|
for (int i = 0; i < propertyMappings.Count; ++i)
|
||
|
{
|
||
|
var propertyMapping = propertyMappings[i];
|
||
|
Debug.Assert(properties[i].EdmEquals(propertyMapping.EdmProperty), "properties[i].EdmEquals(propertyMapping.EdmProperty)");
|
||
|
var propertyMappingView = GeneratePropertyMappingView(propertyMapping, row, new List<string>() { propertyMapping.EdmProperty.Name }, errors);
|
||
|
if (propertyMappingView != null)
|
||
|
{
|
||
|
constructorArgs.Add(propertyMappingView);
|
||
|
}
|
||
|
}
|
||
|
if (constructorArgs.Count != propertyMappings.Count)
|
||
|
{
|
||
|
Debug.Assert(errors.Count > 0, "errors.Count > 0");
|
||
|
return null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Return the structural type constructor.
|
||
|
return TypeUsage.Create(structuralType).New(constructorArgs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private DbExpression GenerateStructuralTypeConditionsPredicate(List<StorageConditionPropertyMapping> conditions, DbExpression row)
|
||
|
{
|
||
|
Debug.Assert(conditions.Count > 0, "conditions.Count > 0");
|
||
|
DbExpression predicate = Helpers.BuildBalancedTreeInPlace(conditions.Select(c => GeneratePredicate(c, row)).ToArray(), (prev, next) => prev.And(next));
|
||
|
return predicate;
|
||
|
}
|
||
|
|
||
|
private DbExpression GeneratePredicate(StorageConditionPropertyMapping condition, DbExpression row)
|
||
|
{
|
||
|
Debug.Assert(condition.EdmProperty == null, "C-side conditions are not supported in function mappings.");
|
||
|
DbExpression columnRef = GenerateColumnRef(row, condition.ColumnProperty);
|
||
|
|
||
|
if (condition.IsNull.HasValue)
|
||
|
{
|
||
|
return condition.IsNull.Value ? (DbExpression)columnRef.IsNull() : (DbExpression)columnRef.IsNull().Not();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return columnRef.Equal(columnRef.ResultType.Constant(condition.Value));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private DbExpression GeneratePropertyMappingView(StoragePropertyMapping mapping, DbExpression row, List<string> context, IList<EdmSchemaError> errors)
|
||
|
{
|
||
|
Debug.Assert(mapping is StorageScalarPropertyMapping, "Complex property mapping is not supported in function imports.");
|
||
|
var scalarPropertyMapping = (StorageScalarPropertyMapping)mapping;
|
||
|
return GenerateScalarPropertyMappingView(scalarPropertyMapping.EdmProperty, scalarPropertyMapping.ColumnProperty, row);
|
||
|
}
|
||
|
|
||
|
private DbExpression GenerateScalarPropertyMappingView(EdmProperty edmProperty, EdmProperty columnProperty, DbExpression row)
|
||
|
{
|
||
|
DbExpression accessorExpr = GenerateColumnRef(row, columnProperty);
|
||
|
if (!TypeSemantics.IsEqual(accessorExpr.ResultType, edmProperty.TypeUsage))
|
||
|
{
|
||
|
accessorExpr = accessorExpr.CastTo(edmProperty.TypeUsage);
|
||
|
}
|
||
|
return accessorExpr;
|
||
|
}
|
||
|
|
||
|
private DbExpression GenerateColumnRef(DbExpression row, EdmProperty column)
|
||
|
{
|
||
|
Debug.Assert(row.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.RowType, "Input type is expected to be a row type.");
|
||
|
var rowType = (RowType)row.ResultType.EdmType;
|
||
|
Debug.Assert(rowType.Properties.Contains(column.Name), "Column name must be resolvable in the TVF result type.");
|
||
|
return row.Property(column.Name);
|
||
|
}
|
||
|
#endregion
|
||
|
|
||
|
#region GenerateScalarResultMappingView
|
||
|
private DbExpression GenerateScalarResultMappingView(DbExpression storeFunctionInvoke)
|
||
|
{
|
||
|
DbExpression queryExpression = storeFunctionInvoke;
|
||
|
|
||
|
CollectionType functionImportReturnType;
|
||
|
if (!MetadataHelper.TryGetFunctionImportReturnCollectionType(this.FunctionImport, 0, out functionImportReturnType))
|
||
|
{
|
||
|
Debug.Fail("Failed to get the result type of the function import.");
|
||
|
}
|
||
|
|
||
|
Debug.Assert(TypeSemantics.IsCollectionType(queryExpression.ResultType), "Store function must be TVF (collection expected).");
|
||
|
var collectionType = (CollectionType)queryExpression.ResultType.EdmType;
|
||
|
Debug.Assert(TypeSemantics.IsRowType(collectionType.TypeUsage), "Store function must be TVF (collection of rows expected).");
|
||
|
var rowType = (RowType)collectionType.TypeUsage.EdmType;
|
||
|
var column = rowType.Properties[0];
|
||
|
|
||
|
Func<DbExpression, DbExpression> scalarView = (DbExpression row) =>
|
||
|
{
|
||
|
var propertyAccess = row.Property(column);
|
||
|
if (TypeSemantics.IsEqual(functionImportReturnType.TypeUsage, column.TypeUsage))
|
||
|
{
|
||
|
return propertyAccess;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return propertyAccess.CastTo(functionImportReturnType.TypeUsage);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
queryExpression = queryExpression.Select(row => scalarView(row));
|
||
|
return queryExpression;
|
||
|
}
|
||
|
#endregion
|
||
|
#endregion
|
||
|
#endregion
|
||
|
}
|
||
|
}
|