protected override Expression VisitConstant(ConstantExpression node) { #if (DEBUGPRINT) 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) { 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); }
// 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(); string entityName = targetEntityType.Name; var containers = _ObjectContext.MetadataWorkspace.GetItems <EntityContainer>(DataSpace.CSpace).First(); var filterList = FindFiltersForEntitySet(targetEntityType.MetadataProperties, containers); 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(entityName, 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); } var entitySet = containers.EntitySets.FirstOrDefault(e => e.ElementType.Name == baseResult.ResultType.EdmType.Name); if (entitySet == null) { #if (DEBUG) throw new ApplicationException(string.Format("EntitySet not found for {0} - this is a known issue when using TPT", baseResult.ResultType.EdmType.Name)); #else return(baseResult); #endif } var 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 bool baseResultIsFromRole = (basePropertyResult.Instance.ResultType.EdmType == ((AssociationEndMember)associationType.Constraint.FromRole).GetEntityType()); 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(entityName, 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); }