/// <summary> /// Creates a new <see cref="DbNewInstanceExpression"/> that constructs a new row based on the columns /// contained in this Row instance. /// </summary> /// <returns>A new DbNewInstanceExpression that constructs a row with the same column names and DbExpression values as this Row instance</returns> /// <seealso cref="DbExpressionBuilder.NewRow"/> public DbNewInstanceExpression ToExpression() { return(DbExpressionBuilder.NewRow(this.arguments)); }
public void CreateDbCommandDefinition_returns_wrapped_legacy_command_definition() { var commandDefinition = new LegacyDbProviderServicesWrapper(new Mock <SystemDataCommon.DbProviderServices>().Object) .CreateCommandDefinition( new LegacyDbProviderManifestWrapper(LegacyProviderManifest), new DbQueryCommandTree(CreateMetadataWorkspace(), DataSpace.SSpace, DbExpressionBuilder.Constant(42), false)); Assert.NotNull(commandDefinition); Assert.IsType <LegacyDbCommandDefinitionWrapper>(commandDefinition); }
public override DbExpression Visit(DbParameterReferenceExpression expression) { // Inline parameters return(DbExpressionBuilder.Constant(_parameters[expression.ParameterName].Value)); }
private Expression MapContainsExpression(MethodCallExpression node) { var expression = base.VisitMethodCall(node) as MethodCallExpression; // For some reason, if the list is IEnumerable and not the List class, the // list object (the ParameterExpression object) will be in Argument[0] and the param // of the Contains() function will be in Argument[1]. And expression.object is null. // In all other cases, the list object is in expression.Object and the Contains() param is Arguments[0]! DbExpression argExpression = null; ParameterExpression collectionObjExp = null; if ((expression.Arguments.Count > 1) && (expression.Object == null)) { collectionObjExp = expression.Arguments[0] as ParameterExpression; } if (collectionObjExp != null) { argExpression = GetDbExpressionForExpression(expression.Arguments[1]); // IEnumerable } else { argExpression = GetDbExpressionForExpression(expression.Arguments[0]); // List, IList, ICollection collectionObjExp = expression.Object as ParameterExpression; } DbExpression dbExpression; if (collectionObjExp != null) { // collectionObjExp is a parameter expression. This means the content of the collection is // dynamic. DbInExpression only supports a fixed size list of constant values. // So the only way to handle a dynamic collection is for us to create a single Equals expression // with a DbParameterReference. Then when we intercept that parameter, we will see that it's // for a collection and we will modify the SQL to change it from an "=" to an "in". The single // Parameter Reference is set to the first value in the collection and the rest of the values // are inserted into the SQL "in" clause. string paramName = collectionObjExp.Name; Type paramType = PrimitiveTypeForType(collectionObjExp.Type, _DataSpace); var param = CreateParameter(paramName, paramType); dbExpression = DbExpressionBuilder.Equal(argExpression, param); } else { var listExpression = expression.Object as ListInitExpression; if (listExpression == null) { throw new NotSupportedException(string.Format("Unsupported object type used in Contains() - type = {0}", expression.Object?.GetType().Name ?? "null")); } // This is a fixed size list that may contain parameter references or constant values. // This can be handled using either a DbInExpression (if all are constants) or with // a series of OR conditions. // Find all of the constant & parameter expressions. var constantExpressionList = listExpression.Initializers .Select(i => i.Arguments.FirstOrDefault() as ConstantExpression) .Where(c => (c != null) && (c.Value != null)) // null not supported - can only use DbConstant in "In" expression .Select(c => CreateConstantExpression(c.Value)) .ToList(); constantExpressionList.AddRange(listExpression.Initializers .Select(i => i.Arguments.FirstOrDefault() as UnaryExpression) .Where(c => (c != null) && (c.Operand is ConstantExpression)) .Select(c => CreateConstantExpression(((ConstantExpression)c.Operand).Value))); var parameterExpressionList = listExpression.Initializers .Select(i => i.Arguments.FirstOrDefault() as ParameterExpression) .Where(c => c != null) .Select(c => CreateParameter(c.Name, c.Type)) .ToList(); if (constantExpressionList.Count + parameterExpressionList.Count != listExpression.Initializers.Count) { throw new NotSupportedException(string.Format("Unrecognized parameters in Contains list. Null parameters not supported.")); } if (parameterExpressionList.Any() || !SupportsIn()) { // Have parameters or the EF provider does not support the DbInExpression. Need to build a series of OR conditions so // that we can include the DbParameterReferences. EF will optimize this into an "in" condition but with our // DbParameterReferences preserved (which is not possible with a DbInExpression). // The DbParameterReferences will be intercepted as any other parameter. dbExpression = null; var allExpressions = parameterExpressionList.Cast <DbExpression>().Union(constantExpressionList.Cast <DbExpression>()); foreach (var paramReference in allExpressions) { var equalsExpression = DbExpressionBuilder.Equal(argExpression, paramReference); if (dbExpression == null) { dbExpression = equalsExpression; } else { dbExpression = dbExpression.Or(equalsExpression); } } } else { // All values are constants so can use DbInExpression dbExpression = DbExpressionBuilder.In(argExpression, constantExpressionList); } } MapExpressionToDbExpression(expression, dbExpression); return(expression); }
private Expression MapAnyOrAllExpression(MethodCallExpression node) { if (_DataSpace != DataSpace.CSpace) { throw new ApplicationException("Filters on child collections are only supported when using CSpace"); } if ((node.Arguments == null) || (node.Arguments.Count > 2)) { throw new ApplicationException("Any function call has more than 2 arguments"); } // Visit the first argument so that we can get the DbPropertyExpression which is the source of the method call. var sourceExpression = Visit(node.Arguments[0]); var collectionExpression = GetDbExpressionForExpression(sourceExpression); // Visit this DbExpression using the QueryVisitor in case it has it's own filters that need to be applied. var queryVisitor = new DynamicFilterQueryVisitorCSpace(_DbContext); collectionExpression = collectionExpression.Accept(queryVisitor); DbExpression dbExpression; if (node.Arguments.Count == 2) { // The method call has a predicate that needs to be evaluated. This must be done against the source // argument - not the current binding. var binding = collectionExpression.Bind(); // Visit the lambda expression against this binding (which will evaluate all of the // conditions in the expression against this binding and for this filter). var lambdaExpression = node.Arguments[1] as LambdaExpression; var visitor = new LambdaToDbExpressionVisitor(_Filter, binding, _DbContext, _DataSpace); var subExpression = visitor.Visit(lambdaExpression) as LambdaExpression; var subDbExpression = visitor.GetDbExpressionForExpression(subExpression.Body); // Create an "Any" or "All" DbExpression from the results if (node.Method.Name == "All") { dbExpression = DbExpressionBuilder.All(binding, subDbExpression); } else { dbExpression = DbExpressionBuilder.Any(binding, subDbExpression); } } else { // This should not even be possible - linq/IEnumerable does not have such a method! if (node.Method.Name == "All") { throw new ApplicationException("All() with no parameters is not supported"); } // No predicate so just create an Any DbExpression against the collection expression dbExpression = DbExpressionBuilder.Any(collectionExpression); } MapExpressionToDbExpression(node, dbExpression); return(node); }
private DbExpression AddFkRelatedEntityRefs(DbExpression viewConstructor) { // If the extent being simplified is not a C-Space entity set, or if it has already // been processed by the simplifier, then keep the original expression by returning // null. // if (doNotProcess) { return(null); } if (extent.BuiltInTypeKind != BuiltInTypeKind.EntitySet || extent.EntityContainer.DataSpace != DataSpace.CSpace) { doNotProcess = true; return(null); } // Get a reference to the entity set being simplified, and find all the foreign key // (foreign key) associations for which the association set references that entity set, // with either association end. // var targetSet = (EntitySet)extent; var relSets = targetSet.EntityContainer.BaseEntitySets .Where(es => es.BuiltInTypeKind == BuiltInTypeKind.AssociationSet) .Cast <AssociationSet>() .Where( assocSet => assocSet.ElementType.IsForeignKey && assocSet.AssociationSetEnds.Any(se => se.EntitySet == targetSet) ) .ToList(); // If no foreign key association sets that reference the entity set are present, then // no further processing is necessary, because FK-based related entity references cannot // be computed and added to the entities constructed for the entity set. if (relSets.Count == 0) { doNotProcess = true; return(null); } // For every relationship set that references this entity set, the relationship type and // foreign key constraint are used to determine if the entity set is the dependent set. // If it is the dependent set, then it is possible to augment the view definition with a // related entity ref that represents the navigation of the relationship set's relationship // from the dependent end (this entity set) to the the principal end (the entity set that // is referenced by the other association set end of the relationship set). // var principalSetsAndDependentTypes = new HashSet <Tuple <EntityType, AssociationSetEnd, ReferentialConstraint> >(); foreach (var relSet in relSets) { // Retrieve the single referential constraint from the foreign key association, and // use it to determine whether the association set end that represents the dependent // end of the association references this entity set. // var fkConstraint = relSet.ElementType.ReferentialConstraints[0]; var dependentSetEnd = relSet.AssociationSetEnds[fkConstraint.ToRole.Name]; if (dependentSetEnd.EntitySet == targetSet) { var requiredSourceNavType = (EntityType)TypeHelpers.GetEdmType <RefType>(dependentSetEnd.CorrespondingAssociationEndMember.TypeUsage).ElementType; var principalSetEnd = relSet.AssociationSetEnds[fkConstraint.FromRole.Name]; // Record the entity type that an element of this dependent entity set must have in order // to be a valid navigation source for the relationship set's relationship, along with the // association set end for the destination (principal) end of the navigation and the FK // constraint that is associated with the relationship type. This information may be used // later to construct a related entity ref for any entity constructor expression in the view // that produces an entity of the required source type or a subtype. // principalSetsAndDependentTypes.Add(Tuple.Create(requiredSourceNavType, principalSetEnd, fkConstraint)); } } // If no foreign key association sets that use the entity set as the dependent set are present, // then no further processing is possible, since FK-based related entity refs can only be added // to the view definition for navigations from the dependent end of the relationship to the principal. // if (principalSetsAndDependentTypes.Count == 0) { doNotProcess = true; return(null); } // This rule supports a view that is capped with a projection of the form // (input).Project(x => new Entity()) // or // (input).Project(x => CASE WHEN (condition1) THEN new Entity1() ELSE WHEN (condition2) THEN new Entity2()... ELSE new EntityN()) // where every new instance expression Entity1()...EntityN() constructs an entity of a type // that is compatible with the entity set's element type. // Here, the list of all DbNewInstanceExpressions contained in the projection is remembered, // along with any CASE statement conditions, if present. These expressions will be updated // if necessary and used to build a new capping projection if any of the entity constructors // are augmented with FK-based related entity references. // var entityProject = (DbProjectExpression)viewConstructor; var constructors = new List <DbNewInstanceExpression>(); List <DbExpression> conditions = null; if (entityProject.Projection.ExpressionKind == DbExpressionKind.Case) { // If the projection is a DbCaseExpression, then every result must be a DbNewInstanceExpression var discriminatedConstructor = (DbCaseExpression)entityProject.Projection; conditions = new List <DbExpression>(discriminatedConstructor.When.Count); for (var idx = 0; idx < discriminatedConstructor.When.Count; idx++) { conditions.Add(discriminatedConstructor.When[idx]); constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Then[idx]); } constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Else); } else { // Otherwise, the projection must be a single DbNewInstanceExpression constructors.Add((DbNewInstanceExpression)entityProject.Projection); } var rebuildView = false; for (var idx = 0; idx < constructors.Count; idx++) { var entityConstructor = constructors[idx]; var constructedEntityType = TypeHelpers.GetEdmType <EntityType>(entityConstructor.ResultType); var relatedRefs = principalSetsAndDependentTypes .Where(psdt => constructedEntityType == psdt.Item1 || constructedEntityType.IsSubtypeOf(psdt.Item1)) .Select( psdt => RelatedEntityRefFromAssociationSetEnd(constructedEntityType, entityConstructor, psdt.Item2, psdt.Item3)) .ToList(); if (relatedRefs.Count > 0) { if (entityConstructor.HasRelatedEntityReferences) { relatedRefs = entityConstructor.RelatedEntityReferences.Concat(relatedRefs).ToList(); } entityConstructor = DbExpressionBuilder.CreateNewEntityWithRelationshipsExpression( constructedEntityType, entityConstructor.Arguments, relatedRefs); constructors[idx] = entityConstructor; rebuildView = true; } } // Default to returning null to indicate that this rule did not produce a modified expression // DbExpression result = null; if (rebuildView) { // rebuildView is true, so entity constructing DbNewInstanceExpression(s) were encountered // and updated with additional related entity refs. The DbProjectExpression that caps the // view definition therefore needs to be rebuilt and returned as the result of this rule. // if (conditions != null) { // The original view definition projection was a DbCaseExpression. // The new expression is also a DbCaseExpression that uses the conditions from the // original expression together with the updated result expressions to produce the // new capping projection. // var whens = new List <DbExpression>(conditions.Count); var thens = new List <DbExpression>(conditions.Count); for (var idx = 0; idx < conditions.Count; idx++) { whens.Add(conditions[idx]); thens.Add(constructors[idx]); } result = entityProject.Input.Project(DbExpressionBuilder.Case(whens, thens, constructors[conditions.Count])); } else { // Otherwise, the capping projection consists entirely of the updated DbNewInstanceExpression. // result = entityProject.Input.Project(constructors[0]); } } // Regardless of whether or not the view was updated, this rule should not be applied again during rule processing doNotProcess = true; return(result); }
protected override Expression VisitConstant(ConstantExpression node) { #if (DEBUG_VISITS) System.Diagnostics.Debug.Print("VisitConstant: {0}", node); #endif var expression = base.VisitConstant(node); var type = node.Type; if (IsNullableType(type)) { var genericArgs = type.GetGenericArguments(); if ((genericArgs != null) && (genericArgs.Length == 1)) { type = genericArgs[0]; } } if (type == typeof(byte[])) { MapExpressionToDbExpression(expression, DbConstantExpression.FromBinary((byte[])node.Value)); } else if (type == typeof(bool)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromBoolean((bool?)node.Value)); } else if (type == typeof(byte)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromByte((byte?)node.Value)); } else if (type == typeof(DateTime)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromDateTime((DateTime?)node.Value)); } else if (type == typeof(DateTimeOffset)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromDateTimeOffset((DateTimeOffset?)node.Value)); } else if (type == typeof(decimal)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromDecimal((decimal?)node.Value)); } else if (type == typeof(double)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromDouble((double?)node.Value)); } else if (type == typeof(Guid)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromGuid((Guid?)node.Value)); } else if (type == typeof(Int16)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromInt16((Int16?)node.Value)); } else if (type == typeof(Int32)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromInt32((Int32?)node.Value)); } else if (type.IsEnum) { if (_DataSpace == DataSpace.CSpace) { var typeUsage = TypeUsageForPrimitiveType(node.Type); MapExpressionToDbExpression(expression, DbExpressionBuilder.Constant(typeUsage, node.Value)); } else { MapExpressionToDbExpression(expression, DbConstantExpression.FromInt32((Int32)node.Value)); } } else if (type == typeof(Int64)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromInt64((Int64?)node.Value)); } else if (type == typeof(float)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromSingle((float?)node.Value)); } else if (type == typeof(string)) { MapExpressionToDbExpression(expression, DbConstantExpression.FromString((string)node.Value)); } else { throw new NotImplementedException(string.Format("Unhandled Type of {0} for Constant value {1} in LambdaToDbExpressionVisitor.VisitConstant", node.Type.Name, node.Value ?? "null")); } return(expression); }
private IEnumerable <DbModificationClause> BuildSetClauses( DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, bool insertMode, out Dictionary <int, string> outputIdentifiers, out DbExpression returning, ref bool rowMustBeTouched) { Dictionary <EdmProperty, PropagatorResult> dictionary = new Dictionary <EdmProperty, PropagatorResult>(); List <KeyValuePair <string, DbExpression> > keyValuePairList = new List <KeyValuePair <string, DbExpression> >(); outputIdentifiers = new Dictionary <int, string>(); PropagatorFlags propagatorFlags1 = insertMode ? PropagatorFlags.NoFlags : PropagatorFlags.Preserve | PropagatorFlags.Unknown; for (int index1 = 0; index1 < processor.Table.ElementType.Properties.Count; ++index1) { EdmProperty property = processor.Table.ElementType.Properties[index1]; PropagatorResult result = row.GetMemberValue(index1); if (-1 != result.Identifier) { result = result.ReplicateResultWithNewValue(this.m_translator.KeyManager.GetPrincipalValue(result)); } bool flag1 = false; bool flag2 = false; for (int index2 = 0; index2 < processor.KeyOrdinals.Length; ++index2) { if (processor.KeyOrdinals[index2] == index1) { flag2 = true; break; } } PropagatorFlags propagatorFlags2 = PropagatorFlags.NoFlags; if (!insertMode && flag2) { flag1 = true; } else { propagatorFlags2 |= result.PropagatorFlags; } StoreGeneratedPattern generatedPattern = MetadataHelper.GetStoreGeneratedPattern((EdmMember)property); bool flag3 = generatedPattern == StoreGeneratedPattern.Computed || insertMode && generatedPattern == StoreGeneratedPattern.Identity; if (flag3) { DbPropertyExpression propertyExpression = target.Variable.Property(property); keyValuePairList.Add(new KeyValuePair <string, DbExpression>(property.Name, (DbExpression)propertyExpression)); int identifier = result.Identifier; if (-1 != identifier) { if (this.m_translator.KeyManager.HasPrincipals(identifier)) { throw new InvalidOperationException(Strings.Update_GeneratedDependent((object)property.Name)); } outputIdentifiers.Add(identifier, property.Name); if (generatedPattern != StoreGeneratedPattern.Identity && processor.IsKeyProperty(index1)) { throw new NotSupportedException(Strings.Update_NotSupportedComputedKeyColumn((object)"StoreGeneratedPattern", (object)"Computed", (object)"Identity", (object)property.Name, (object)property.DeclaringType.FullName)); } } } if ((propagatorFlags2 & propagatorFlags1) != PropagatorFlags.NoFlags) { flag1 = true; } else if (flag3) { flag1 = true; rowMustBeTouched = true; } if (!flag1 && !insertMode && generatedPattern == StoreGeneratedPattern.Identity) { PropagatorResult memberValue = originalRow.GetMemberValue(index1); if (!ByValueEqualityComparer.Default.Equals(memberValue.GetSimpleValue(), result.GetSimpleValue())) { throw new InvalidOperationException(Strings.Update_ModifyingIdentityColumn((object)"Identity", (object)property.Name, (object)property.DeclaringType.FullName)); } flag1 = true; } if (!flag1) { dictionary.Add(property, result); } } returning = 0 >= keyValuePairList.Count ? (DbExpression)null : (DbExpression)DbExpressionBuilder.NewRow((IEnumerable <KeyValuePair <string, DbExpression> >)keyValuePairList); List <DbModificationClause> modificationClauseList = new List <DbModificationClause>(dictionary.Count); foreach (KeyValuePair <EdmProperty, PropagatorResult> keyValuePair in dictionary) { EdmProperty key = keyValuePair.Key; modificationClauseList.Add((DbModificationClause) new DbSetClause(UpdateCompiler.GeneratePropertyExpression(target, keyValuePair.Key), this.GenerateValueExpression(keyValuePair.Key, keyValuePair.Value))); } return((IEnumerable <DbModificationClause>)modificationClauseList); }
private DbFilterExpression BuildFilterExpressionWithDynamicFilters(string entityName, IEnumerable <DynamicFilterDefinition> filterList, DbExpressionBinding binding, DbExpression predicate) { if (!filterList.Any()) { return(null); } var edmType = binding.VariableType.EdmType as EntityType; if (edmType == null) { return(null); } List <DbExpression> conditionList = new List <DbExpression>(); HashSet <string> processedFilterNames = new HashSet <string>(); foreach (var filter in filterList) { if (processedFilterNames.Contains(filter.FilterName)) { continue; // Already processed this filter - attribute was probably inherited in a base class } processedFilterNames.Add(filter.FilterName); DbExpression dbExpression; if (!string.IsNullOrEmpty(filter.ColumnName)) { // Single column equality filter // Need to map through the EdmType properties to find the actual database/cspace name for the entity property. // It may be different from the entity property! var edmProp = edmType.Properties.Where(p => p.MetadataProperties.Any(m => m.Name == "PreferredName" && m.Value.Equals(filter.ColumnName))).FirstOrDefault(); if (edmProp == null) { continue; // ??? } // database column name is now in edmProp.Name. Use that instead of filter.ColumnName var columnProperty = DbExpressionBuilder.Property(DbExpressionBuilder.Variable(binding.VariableType, binding.VariableName), edmProp.Name); var param = columnProperty.Property.TypeUsage.Parameter(filter.CreateDynamicFilterName(filter.ColumnName)); dbExpression = DbExpressionBuilder.Equal(columnProperty, param); } else if (filter.Predicate != null) { // Lambda expression filter dbExpression = LambdaToDbExpressionVisitor.Convert(filter, binding, _ObjectContext); } else { throw new System.ArgumentException(string.Format("Filter {0} does not contain a ColumnName or a Predicate!", filter.FilterName)); } // Create an expression to check to see if the filter has been disabled and include that check with the rest of the filter expression. // When this parameter is null, the filter is enabled. It will be set to true (in DynamicFilterExtensions.GetFilterParameterValue) if // the filter has been disabled. var boolPrimitiveType = _ObjectContext.MetadataWorkspace .GetPrimitiveTypes(DataSpace.CSpace) .Where(p => p.ClrEquivalentType == typeof(bool)) .Single(); var isDisabledParam = DbExpressionBuilder.Parameter(TypeUsage.Create(boolPrimitiveType, new List <Facet>()), filter.CreateFilterDisabledParameterName()); conditionList.Add(DbExpressionBuilder.Or(dbExpression, DbExpressionBuilder.Not(DbExpressionBuilder.IsNull(isDisabledParam)))); } int numConditions = conditionList.Count; DbExpression newPredicate; switch (numConditions) { case 0: return(null); case 1: newPredicate = conditionList.First(); break; default: // Have multiple conditions. Need to append them together using 'and' conditions. newPredicate = conditionList.First(); for (int i = 1; i < numConditions; i++) { newPredicate = newPredicate.And(conditionList[i]); } break; } // 'and' the existing Predicate if there is one if (predicate != null) { newPredicate = newPredicate.And(predicate); } return(DbExpressionBuilder.Filter(binding, newPredicate)); }
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) { if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace) { var insertCommand = interceptionContext.Result as DbInsertCommandTree; if (insertCommand != null) { List <DbModificationClause> finalSetClauses = new List <DbModificationClause>( (IEnumerable <DbModificationClause>)insertCommand.SetClauses.Select( a => { var dbSetClause = a as DbSetClause; if (dbSetClause != null) { var dbPropertyExpression = dbSetClause.Property as DbPropertyExpression; if (dbPropertyExpression != null) { var edmProperty = dbPropertyExpression.Property as EdmProperty; if (edmProperty != null && edmProperty.MaxLength != null && _typesToTrim.Contains(edmProperty.TypeName)) { var dbConstantExpression = dbSetClause.Value as DbConstantExpression; if (dbConstantExpression != null && dbConstantExpression.Value != null) { var value = dbConstantExpression.Value.ToString(); if (!string.IsNullOrEmpty(value) && value.Length > (int)edmProperty.MaxLength) { return(DbExpressionBuilder.SetClause( DbExpressionBuilder.Property( DbExpressionBuilder.Variable( insertCommand.Target.VariableType, insertCommand.Target.VariableName), dbPropertyExpression.Property.Name), EdmFunctions.Trim( dbConstantExpression.Value.ToString() .Substring(0, (int)edmProperty.MaxLength)))); } } } } } return(a); })); var newInsertCommand = new DbInsertCommandTree( insertCommand.MetadataWorkspace, insertCommand.DataSpace, insertCommand.Target, new ReadOnlyCollection <DbModificationClause>(finalSetClauses), insertCommand.Returning); interceptionContext.Result = newInsertCommand; } } var updateCommand = interceptionContext.Result as DbUpdateCommandTree; if (updateCommand != null) { List <DbModificationClause> finalSetClauses = new List <DbModificationClause>( (IEnumerable <DbModificationClause>)updateCommand.SetClauses.Select( a => { var dbSetClause = a as DbSetClause; if (dbSetClause != null) { var dbPropertyExpression = dbSetClause.Property as DbPropertyExpression; if (dbPropertyExpression != null) { var edmProperty = dbPropertyExpression.Property as EdmProperty; if (edmProperty != null && edmProperty.MaxLength != null && _typesToTrim.Contains(edmProperty.TypeName)) { var dbConstantExpression = dbSetClause.Value as DbConstantExpression; if (dbConstantExpression != null && dbConstantExpression.Value != null) { var value = dbConstantExpression.Value.ToString(); if (!string.IsNullOrEmpty(value) && value.Length > (int)edmProperty.MaxLength) { return(DbExpressionBuilder.SetClause( DbExpressionBuilder.Property( DbExpressionBuilder.Variable( updateCommand.Target.VariableType, updateCommand.Target.VariableName), dbPropertyExpression.Property.Name), EdmFunctions.Trim( dbConstantExpression.Value.ToString() .Substring(0, (int)edmProperty.MaxLength)))); } } } } } return(a); })); var newInsertCommand = new DbUpdateCommandTree( updateCommand.MetadataWorkspace, updateCommand.DataSpace, updateCommand.Target, updateCommand.Predicate, new ReadOnlyCollection <DbModificationClause>(finalSetClauses), updateCommand.Returning); interceptionContext.Result = newInsertCommand; } }
private DbFilterExpression BuildFilterExpressionWithDynamicFilters(string entityName, IEnumerable <DynamicFilterDefinition> filterList, DbExpressionBinding binding, DbExpression predicate) { if (!filterList.Any()) { return(null); } var edmType = binding.VariableType.EdmType as EntityType; if (edmType == null) { return(null); } List <DbExpression> conditionList = new List <DbExpression>(); HashSet <string> processedFilterNames = new HashSet <string>(); foreach (var filter in filterList) { if (processedFilterNames.Contains(filter.FilterName)) { continue; // Already processed this filter - attribute was probably inherited in a base class } processedFilterNames.Add(filter.FilterName); DbExpression dbExpression; if (!string.IsNullOrEmpty(filter.ColumnName)) { // Single column equality filter // Need to map through the EdmType properties to find the actual database/cspace name for the entity property. // It may be different from the entity property! var edmProp = edmType.Properties.Where(p => p.MetadataProperties.Any(m => m.Name == "PreferredName" && m.Value.Equals(filter.ColumnName))).FirstOrDefault(); if (edmProp == null) { continue; // ??? } // database column name is now in edmProp.Name. Use that instead of filter.ColumnName var columnProperty = DbExpressionBuilder.Property(DbExpressionBuilder.Variable(binding.VariableType, binding.VariableName), edmProp.Name); var param = columnProperty.Property.TypeUsage.Parameter(filter.CreateDynamicFilterName(filter.ColumnName, DataSpace.SSpace)); // When using SSpace, need some special handling for an Oracle Boolean property. // Not necessary when using CSpace since the translation into the Oracle types has not happened yet. if ((columnProperty.ResultType.EdmType.FullName == "Edm.Boolean") && param.ResultType.EdmType.FullName.StartsWith("Oracle", StringComparison.CurrentCultureIgnoreCase) && (param.ResultType.EdmType.Name == "number")) // Don't trust Oracle's type name to stay the same... { // Special handling needed for columnProperty boolean. For some reason, the Oracle EF driver does not correctly // set the ResultType to a number(1) in columnProperty like it does in columnProperty.Property.TypeUsage. That // results in us trying to do a comparison of a Boolean to a number(1) which causes DbExpressionBuilder.Equal // to throw an exception. To get this to process correctly, we need to do a cast on the columnProperty to // "number(1)" so that it matches the param.ResultType. And that results in the sql sent to Oracle converting // the column to the type that it already is... dbExpression = DbExpressionBuilder.Equal(DbExpressionBuilder.CastTo(columnProperty, param.ResultType), param); } else { dbExpression = DbExpressionBuilder.Equal(columnProperty, param); } } else if (filter.Predicate != null) { // Lambda expression filter dbExpression = LambdaToDbExpressionVisitor.Convert(filter, binding, _DbContext, DataSpace.SSpace); } else { throw new System.ArgumentException(string.Format("Filter {0} does not contain a ColumnName or a Predicate!", filter.FilterName)); } if (DynamicFilterExtensions.AreFilterDisabledConditionsAllowed(filter.FilterName)) { // Create an expression to check to see if the filter has been disabled and include that check with the rest of the filter expression. // When this parameter is null, the filter is enabled. It will be set to true (in DynamicFilterExtensions.GetFilterParameterValue) if // the filter has been disabled. var boolPrimitiveType = LambdaToDbExpressionVisitor.TypeUsageForPrimitiveType(typeof(bool?), _ObjectContext, DataSpace.SSpace); var isDisabledParam = boolPrimitiveType.Parameter(filter.CreateFilterDisabledParameterName(DataSpace.SSpace)); conditionList.Add(DbExpressionBuilder.Or(dbExpression, DbExpressionBuilder.Not(DbExpressionBuilder.IsNull(isDisabledParam)))); } else { conditionList.Add(dbExpression); } } int numConditions = conditionList.Count; DbExpression newPredicate; switch (numConditions) { case 0: return(null); case 1: newPredicate = conditionList.First(); break; default: // Have multiple conditions. Need to append them together using 'and' conditions. newPredicate = conditionList.First(); for (int i = 1; i < numConditions; i++) { newPredicate = newPredicate.And(conditionList[i]); } break; } // 'and' the existing Predicate if there is one if (predicate != null) { newPredicate = newPredicate.And(predicate); } return(DbExpressionBuilder.Filter(binding, newPredicate)); }
public override DbExpression Visit(DbParameterReferenceExpression expression) { return((DbExpression)DbExpressionBuilder.Constant(this._parameters[expression.ParameterName].Value)); }
/// <summary> /// Visit a Member Expression. Creates a mapping of the MemberExpression to a DbPropertyExpression /// which is a reference to the table/column name that matches the MemberExpression. /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { #if (DEBUGPRINT) System.Diagnostics.Debug.Print("VisitMember: {0}, expression.NodeType={1}, Member={2}", node, node.Expression.NodeType, node.Member); #endif var expression = base.VisitMember(node) as MemberExpression; if ((expression.Expression.NodeType == ExpressionType.Parameter) && (expression.Expression.Type.IsClass || expression.Expression.Type.IsInterface)) { // expression is a reference to a class/interface property. Need to map it to a sql parameter or look up // the existing parameter. // The class/interface is defined by expression.Expression while the property is in expression.Member. string propertyName; if (IsNullableType(expression.Member.ReflectedType)) { var subExpression = expression.Expression as MemberExpression; propertyName = subExpression.Member.Name; } else { propertyName = expression.Member.Name; } // TODO: To support different class/interfaces, can we figure out the correct binding from what's in expression.Expression? var edmType = _Binding.VariableType.EdmType as EntityType; DbPropertyExpression propertyExpression; if (!_Properties.TryGetValue(propertyName, out propertyExpression)) { // Not created yet // Need to map through the EdmType properties to find the actual database/cspace name for the entity property. // It may be different from the entity property! var edmProp = edmType.Properties.Where(p => p.MetadataProperties.Any(m => m.Name == "PreferredName" && m.Value.Equals(propertyName))).FirstOrDefault(); if (edmProp == null) { // Accessing properties outside the main entity is not supported and will cause this exception. throw new ApplicationException(string.Format("Property {0} not found in Entity Type {1}", propertyName, expression.Expression.Type.Name)); } // database column name is now in edmProp.Name. Use that instead of filter.ColumnName propertyExpression = DbExpressionBuilder.Property(DbExpressionBuilder.Variable(_Binding.VariableType, _Binding.VariableName), edmProp.Name); _Properties.Add(propertyName, propertyExpression); #if (DEBUGPRINT) System.Diagnostics.Debug.Print("Created new property expression for {0}", propertyName); #endif } // Nothing else to do here MapExpressionToDbExpression(expression, propertyExpression); return(expression); } // We are accessing a member property such that expression.Expression is the object and expression.Member is the property. // And the property is one that requires special handling. Regular class properties are all handled up above. var objectExpression = GetDbExpressionForExpression(expression.Expression); DbExpression dbExpression; switch (expression.Member.Name) { case "HasValue": // Map HasValue to !IsNull dbExpression = DbExpressionBuilder.Not(DbExpressionBuilder.IsNull(objectExpression)); break; case "Value": // This is a nullable Value accessor so just map to the object itself and it will be mapped for us dbExpression = objectExpression; break; default: throw new ApplicationException(string.Format("Unhandled property accessor in expression: {0}", expression)); } MapExpressionToDbExpression(expression, dbExpression); return(expression); }
public override DbExpression Visit(DbScanExpression expression) { // here we go! return(DbExpressionBuilder.Limit(expression, 128)); }
private DbFilterExpression BuildFilterExpressionWithDynamicFilters(IEnumerable <DynamicFilterDefinition> filterList, DbExpressionBinding binding, DbExpression predicate) { if (!filterList.Any()) { return(null); } var edmType = binding.VariableType.EdmType as EntityType; if (edmType == null) { return(null); } List <DbExpression> conditionList = new List <DbExpression>(); HashSet <string> processedFilterNames = new HashSet <string>(); foreach (var filter in filterList) { if (processedFilterNames.Contains(filter.FilterName)) { continue; // Already processed this filter - attribute was probably inherited in a base class } processedFilterNames.Add(filter.FilterName); _AppliedFilters.Add(filter.FilterName); DbExpression dbExpression; if (!string.IsNullOrEmpty(filter.ColumnName)) { // Single column equality filter var edmProp = edmType.Properties.FirstOrDefault(p => p.Name == filter.ColumnName); if (edmProp == null) { continue; // ??? } // database column name is now in edmProp.Name. Use that instead of filter.ColumnName var columnProperty = DbExpressionBuilder.Property(DbExpressionBuilder.Variable(binding.VariableType, binding.VariableName), edmProp.Name); var param = columnProperty.Property.TypeUsage.Parameter(filter.CreateDynamicFilterName(filter.ColumnName, DataSpace.CSpace)); dbExpression = DbExpressionBuilder.Equal(columnProperty, param); // When using SSpace, need some special handling for an Oracle Boolean property. // Not necessary when using CSpace since the translation into the Oracle types has not happened yet. } else if (filter.Predicate != null) { // Lambda expression filter dbExpression = LambdaToDbExpressionVisitor.Convert(filter, binding, _DbContext, DataSpace.CSpace); } else { throw new System.ArgumentException(string.Format("Filter {0} does not contain a ColumnName or a Predicate!", filter.FilterName)); } if (DynamicFilterExtensions.AreFilterDisabledConditionsAllowed(filter.FilterName)) { // Create an expression to check to see if the filter has been disabled and include that check with the rest of the filter expression. // When this parameter is null, the filter is enabled. It will be set to true (in DynamicFilterExtensions.GetFilterParameterValue) if // the filter has been disabled. var boolPrimitiveType = LambdaToDbExpressionVisitor.TypeUsageForPrimitiveType(typeof(bool?), _ObjectContext, DataSpace.CSpace); var isDisabledParam = boolPrimitiveType.Parameter(filter.CreateFilterDisabledParameterName(DataSpace.CSpace)); conditionList.Add(DbExpressionBuilder.Or(dbExpression, DbExpressionBuilder.Not(DbExpressionBuilder.IsNull(isDisabledParam)))); } else { conditionList.Add(dbExpression); } } int numConditions = conditionList.Count; DbExpression newPredicate; switch (numConditions) { case 0: return(null); case 1: newPredicate = conditionList.First(); break; default: // Have multiple conditions. Need to append them together using 'and' conditions. newPredicate = conditionList.First(); for (int i = 1; i < numConditions; i++) { newPredicate = newPredicate.And(conditionList[i]); } break; } // 'and' the existing Predicate if there is one if (predicate != null) { newPredicate = newPredicate.And(predicate); } return(DbExpressionBuilder.Filter(binding, newPredicate)); }
private DbExpression RewriteEntity(DbExpression expression, EntityType entityType) { // If the expression is an Entity constructor, spanning will not produce any useful results // (null for an Entity/Ref navigation property, or an empty collection for a Collection // of Entity/Ref navigation property) since a Ref produced from the constructed Entity // will not indicate an Entity set, and therefore no Ref created against any Entity set // in the container can possibly be a match for it. if (DbExpressionKind.NewInstance == expression.ExpressionKind) { return(expression); } // Save the span count for later use. _spanCount++; var thisSpan = _spanCount; var tracking = CreateEntitySpanTrackingInfo(expression, entityType); // If relationship span is required then attempt to span any appropriate relationship ends. List <KeyValuePair <AssociationEndMember, AssociationEndMember> > relationshipSpans = null; relationshipSpans = GetRelationshipSpanEnds(entityType); // Is the Entity type of this expression valid as the source of at least one relationship span? if (relationshipSpans != null) { // If the span tracking information was not initialized by CreateEntitySpanTrackingInfo, // then do so now as relationship span rewrites need to be tracked. if (null == tracking.ColumnDefinitions) { tracking = InitializeTrackingInfo(false); } // Track column index to span information, starting at the current column count (which could be zero) plus 1. // 1 is added because the column containing the root entity will be added later to provide column zero. var idx = tracking.ColumnDefinitions.Count + 1; // For all applicable relationship spans that were identified... foreach (var relSpan in relationshipSpans) { // If the specified association end member was already full-spanned then the full entity // will be returned in the query and there is no need to relationship-span this end to produce // another result column that contains the Entity key of the full entity. // Hence the relationship span is only added if there are no full-span columns or the full-span // columns do not indicate that they include the target association end member of this relationship span. if (null == tracking.FullSpannedEnds || !tracking.FullSpannedEnds.ContainsKey(relSpan.Value)) { // If the source Ref is already available, because the currently spanned Entity is // the result of a Relationship Navigation operation from that Ref, then use the source // Ref directly rather than introducing a new Navigation operation. DbExpression columnDef = null; if (!TryGetNavigationSource(relSpan.Value, out columnDef)) { // Add a new column defined by the navigation required to reach the targeted association end // and update the column -> association end map to include an entry for this new column. DbExpression navSource = expression.GetEntityRef(); columnDef = navSource.NavigateAllowingAllRelationshipsInSameTypeHierarchy(relSpan.Key, relSpan.Value); } tracking.ColumnDefinitions.Add( new KeyValuePair <string, DbExpression>( tracking.ColumnNames.Next(), columnDef ) ); tracking.SpannedColumns[idx] = relSpan.Value; // Increment the tracked column count idx++; } } } // If no spanned columns have been added then simply return the original expression if (null == tracking.ColumnDefinitions) { _spanCount--; return(expression); } // Add the original entity-producing expression as the first (root) span column. tracking.ColumnDefinitions.Insert( 0, new KeyValuePair <string, DbExpression>( string.Format(CultureInfo.InvariantCulture, "Span{0}_SpanRoot", thisSpan), expression ) ); // Create the span row-producing NewInstanceExpression from which the span RowType can be retrieved. DbExpression spannedExpression = DbExpressionBuilder.NewRow(tracking.ColumnDefinitions); // Update the rowtype -> spaninfo map for the newly created row type instance. var spanRowType = (RowType)spannedExpression.ResultType.EdmType; AddSpanMap(spanRowType, tracking.SpannedColumns); // Return the rewritten expression return(spannedExpression); }
private static DbExpression SimplifyNestedTphDiscriminator(DbExpression expression) { var entityProjection = (DbProjectExpression)expression; var booleanColumnFilter = (DbFilterExpression)entityProjection.Input.Expression; var rowProjection = (DbProjectExpression)booleanColumnFilter.Input.Expression; var discriminatorFilter = (DbFilterExpression)rowProjection.Input.Expression; var predicates = FlattenOr(booleanColumnFilter.Predicate).ToList(); var propertyPredicates = predicates.OfType <DbPropertyExpression>() .Where( px => px.Instance.ExpressionKind == DbExpressionKind.VariableReference && ((DbVariableReferenceExpression)px.Instance).VariableName == booleanColumnFilter.Input.VariableName) .ToList(); if (predicates.Count != propertyPredicates.Count) { return(null); } var predicateColumnNames = propertyPredicates.Select(px => px.Property.Name).ToList(); var discriminatorPredicates = new Dictionary <object, DbComparisonExpression>(); if (!TypeSemantics.IsEntityType(discriminatorFilter.Input.VariableType) || !TryMatchDiscriminatorPredicate(discriminatorFilter, (compEx, discValue) => discriminatorPredicates.Add(discValue, compEx))) { return(null); } var discriminatorProp = (EdmProperty)((DbPropertyExpression)(discriminatorPredicates.First().Value).Left).Property; var rowConstructor = (DbNewInstanceExpression)rowProjection.Projection; var resultRow = TypeHelpers.GetEdmType <RowType>(rowConstructor.ResultType); var inputPredicateMap = new Dictionary <string, DbComparisonExpression>(); var selectorPredicateMap = new Dictionary <string, DbComparisonExpression>(); var columnValues = new Dictionary <string, DbExpression>(rowConstructor.Arguments.Count); for (var idx = 0; idx < rowConstructor.Arguments.Count; idx++) { var propName = resultRow.Properties[idx].Name; var columnVal = rowConstructor.Arguments[idx]; if (predicateColumnNames.Contains(propName)) { if (columnVal.ExpressionKind != DbExpressionKind.Case) { return(null); } var casePredicate = (DbCaseExpression)columnVal; if (casePredicate.When.Count != 1 || !TypeSemantics.IsBooleanType(casePredicate.Then[0].ResultType) || !TypeSemantics.IsBooleanType(casePredicate.Else.ResultType) || casePredicate.Then[0].ExpressionKind != DbExpressionKind.Constant || casePredicate.Else.ExpressionKind != DbExpressionKind.Constant || (bool)((DbConstantExpression)casePredicate.Then[0]).Value != true || (bool)((DbConstantExpression)casePredicate.Else).Value) { return(null); } DbPropertyExpression comparedProp; object constValue; if ( !TryMatchPropertyEqualsValue( casePredicate.When[0], rowProjection.Input.VariableName, out comparedProp, out constValue) || comparedProp.Property != discriminatorProp || !discriminatorPredicates.ContainsKey(constValue)) { return(null); } inputPredicateMap.Add(propName, discriminatorPredicates[constValue]); selectorPredicateMap.Add(propName, (DbComparisonExpression)casePredicate.When[0]); } else { columnValues.Add(propName, columnVal); } } // Build a new discriminator-based filter that only includes the same rows allowed by the higher '_from0' column-based filter var newDiscriminatorPredicate = Helpers.BuildBalancedTreeInPlace( new List <DbExpression>(inputPredicateMap.Values), (left, right) => left.Or(right)); discriminatorFilter = discriminatorFilter.Input.Filter(newDiscriminatorPredicate); var entitySelector = (DbCaseExpression)entityProjection.Projection; var newWhens = new List <DbExpression>(entitySelector.When.Count); var newThens = new List <DbExpression>(entitySelector.Then.Count); for (var idx = 0; idx < entitySelector.When.Count; idx++) { var propWhen = (DbPropertyExpression)entitySelector.When[idx]; var entityThen = (DbNewInstanceExpression)entitySelector.Then[idx]; DbComparisonExpression discriminatorWhen; if (!selectorPredicateMap.TryGetValue(propWhen.Property.Name, out discriminatorWhen)) { return(null); } newWhens.Add(discriminatorWhen); var inputBoundEntityConstructor = ValueSubstituter.Substitute(entityThen, entityProjection.Input.VariableName, columnValues); newThens.Add(inputBoundEntityConstructor); } var newElse = ValueSubstituter.Substitute(entitySelector.Else, entityProjection.Input.VariableName, columnValues); var newEntitySelector = DbExpressionBuilder.Case(newWhens, newThens, newElse); DbExpression result = discriminatorFilter.BindAs(rowProjection.Input.VariableName).Project(newEntitySelector); return(result); }
private DbExpression RewriteRow(DbExpression expression, RowType rowType) { var lambdaExpression = expression as DbLambdaExpression; DbNewInstanceExpression newRow; if (lambdaExpression != null) { // NOTE: We rely on the fact that today span cannot be done over queries containing DbLambdaExpressions // created by users, because user-created expressions cannot be used for querying in O-space. // If that were to change, pushing span beyond a LambdaExpression could cause variable name // collisions between the variable names used in the Lambda and the names generated by the // RelationshipNavigationVisitor. newRow = lambdaExpression.Lambda.Body as DbNewInstanceExpression; } else { newRow = expression as DbNewInstanceExpression; } Dictionary <int, DbExpression> unmodifiedColumns = null; Dictionary <int, DbExpression> spannedColumns = null; for (var idx = 0; idx < rowType.Properties.Count; idx++) { // Retrieve the property that represents the current column var columnProp = rowType.Properties[idx]; // Construct an expression that defines the current column. DbExpression columnExpr = null; if (newRow != null) { // For a row-constructing NewInstance expression, the corresponding argument can simply be used columnExpr = newRow.Arguments[idx]; } else { // For all other expressions the property corresponding to the column name must be retrieved // from the row-typed expression columnExpr = expression.Property(columnProp.Name); } var spannedColumn = Rewrite(columnExpr); if (!ReferenceEquals(spannedColumn, columnExpr)) { // If so, then update the dictionary of column index to span information if (null == spannedColumns) { spannedColumns = new Dictionary <int, DbExpression>(); } spannedColumns[idx] = spannedColumn; } else { // Otherwise, update the dictionary of column index to unmodified expression if (null == unmodifiedColumns) { unmodifiedColumns = new Dictionary <int, DbExpression>(); } unmodifiedColumns[idx] = columnExpr; } } // A new expression need only be built if at least one column was spanned if (null == spannedColumns) { // No columns were spanned, indicate that the original expression should remain. return(expression); } else { // At least one column was spanned, so build a new row constructor that defines the new row, including spanned columns. var columnArguments = new List <DbExpression>(rowType.Properties.Count); var properties = new List <EdmProperty>(rowType.Properties.Count); for (var idx = 0; idx < rowType.Properties.Count; idx++) { var columnProp = rowType.Properties[idx]; DbExpression columnDef = null; if (!spannedColumns.TryGetValue(idx, out columnDef)) { columnDef = unmodifiedColumns[idx]; } columnArguments.Add(columnDef); properties.Add(new EdmProperty(columnProp.Name, columnDef.ResultType)); } // Copy over any eLinq initializer metadata (if present, or null if not). // Note that this initializer metadata does not strictly match the new row type // that includes spanned columns, but will be correct once the object materializer // has interpreted the query results to produce the correct value for each colum. var rewrittenRow = new RowType(properties, rowType.InitializerMetadata); var rewrittenRowTypeUsage = TypeUsage.Create(rewrittenRow); DbExpression rewritten = rewrittenRowTypeUsage.New(columnArguments); // SQLBUDT #554182: If we insert a new projection we should make sure to // not interfere with the nullability of the input. // In particular, if the input row is null and we construct a new row as a projection over its columns // we would get a row consisting of nulls, instead of a null row. // Thus, given an input X, we rewritte it as: if (X is null) then NULL else rewritten. if (newRow == null) { DbExpression condition = expression.IsNull(); DbExpression nullExpression = rewrittenRowTypeUsage.Null(); rewritten = DbExpressionBuilder.Case( new List <DbExpression>(new[] { condition }), new List <DbExpression>(new[] { nullExpression }), rewritten); } // Add an entry to the spanned row type => original row type map for the new row type. AddSpannedRowType(rewrittenRow, expression.ResultType); if (lambdaExpression != null && newRow != null) { rewritten = DbLambda.Create(rewritten, lambdaExpression.Lambda.Variables).Invoke(lambdaExpression.Arguments); } return(rewritten); } }
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 RewriteCollection(DbExpression expression) { var target = expression; // If the collection expression is a project expression, get a strongly typed reference to it for later use. DbProjectExpression project = null; if (DbExpressionKind.Project == expression.ExpressionKind) { project = (DbProjectExpression)expression; target = project.Input.Expression; } // If Relationship span is enabled and the source of this collection is (directly or indirectly) // a RelationshipNavigation operation, it may be possible to optimize the relationship span rewrite // for the Entities produced by the navigation. NavigationInfo navInfo = null; if (RelationshipSpan) { // Attempt to find a RelationshipNavigationExpression in the collection-defining expression target = RelationshipNavigationVisitor.FindNavigationExpression(target, _aliasGenerator, out navInfo); } // If a relationship navigation expression defines this collection, make the Ref that is the navigation source // and the source association end available for possible use when the projection over the collection is rewritten. if (navInfo != null) { EnterNavigationCollection(navInfo); } else { // Otherwise, add a null navigation info instance to the stack to indicate that relationship navigation // cannot be optimized for the entities produced by this collection expression (if it is a collection of entities). EnterCollection(); } // If the expression is already a DbProjectExpression then simply visit the projection, // instead of introducing another projection over the existing one. var result = expression; if (project != null) { var newProjection = Rewrite(project.Projection); if (!ReferenceEquals(project.Projection, newProjection)) { result = target.BindAs(project.Input.VariableName).Project(newProjection); } } else { // This is not a recognized special case, so simply add the span projection over the original // collection-producing expression, if it is required. var collectionBinding = target.BindAs(_aliasGenerator.Next()); DbExpression projection = collectionBinding.Variable; var spannedProjection = Rewrite(projection); if (!ReferenceEquals(projection, spannedProjection)) { result = collectionBinding.Project(spannedProjection); } } // Remove any navigation information from scope, if it was added ExitCollection(); // If a navigation expression defines this collection and its navigation information was used to // short-circuit relationship span rewrites, then enclose the entire rewritten expression in a // Lambda binding that brings the source Ref of the navigation operation into scope. This ref is // refered to by VariableReferenceExpressions in the original navigation expression as well as any // short-circuited relationship span columns in the rewritten expression. if (navInfo != null && navInfo.InUse) { // Create a Lambda function that binds the original navigation source expression under the variable name // used in the navigation expression and the relationship span columns, and which has its Lambda body // defined by the rewritten collection expression. var formals = new List <DbVariableReferenceExpression>(1); formals.Add(navInfo.SourceVariable); var args = new List <DbExpression>(1); args.Add(navInfo.Source); result = DbExpressionBuilder.Lambda(result, formals).Invoke(args); } // Return the (possibly rewritten) collection expression. return(result); }
/// <summary> /// Visit a Member Expression. Creates a mapping of the MemberExpression to a DbPropertyExpression /// which is a reference to the table/column name that matches the MemberExpression. /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { #if (DEBUG_VISITS) System.Diagnostics.Debug.Print("VisitMember: {0}, expression.NodeType={1}, Member={2}", node, node.Expression.NodeType, node.Member); #endif var expression = base.VisitMember(node) as MemberExpression; if ((expression.Expression.NodeType == ExpressionType.Parameter) && (expression.Expression.Type.IsClass || expression.Expression.Type.IsInterface)) { // expression is a reference to a class/interface property. Need to map it to a sql parameter or look up // the existing parameter. // The class/interface is defined by expression.Expression while the property is in expression.Member. string propertyName; if (IsNullableType(expression.Member.ReflectedType)) { var subExpression = expression.Expression as MemberExpression; propertyName = subExpression.Member.Name; } else { propertyName = expression.Member.Name; } // TODO: To support different class/interfaces, can we figure out the correct binding from what's in expression.Expression? var edmType = _Binding.VariableType.EdmType as EntityType; DbPropertyExpression propertyExpression; if (!_Properties.TryGetValue(propertyName, out propertyExpression)) { // Not created yet // Need to map through the EdmType properties to find the actual database/cspace name for the entity property. // It may be different from the entity property! EdmMember edmProp; if (_DataSpace == DataSpace.CSpace) { edmProp = edmType.Members.FirstOrDefault(m => m.Name == propertyName); } else { edmProp = edmType.Properties.Where(p => p.MetadataProperties.Any(m => m.Name == "PreferredName" && m.Value.Equals(propertyName))).FirstOrDefault(); } if (edmProp == null) { // If using SSpace: Accessing properties outside the main entity is not supported and will cause this exception. // If using CSpace: Navigation properties are handled (and will be found above) throw new ApplicationException(string.Format("Property {0} not found in Entity Type {1}", propertyName, expression.Expression.Type.Name)); } // If edmProp is a navigation property (only available when using CSpace), EF will automatically add the join to it when it sees we are referencing this property. propertyExpression = DbExpressionBuilder.Property(DbExpressionBuilder.Variable(_Binding.VariableType, _Binding.VariableName), edmProp.Name); _Properties.Add(propertyName, propertyExpression); #if (DEBUG_VISITS) System.Diagnostics.Debug.Print("Created new property expression for {0}", propertyName); #endif } // Nothing else to do here MapExpressionToDbExpression(expression, propertyExpression); return(expression); } // We are accessing a member property such that expression.Expression is the object and expression.Member is the property. // And the property is one that requires special handling. Regular class properties are all handled up above. var objectExpression = GetDbExpressionForExpression(expression.Expression); DbExpression dbExpression; var isNullableType = IsNullableType(expression.Expression.Type); int? dummy; if (isNullableType && (expression.Member.Name == nameof(dummy.HasValue))) { // Map HasValue to !IsNull dbExpression = DbExpressionBuilder.Not(DbExpressionBuilder.IsNull(objectExpression)); } else if (isNullableType && (expression.Member.Name == nameof(dummy.Value))) { // This is a nullable Value accessor so just map to the object itself and it will be mapped for us dbExpression = objectExpression; } else { if (_DataSpace == DataSpace.CSpace) { // When using CSpace, we can map a property to the class member and EF will figure out the relationship for us. dbExpression = DbExpressionBuilder.Property(objectExpression, expression.Member.Name); } else { // When using SSpace, we cannot access class members throw new ApplicationException(string.Format("Unhandled property accessor in expression: {0}", expression)); } } MapExpressionToDbExpression(expression, dbExpression); return(expression); }
protected virtual IEnumerable <MigrationStatement> Generate(HistoryOperation operation) { foreach (var commandTree in operation.CommandTrees) { List <DbParameter> _; switch (commandTree.CommandTreeKind) { case DbCommandTreeKind.Insert: const int MigrationIdColumn = 0; const int ContextKeyColumn = 1; const int ModelColumn = 2; const int VersionColumn = 3; const int MaxChunkLength = 32000; var dbInsert = (DbInsertCommandTree)commandTree; var modelData = ((dbInsert.SetClauses[ModelColumn] as DbSetClause).Value as DbConstantExpression).Value as byte[]; // If model length is less than max value, stick to original version if (modelData.Length < MaxChunkLength) { using (var writer = SqlWriter()) { writer.Write(DmlSqlGenerator.GenerateInsertSql(dbInsert, out _, generateParameters: false)); yield return(Statement(writer)); } } else { // If it's bigger - we split it into chunks, as big as possible var dataChunks = modelData.Split(MaxChunkLength); // We can't change CommandTree, but we can create new one, only difference being data length using (var writer = SqlWriter()) { var setClauses = new ReadOnlyCollection <DbModificationClause>( new List <DbModificationClause> { dbInsert.SetClauses[MigrationIdColumn], dbInsert.SetClauses[ContextKeyColumn], DbExpressionBuilder.SetClause( ((DbSetClause)dbInsert.SetClauses[ModelColumn]).Property, dataChunks.ElementAt(0).ToArray() ), dbInsert.SetClauses[VersionColumn], }); var newCommandTree = new DbInsertCommandTree(dbInsert.MetadataWorkspace, commandTree.DataSpace, dbInsert.Target, setClauses, dbInsert.Returning); writer.Write(DmlSqlGenerator.GenerateInsertSql(newCommandTree, out _, generateParameters: false)); yield return(Statement(writer)); } // Now we have first Insert, let's update it with chunks of remaing data foreach (var dataChunk in dataChunks.Skip(1)) { using (var writer = SqlWriter()) { var modelProperty = (dbInsert.SetClauses[ModelColumn] as DbSetClause).Property as DbPropertyExpression; var modificationClauses = new List <DbModificationClause> { // Updating existing chunk of data with subsequent part DbExpressionBuilder.SetClause( modelProperty, // TODO: Better solution required // Best if we could use DbExpression.Concat, but it returns DbFunctionExpression, which is not supported // Here we'll get SET Model = 'data', which we can update as text later dataChunk.ToArray() ) }.AsReadOnly(); var updateCommandTree = new DbUpdateCommandTree(dbInsert.MetadataWorkspace, dbInsert.DataSpace, dbInsert.Target, // Predicate is MigrationId value DbExpressionBuilder.Equal( ((DbSetClause)dbInsert.SetClauses[MigrationIdColumn]).Property, ((DbSetClause)dbInsert.SetClauses[MigrationIdColumn]).Value), modificationClauses, dbInsert.Returning); writer.Write(DmlSqlGenerator.GenerateUpdateSql(updateCommandTree, out _, generateParameters: false)); // Since we couldn't concat before, replacing query as string // Replacing SET Model = 'data' // with SET Model = Model || 'data' // Model being first is important, since these are parts of single value var statement = writer.ToString(); var newStatement = statement.Replace($"SET \"{modelProperty.Property.Name}\" = ", $"SET \"{modelProperty.Property.Name}\" = \"{modelProperty.Property.Name}\" || "); yield return(Statement(newStatement)); } } } break; case DbCommandTreeKind.Delete: using (var writer = SqlWriter()) { writer.Write(DmlSqlGenerator.GenerateDeleteSql((DbDeleteCommandTree)commandTree, out _, generateParameters: false)); yield return(Statement(writer)); } break; } } }
private Expression MapStartsWithExpression(MethodCallExpression node) { var expression = base.VisitMethodCall(node) as MethodCallExpression; if ((expression.Arguments == null) || (expression.Arguments.Count != 1)) { throw new ApplicationException("Did not find exactly 1 Argument to StartsWith function"); } DbExpression srcExpression = GetDbExpressionForExpression(expression.Object); DbExpression dbExpression; if (expression.Arguments[0] is ConstantExpression) { var constantExpression = GetDbExpressionForExpression(expression.Arguments[0]) as DbConstantExpression; if ((constantExpression == null) || (constantExpression.Value == null)) { throw new NullReferenceException("Parameter to StartsWith cannot be null"); } dbExpression = DbExpressionBuilder.Like(srcExpression, DbExpressionBuilder.Constant(constantExpression.Value.ToString() + "%")); } else { var argExpression = GetDbExpressionForExpression(expression.Arguments[0]); // Note: Can also do this using StartsWith function on srcExpression (which avoids having to hardcode the % character). // It works but generates some crazy conditions using charindex which I don't think will use indexes as well as "like"... //dbExpression = DbExpressionBuilder.Equal(DbExpressionBuilder.True, srcExpression.StartsWith(argExpression)); dbExpression = DbExpressionBuilder.Like(srcExpression, argExpression.Concat(DbExpressionBuilder.Constant("%"))); } MapExpressionToDbExpression(expression, dbExpression); return(expression); }
protected virtual DbCommand CreateCommand(Dictionary <int, object> identifierValues) { DbModificationCommandTree modificationCommandTree = this._modificationCommandTree; if (this._inputIdentifiers != null) { Dictionary <DbSetClause, DbSetClause> clauseMappings = new Dictionary <DbSetClause, DbSetClause>(); for (int index = 0; index < this._inputIdentifiers.Count; ++index) { KeyValuePair <int, DbSetClause> inputIdentifier = this._inputIdentifiers[index]; object obj; if (identifierValues.TryGetValue(inputIdentifier.Key, out obj)) { DbSetClause dbSetClause = new DbSetClause(inputIdentifier.Value.Property, (DbExpression)DbExpressionBuilder.Constant(obj)); clauseMappings[inputIdentifier.Value] = dbSetClause; this._inputIdentifiers[index] = new KeyValuePair <int, DbSetClause>(inputIdentifier.Key, dbSetClause); } } modificationCommandTree = DynamicUpdateCommand.RebuildCommandTree(modificationCommandTree, clauseMappings); } return(this.Translator.CreateCommand(modificationCommandTree)); }
protected override Expression VisitBinary(BinaryExpression node) { #if (DEBUG_VISITS) System.Diagnostics.Debug.Print("VisitBinary: {0}", node); #endif var expression = base.VisitBinary(node) as BinaryExpression; DbExpression dbExpression; // Need special handling for comparisons against the null constant. If we don't translate these // using an "IsNull" expression, EF will convert it literally as "= null" which doesn't work in SQL Server. if (IsNullConstantExpression(expression.Right)) { dbExpression = MapNullComparison(expression.Left, expression.NodeType); } else if (IsNullConstantExpression(expression.Left)) { dbExpression = MapNullComparison(expression.Right, expression.NodeType); } else { DbExpression leftExpression = GetDbExpressionForExpression(expression.Left); DbExpression rightExpression = GetDbExpressionForExpression(expression.Right); switch (expression.NodeType) { case ExpressionType.Equal: // DbPropertyExpression = class property that has been mapped to a database column // DbParameterReferenceExpression = lambda parameter if (IsNullableExpressionOfType <DbPropertyExpression>(leftExpression) && IsNullableExpressionOfType <DbParameterReferenceExpression>(rightExpression)) { dbExpression = CreateEqualComparisonOfNullablePropToNullableParam(leftExpression, rightExpression); } else if (IsNullableExpressionOfType <DbPropertyExpression>(rightExpression) && IsNullableExpressionOfType <DbParameterReferenceExpression>(leftExpression)) { dbExpression = CreateEqualComparisonOfNullablePropToNullableParam(rightExpression, leftExpression); } else { dbExpression = DbExpressionBuilder.Equal(leftExpression, rightExpression); } break; case ExpressionType.NotEqual: dbExpression = DbExpressionBuilder.NotEqual(leftExpression, rightExpression); break; case ExpressionType.GreaterThan: dbExpression = DbExpressionBuilder.GreaterThan(leftExpression, rightExpression); break; case ExpressionType.GreaterThanOrEqual: dbExpression = DbExpressionBuilder.GreaterThanOrEqual(leftExpression, rightExpression); break; case ExpressionType.LessThan: dbExpression = DbExpressionBuilder.LessThan(leftExpression, rightExpression); break; case ExpressionType.LessThanOrEqual: dbExpression = DbExpressionBuilder.LessThanOrEqual(leftExpression, rightExpression); break; case ExpressionType.AndAlso: dbExpression = DbExpressionBuilder.And(leftExpression, rightExpression); break; case ExpressionType.OrElse: dbExpression = DbExpressionBuilder.Or(leftExpression, rightExpression); break; case ExpressionType.And: dbExpression = EdmFunctions.BitwiseAnd(leftExpression, rightExpression); break; case ExpressionType.Or: dbExpression = EdmFunctions.BitwiseOr(leftExpression, rightExpression); break; case ExpressionType.ExclusiveOr: dbExpression = EdmFunctions.BitwiseXor(leftExpression, rightExpression); break; case ExpressionType.Coalesce: // EF does not expose the "coalesce" function. So best we can do is a case statement. Issue #77. var whenExpressions = new List <DbExpression>() { DbExpressionBuilder.IsNull(leftExpression) }; var thenExpressions = new List <DbExpression>() { rightExpression }; dbExpression = DbExpressionBuilder.Case(whenExpressions, thenExpressions, leftExpression); break; default: throw new NotImplementedException(string.Format("Unhandled NodeType of {0} in LambdaToDbExpressionVisitor.VisitBinary", expression.NodeType)); } } MapExpressionToDbExpression(expression, dbExpression); return(expression); }
// <summary> // Gets DB command definition encapsulating store logic for this command. // </summary> protected virtual DbCommand CreateCommand(Dictionary <int, object> identifierValues) { var commandTree = _modificationCommandTree; // check if any server gen identifiers need to be set if (null != _inputIdentifiers) { var modifiedClauses = new Dictionary <DbSetClause, DbSetClause>(); for (var idx = 0; idx < _inputIdentifiers.Count; idx++) { var inputIdentifier = _inputIdentifiers[idx]; object value; if (identifierValues.TryGetValue(inputIdentifier.Key, out value)) { // reset the value of the identifier var newClause = new DbSetClause(inputIdentifier.Value.Property, DbExpressionBuilder.Constant(value)); modifiedClauses[inputIdentifier.Value] = newClause; _inputIdentifiers[idx] = new KeyValuePair <int, DbSetClause>(inputIdentifier.Key, newClause); } } commandTree = RebuildCommandTree(commandTree, modifiedClauses); } return(Translator.CreateCommand(commandTree)); }
public void CreateDbCommandDefinition_converts_legacy_ProviderIncompatibleException_to_non_legacy_ProviderIncompatibleException() { var expectedException = new InvalidOperationException("Test"); try { var mockProviderServices = new Mock <SystemDataCommon.DbProviderServices>(); mockProviderServices .Protected() .Setup("CreateDbCommandDefinition", ItExpr.IsAny <SystemDataCommon.DbProviderManifest>(), ItExpr.IsAny <DbCommandTree>()) .Throws(expectedException); new LegacyDbProviderServicesWrapper(mockProviderServices.Object) .CreateCommandDefinition( new LegacyDbProviderManifestWrapper(LegacyProviderManifest), new DbQueryCommandTree(CreateMetadataWorkspace(), DataSpace.SSpace, DbExpressionBuilder.Constant(42), false)); throw new InvalidOperationException("Expected exception but none thrown."); } catch (ProviderIncompatibleException exception) { Assert.Same(expectedException, exception.InnerException); } }
// This is called for any navigation property reference so we can apply filters for those entities here. // That includes any navigation properties referenced in functions (.Where() clauses) and also any // child entities that are .Include()'d. public override DbExpression Visit(DbPropertyExpression expression) { #if DEBUG_VISITS System.Diagnostics.Debug.Print("Visit(DbPropertyExpression): EdmType.Name={0}", expression.ResultType.ModelTypeUsage.EdmType.Name); #endif var baseResult = base.Visit(expression); var basePropertyResult = baseResult as DbPropertyExpression; if (basePropertyResult == null) { return(baseResult); // base.Visit changed type! } var navProp = basePropertyResult.Property as NavigationProperty; if (navProp != null) { var targetEntityType = navProp.ToEndMember.GetEntityType(); var containers = _ObjectContext.MetadataWorkspace.GetItems <EntityContainer>(DataSpace.CSpace).First(); var filterList = FindFiltersForEntitySet(targetEntityType.MetadataProperties, false); if (filterList.Any()) { // If the expression contains a collection (i.e. the child property is an IEnumerable), we can bind directly to it. // Otherwise, we have to create a DbScanExpression over the ResultType in order to bind. if (baseResult.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType) { var binding = DbExpressionBuilder.Bind(baseResult); var newFilterExpression = BuildFilterExpressionWithDynamicFilters(filterList, binding, null); if (newFilterExpression != null) { // If not null, a new DbFilterExpression has been created with our dynamic filters. return(newFilterExpression); } } else if (baseResult.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType) { if (DoesNotSupportElementMethod(_DbContext)) { // Oracle and MySQL do not support the "newFilterExpression.Element()" method that we need to call // at the end of this block. Oracle *MAY* support it in a newer release but not sure // (see https://community.oracle.com/message/10168766#10168766). // But users may not have the option of upgrading their database so decided to try to support it. // If we find it is supported by newer versions, can detect those versions and allow the normal handling. // To apply any necessary filters to these entities, we're going to have to do it using SSpace. // These entities will be visited via the DbScan visit method so we will apply filters there. // If one of those filters then references a child property, the filter will fail. return(baseResult); } DbExpression scanExpr; var entitySet = containers.EntitySets.FirstOrDefault(e => e.ElementType.Name == baseResult.ResultType.EdmType.Name); if (entitySet == null) { if (baseResult.ResultType.EdmType.BaseType == null) { throw new ApplicationException(string.Format("EntitySet not found for {0}", baseResult.ResultType.EdmType.Name)); } // Did not find the entity set for the property but it has a base type. // This means the entity set of the property is a derived class of a TPT base class. // Find the entity set of the parent and then map it to the type we need. // Then we can bind against that expression. var currentBaseType = baseResult.ResultType.EdmType.BaseType; while (entitySet == null && currentBaseType != null) { entitySet = containers.EntitySets.FirstOrDefault(e => e.ElementType.Name == currentBaseType.Name); if (entitySet == null) { currentBaseType = currentBaseType.BaseType; } } if (entitySet == null) // hope we don't need to do this recursively... { throw new ApplicationException(string.Format("EntitySet not found for {0} or BaseType {1}", baseResult.ResultType.EdmType.Name, baseResult.ResultType.EdmType.BaseType.Name)); } var parentScanExpr = DbExpressionBuilder.Scan(entitySet); scanExpr = DbExpressionBuilder.OfType(parentScanExpr, baseResult.ResultType); } else { scanExpr = DbExpressionBuilder.Scan(entitySet); } var binding = DbExpressionBuilder.Bind(scanExpr); // Build the join conditions that are needed to join from the source object (basePropertyResult.Instance) // to the child object (the scan expression we just creating the binding for). // These conditions will be and'd with the filter conditions. var associationType = navProp.RelationshipType as AssociationType; if (associationType == null) { throw new ApplicationException(string.Format("Unable to find AssociationType on navigation property of single child property {0} in type {1}", navProp.Name, navProp.DeclaringType.FullName)); } if (associationType.Constraint == null) { // KNOWN_ISSUE: // If this happens, the model does not contain the foreign key (the "id" property). EF will automatically generate // it based on naming rules when generating the SSpace/database models but does not expose the Constraint here in the // AssociationType. In order for us to be able to generate the conditions correctly, those Foreign Keys need to be // specified on the model. To fix/handle this, we would need to examine the SSpace Association Sets (which do have // these relations!!) to try to map that information back to CSpace to figure out the correct properties of the FK conditions. // or...the models just need to contain the necessary "ID" properties for the FK relations so that they are available here // (in CSpace) for us to generate the necessary join conditions. throw new ApplicationException(string.Format("FK Constriant not found for association '{0}' - must directly specify foreign keys on model to be able to apply this filter", associationType.FullName)); } // Figure out if the "baseResults" are the from side or to side of the constraint so we can create the properties correctly // Note that this navigation property may be the same type as parent entity (so both association types // will be the same type). In that case, we need to figure out which side of the association matches the // PKs of the main entity. var fromEdmType = ((AssociationEndMember)associationType.Constraint.FromRole).GetEntityType(); var toEdmType = ((AssociationEndMember)associationType.Constraint.ToRole).GetEntityType(); bool baseResultIsFromRole; if (fromEdmType != toEdmType) { baseResultIsFromRole = (basePropertyResult.Instance.ResultType.EdmType == fromEdmType); } else { // When same, basePropertyResult is the child property and binding is the main entity. // Fixes issue #85. baseResultIsFromRole = false; } DbExpression joinCondition = null; for (int i = 0; i < associationType.Constraint.FromProperties.Count; i++) { var prop1 = DbExpressionBuilder.Property(basePropertyResult.Instance, baseResultIsFromRole ? associationType.Constraint.FromProperties[i] : associationType.Constraint.ToProperties[i]); var prop2 = DbExpressionBuilder.Property(binding.Variable, baseResultIsFromRole ? associationType.Constraint.ToProperties[i] : associationType.Constraint.FromProperties[i]); var condition = prop1.Equal(prop2) as DbExpression; joinCondition = (joinCondition == null) ? condition : joinCondition.And(condition); } // Translate the filter predicate into a DbExpression bound to the Scan expression of the target entity set. // Those conditions are then and'd with the join conditions necessary to join the target table with the source table. var newFilterExpression = BuildFilterExpressionWithDynamicFilters(filterList, binding, joinCondition); if (newFilterExpression != null) { // Converts the collection results into a single row. The expected output is a single item so EF will // then populate the results of that query into the property in the model. // The resulting SQL will be a normal "left outer join" just as it would normally be except that our // filter predicate conditions will be included with the normal join conditions. // MySQL needs this Limit() applied here or it throws an error saying: // Unable to cast object of type 'MySql.Data.Entity.SelectStatement' to type 'MySql.Data.Entity.LiteralFragment'. // But don't do that unless necessary because it produces extra "outer apply" sub queries in MS SQL. // This trick does not work for Oracle... if (_DbContext.IsMySql()) { return(newFilterExpression.Limit(DbConstantExpression.FromInt32(1)).Element()); } return(newFilterExpression.Element()); } } } } return(baseResult); }
// <summary> // Determines column/value used to set values for a row. // </summary> // <remarks> // The following columns are not included in the result: // <list> // <item>Keys in non-insert operations (keys are only set for inserts).</item> // <item>Values flagged 'preserve' (these are values the propagator claims are untouched).</item> // <item>Server generated values.</item> // </list> // </remarks> // <param name="target"> Expression binding representing the table. </param> // <param name="row"> Row containing values to set. </param> // <param name="processor"> Context for table. </param> // <param name="insertMode"> Determines whether key columns and 'preserve' columns are omitted from the list. </param> // <param name="outputIdentifiers"> Dictionary listing server generated identifiers. </param> // <param name="returning"> DbExpression describing result projection for server generated values. </param> // <param name="rowMustBeTouched"> Indicates whether the row must be touched because it produces a value (e.g. computed) </param> // <returns> Column value pairs. </returns> private IEnumerable <DbModificationClause> BuildSetClauses( DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, bool insertMode, out Dictionary <int, string> outputIdentifiers, out DbExpression returning, ref bool rowMustBeTouched) { var setClauses = new Dictionary <EdmProperty, PropagatorResult>(); var returningArguments = new List <KeyValuePair <string, DbExpression> >(); outputIdentifiers = new Dictionary <int, string>(); // Determine which flags indicate a property should be omitted from the set list. var omitMask = insertMode ? PropagatorFlags.NoFlags : PropagatorFlags.Preserve | PropagatorFlags.Unknown; for (var propertyOrdinal = 0; propertyOrdinal < processor.Table.ElementType.Properties.Count; propertyOrdinal++) { var property = processor.Table.ElementType.Properties[propertyOrdinal]; // Type members and result values are ordinally aligned var propertyResult = row.GetMemberValue(propertyOrdinal); if (PropagatorResult.NullIdentifier != propertyResult.Identifier) { // retrieve principal value propertyResult = propertyResult.ReplicateResultWithNewValue( m_translator.KeyManager.GetPrincipalValue(propertyResult)); } var omitFromSetList = false; Debug.Assert(propertyResult.IsSimple); // Determine if this is a key value var isKey = false; for (var i = 0; i < processor.KeyOrdinals.Length; i++) { if (processor.KeyOrdinals[i] == propertyOrdinal) { isKey = true; break; } } // check if this value should be omitted var flags = PropagatorFlags.NoFlags; if (!insertMode && isKey) { // Keys are only set for inserts omitFromSetList = true; } else { // See if this value has been marked up with some context. If so, add the flag information // from the markup. Markup includes information about whether the property is a concurrency value, // whether it is known (it may be a property that is preserved across an update for instance) flags |= propertyResult.PropagatorFlags; } // Determine if this value is server-generated var genPattern = MetadataHelper.GetStoreGeneratedPattern(property); var isServerGen = genPattern == StoreGeneratedPattern.Computed || (insertMode && genPattern == StoreGeneratedPattern.Identity); if (isServerGen) { var propertyExpression = target.Variable.Property(property); returningArguments.Add(new KeyValuePair <string, DbExpression>(property.Name, propertyExpression)); // check if this is a server generated identifier var identifier = propertyResult.Identifier; if (PropagatorResult.NullIdentifier != identifier) { if (m_translator.KeyManager.HasPrincipals(identifier)) { throw new InvalidOperationException(Strings.Update_GeneratedDependent(property.Name)); } outputIdentifiers.Add(identifier, property.Name); // If this property maps an identifier (in the update pipeline) it may // also be a store key. If so, the pattern had better be "Identity" // since otherwise we're dealing with a mutable key. if (genPattern != StoreGeneratedPattern.Identity && processor.IsKeyProperty(propertyOrdinal)) { throw new NotSupportedException( Strings.Update_NotSupportedComputedKeyColumn( EdmProviderManifest.StoreGeneratedPatternFacetName, XmlConstants.Computed, XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } } } if (PropagatorFlags.NoFlags != (flags & (omitMask))) { // column value matches "omit" pattern, therefore should not be set omitFromSetList = true; } else if (isServerGen) { // column value does not match "omit" pattern, but it is server generated // so it cannot be set omitFromSetList = true; // if the row has a modified value overridden by server gen, // it must still be touched in order to retrieve the value rowMustBeTouched = true; } // make the user is not updating an identity value if (!omitFromSetList && !insertMode && genPattern == StoreGeneratedPattern.Identity) { //throw the error only if the value actually changed Debug.Assert(originalRow != null, "Updated records should have a original row"); var originalPropertyResult = originalRow.GetMemberValue(propertyOrdinal); Debug.Assert(originalPropertyResult.IsSimple, "Server Gen property that is not primitive?"); Debug.Assert(propertyResult.IsSimple, "Server Gen property that is not primitive?"); if (!ByValueEqualityComparer.Default.Equals(originalPropertyResult.GetSimpleValue(), propertyResult.GetSimpleValue())) { throw new InvalidOperationException( Strings.Update_ModifyingIdentityColumn( XmlConstants.Identity, property.Name, property.DeclaringType.FullName)); } else { omitFromSetList = true; } } if (!omitFromSetList) { setClauses.Add(property, propertyResult); } } // Construct returning projection if (0 < returningArguments.Count) { returning = DbExpressionBuilder.NewRow(returningArguments); } else { returning = null; } // Construct clauses corresponding to the set clauses var result = new List <DbModificationClause>(setClauses.Count); foreach (var setClause in setClauses) { result.Add( new DbSetClause( GeneratePropertyExpression(target, setClause.Key), GenerateValueExpression(setClause.Key, setClause.Value))); } return(result); }
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) { if (interceptionContext.Result.DataSpace == DataSpace.SSpace && interceptionContext.DbContexts.All(con => con.GetType().GetCustomAttribute <TrackDisabledAttribute>() == null)) { #region Query var queryCommand = interceptionContext.Result as DbQueryCommandTree; if (queryCommand != null) { var newQuery = queryCommand.Query.Accept(new TrackingQueryVisitor()); interceptionContext.Result = new DbQueryCommandTree( queryCommand.MetadataWorkspace, queryCommand.DataSpace, newQuery); } #endregion #region Delete var deleteCommand = interceptionContext.Result as DbDeleteCommandTree; if (deleteCommand != null) { var bitColumn = ColumnAnnotationAttribute.GetColumnName <TrackDeletedBitAttribute>(deleteCommand.Target.VariableType.EdmType); var dateColumn = ColumnAnnotationAttribute.GetColumnName <TrackDeletedDateAttribute>(deleteCommand.Target.VariableType.EdmType); var identityColumn = ColumnAnnotationAttribute.GetColumnName <TrackDeletedIdentityAttribute>(deleteCommand.Target.VariableType.EdmType); var clauses = new List <DbModificationClause>(); if (bitColumn != null) { clauses.Add(DbExpressionBuilder.SetClause(deleteCommand.Target.Variable.Property(bitColumn), DbExpression.FromBoolean(true))); if (dateColumn != null) { clauses.Add(DbExpressionBuilder.SetClause(deleteCommand.Target.Variable.Property(dateColumn), DbExpression.FromDateTimeOffset(TimeZoneInfo.ConvertTime(timeProvider.NowOffsetted().Add(_dateTimeAdjustment), getCurrentTimeZoneFunction())))); } if (identityColumn != null) { clauses.Add(DbExpressionBuilder.SetClause(deleteCommand.Target.Variable.Property(identityColumn), DbExpression.FromString(getCurrentIdentityFunction()))); } } if (clauses.Count > 0) { //Add this constant clause to trace deletions on update command clauses.Add(DbExpressionBuilder.SetClause(DbExpressionBuilder.Constant(137), DbExpressionBuilder.Constant(137))); interceptionContext.Result = new DbUpdateCommandTree( deleteCommand.MetadataWorkspace, deleteCommand.DataSpace, deleteCommand.Target, deleteCommand.Predicate, clauses.AsReadOnly(), null); } } #endregion #region Insert var insertCommand = interceptionContext.Result as DbInsertCommandTree; if (insertCommand != null) { var dateColumn = ColumnAnnotationAttribute.GetColumnName <TrackCreatedDateAttribute>(insertCommand.Target.VariableType.EdmType); var identityColumn = ColumnAnnotationAttribute.GetColumnName <TrackCreatedIdentityAttribute>(insertCommand.Target.VariableType.EdmType); var clauses = insertCommand.SetClauses.ToList(); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedBitAttribute>(insertCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedDateAttribute>(insertCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedIdentityAttribute>(insertCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackModifiedDateAttribute>(insertCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackModifiedIdentityAttribute>(insertCommand.Target.VariableType.EdmType)); if (dateColumn != null) { AddOrReplaceClause(clauses, DbExpressionBuilder.SetClause(insertCommand.Target.Variable.Property(dateColumn), DbExpression.FromDateTimeOffset(TimeZoneInfo.ConvertTime(timeProvider.NowOffsetted().Add(_dateTimeAdjustment), getCurrentTimeZoneFunction())))); } if (identityColumn != null) { AddOrReplaceClause(clauses, DbExpressionBuilder.SetClause(insertCommand.Target.Variable.Property(identityColumn), DbExpression.FromString(getCurrentIdentityFunction()))); } if (dateColumn != null || (identityColumn != null)) { interceptionContext.Result = new DbInsertCommandTree( insertCommand.MetadataWorkspace, insertCommand.DataSpace, insertCommand.Target, clauses.AsReadOnly(), null); } } #endregion #region Update var updateCommand = interceptionContext.Result as DbUpdateCommandTree; if (updateCommand != null) { var dateColumn = ColumnAnnotationAttribute.GetColumnName <TrackModifiedDateAttribute>(updateCommand.Target.VariableType.EdmType); var identityColumn = ColumnAnnotationAttribute.GetColumnName <TrackModifiedIdentityAttribute>(updateCommand.Target.VariableType.EdmType); var clauses = updateCommand.SetClauses.ToList(); var traceDeleteionsClause = clauses.FirstOrDefault(c => ((DbSetClause)c).Property is DbConstantExpression && ((DbConstantExpression)((DbSetClause)c).Property).Value is int && ((int)((DbConstantExpression)((DbSetClause)c).Property).Value) == 137 && ((DbSetClause)c).Value is DbConstantExpression && ((DbConstantExpression)((DbSetClause)c).Value).Value is int && ((int)((DbConstantExpression)((DbSetClause)c).Value).Value) == 137 ); if (traceDeleteionsClause != null) { //This command is derived from a deletion operation clauses.Remove(traceDeleteionsClause); } else { RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedBitAttribute>(updateCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedDateAttribute>(updateCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackDeletedIdentityAttribute>(updateCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackCreatedDateAttribute>(updateCommand.Target.VariableType.EdmType)); RemoveClause(clauses, ColumnAnnotationAttribute.GetColumnName <TrackCreatedIdentityAttribute>(updateCommand.Target.VariableType.EdmType)); if (dateColumn != null) { AddOrReplaceClause(clauses, DbExpressionBuilder.SetClause(updateCommand.Target.Variable.Property(dateColumn), DbExpression.FromDateTimeOffset(TimeZoneInfo.ConvertTime(timeProvider.NowOffsetted().Add(_dateTimeAdjustment), getCurrentTimeZoneFunction())))); } if (identityColumn != null) { AddOrReplaceClause(clauses, DbExpressionBuilder.SetClause(updateCommand.Target.Variable.Property(identityColumn), DbExpression.FromString(getCurrentIdentityFunction()))); } } if (dateColumn != null || (identityColumn != null)) { interceptionContext.Result = new DbUpdateCommandTree( updateCommand.MetadataWorkspace, updateCommand.DataSpace, updateCommand.Target, updateCommand.Predicate, clauses.AsReadOnly(), null); } } #endregion } }