コード例 #1
0
ファイル: BaseDmlNode.cs プロジェクト: MarkMpn/Sql4Cds
        /// <summary>
        /// Compiles methods to access the data required for the DML operation
        /// </summary>
        /// <param name="mappings">The mappings of attribute name to source column</param>
        /// <param name="schema">The schema of data source</param>
        /// <param name="attributes">The attributes in the target metadata</param>
        /// <param name="dateTimeKind">The time zone that datetime values are supplied in</param>
        /// <returns></returns>
        protected Dictionary <string, Func <Entity, object> > CompileColumnMappings(EntityMetadata metadata, IDictionary <string, string> mappings, INodeSchema schema, IDictionary <string, AttributeMetadata> attributes, DateTimeKind dateTimeKind)
        {
            var attributeAccessors = new Dictionary <string, Func <Entity, object> >();
            var entityParam        = Expression.Parameter(typeof(Entity));

            foreach (var mapping in mappings)
            {
                var sourceColumnName  = mapping.Value;
                var destAttributeName = mapping.Key;

                if (!schema.ContainsColumn(sourceColumnName, out sourceColumnName))
                {
                    throw new QueryExecutionException($"Missing source column {mapping.Value}")
                          {
                              Node = this
                          }
                }
                ;

                // We might be using a virtual ___type attribute that has a different name in the metadata. We can safely
                // ignore these attributes - the attribute names have already been validated in the ExecutionPlanBuilder
                if (!attributes.TryGetValue(destAttributeName, out var attr) || attr.AttributeOf != null)
                {
                    continue;
                }

                var sourceType  = schema.Schema[sourceColumnName];
                var destType    = attr.GetAttributeType();
                var destSqlType = SqlTypeConverter.NetToSqlType(destType);

                var expr         = (Expression)Expression.Property(entityParam, typeof(Entity).GetCustomAttribute <DefaultMemberAttribute>().MemberName, Expression.Constant(sourceColumnName));
                var originalExpr = expr;

                if (sourceType == typeof(object))
                {
                    // null literal
                    expr = Expression.Constant(null, destType);
                    expr = Expr.Box(expr);
                }
                else
                {
                    expr = SqlTypeConverter.Convert(expr, sourceType);
                    expr = SqlTypeConverter.Convert(expr, destSqlType);
                    var convertedExpr = SqlTypeConverter.Convert(expr, destType);

                    if (attr is LookupAttributeMetadata lookupAttr && lookupAttr.AttributeType != AttributeTypeCode.PartyList)
                    {
                        // Special case: intersect attributes can be simple guids
                        if (metadata.IsIntersect != true)
                        {
                            if (sourceType == typeof(SqlEntityReference))
                            {
                                expr          = SqlTypeConverter.Convert(originalExpr, sourceType);
                                convertedExpr = SqlTypeConverter.Convert(expr, typeof(EntityReference));
                            }
                            else
                            {
                                Expression targetExpr;

                                if (lookupAttr.Targets.Length == 1)
                                {
                                    targetExpr = Expression.Constant(lookupAttr.Targets[0]);
                                }
                                else
                                {
                                    var sourceTargetColumnName = mappings[destAttributeName + "type"];
                                    var sourceTargetType       = schema.Schema[sourceTargetColumnName];
                                    targetExpr = Expression.Property(entityParam, typeof(Entity).GetCustomAttribute <DefaultMemberAttribute>().MemberName, Expression.Constant(sourceTargetColumnName));
                                    targetExpr = SqlTypeConverter.Convert(targetExpr, sourceTargetType);
                                    targetExpr = SqlTypeConverter.Convert(targetExpr, typeof(SqlString));
                                    targetExpr = SqlTypeConverter.Convert(targetExpr, typeof(string));
                                }

                                convertedExpr = Expression.New(
                                    typeof(EntityReference).GetConstructor(new[] { typeof(string), typeof(Guid) }),
                                    targetExpr,
                                    Expression.Convert(convertedExpr, typeof(Guid))
                                    );
                            }

                            destType = typeof(EntityReference);
                        }
                    }
                    else if (attr is EnumAttributeMetadata && !(attr is MultiSelectPicklistAttributeMetadata))
                    {
                        convertedExpr = Expression.New(
                            typeof(OptionSetValue).GetConstructor(new[] { typeof(int) }),
                            Expression.Convert(convertedExpr, typeof(int))
                            );
                        destType = typeof(OptionSetValue);
                    }
                    else if (attr is MoneyAttributeMetadata)
                    {
                        convertedExpr = Expression.New(
                            typeof(Money).GetConstructor(new[] { typeof(decimal) }),
                            Expression.Convert(expr, typeof(decimal))
                            );
                        destType = typeof(Money);
                    }
                    else if (attr is DateTimeAttributeMetadata)
                    {
                        convertedExpr = Expression.Convert(
                            Expr.Call(() => DateTime.SpecifyKind(Expr.Arg <DateTime>(), Expr.Arg <DateTimeKind>()),
                                      expr,
                                      Expression.Constant(dateTimeKind)
                                      ),
                            typeof(DateTime?)
                            );
                    }

                    // Check for null on the value BEFORE converting from the SQL to BCL type to avoid e.g. SqlDateTime.Null being converted to 1900-01-01
                    expr = Expression.Condition(
                        SqlTypeConverter.NullCheck(expr),
                        Expression.Constant(null, destType),
                        convertedExpr);

                    if (expr.Type.IsValueType)
                    {
                        expr = SqlTypeConverter.Convert(expr, typeof(object));
                    }
                }

                attributeAccessors[destAttributeName] = Expression.Lambda <Func <Entity, object> >(expr, entityParam).Compile();
            }