File: System\Data\Objects\Internal\ComplexTypeMaterializer.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="ComplexTypeMaterializer.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Diagnostics;
using System.Data.Mapping;
namespace System.Data.Objects.Internal
{
    /// <summary>
    /// Supports materialization of complex type instances from records. Used
    /// by the ObjectStateManager.
    /// </summary>
    internal class ComplexTypeMaterializer
    {
        private readonly MetadataWorkspace _workspace;
        private const int MaxPlanCount = 4;
        private Plan[] _lastPlans;
        private int _lastPlanIndex;
 
        internal ComplexTypeMaterializer(MetadataWorkspace workspace)
        {
            _workspace = workspace;
        }
 
        internal object CreateComplex(IExtendedDataRecord record, DataRecordInfo recordInfo, object result)
        {
            Debug.Assert(null != record, "null IExtendedDataRecord");
            Debug.Assert(null != recordInfo, "null DataRecordInfo");
            Debug.Assert(null != recordInfo.RecordType, "null TypeUsage");
            Debug.Assert(null != recordInfo.RecordType.EdmType, "null EdmType");
 
            Debug.Assert(Helper.IsEntityType(recordInfo.RecordType.EdmType) ||
                         Helper.IsComplexType(recordInfo.RecordType.EdmType),
                         "not EntityType or ComplexType");
 
            Plan plan = GetPlan(record, recordInfo);
            if (null == result)
            {
                result = ((Func<object>)plan.ClrType)();
            }
            SetProperties(record, result, plan.Properties);
            return result;
        }
 
        private void SetProperties(IExtendedDataRecord record, object result, PlanEdmProperty[] properties)
        {
            Debug.Assert(null != record, "null IExtendedDataRecord");
            Debug.Assert(null != result, "null object");
            Debug.Assert(null != properties, "null object");
 
            for (int i = 0; i < properties.Length; ++i)
            {
                if (null != properties[i].GetExistingComplex)
                {
                    object existing = properties[i].GetExistingComplex(result);
                    object obj = CreateComplexRecursive(record.GetValue(properties[i].Ordinal), existing);
                    if (null == existing)
                    {
                        properties[i].ClrProperty(result, obj);
                    }
                }
                else
                {
                    properties[i].ClrProperty(result,
                        ConvertDBNull(
                            record.GetValue(
                                properties[i].Ordinal)));
                }
            }
        }
 
        private static object ConvertDBNull(object value)
        {
            return ((DBNull.Value != value) ? value : null);
        }
 
        private object CreateComplexRecursive(object record, object existing)
        {
            return ((DBNull.Value != record) ? CreateComplexRecursive((IExtendedDataRecord)record, existing) : existing);
        }
 
        private object CreateComplexRecursive(IExtendedDataRecord record, object existing)
        {
            return CreateComplex(record, record.DataRecordInfo, existing);
        }
 
        private Plan GetPlan(IExtendedDataRecord record, DataRecordInfo recordInfo)
        {
            Debug.Assert(null != record, "null IExtendedDataRecord");
            Debug.Assert(null != recordInfo, "null DataRecordInfo");
            Debug.Assert(null != recordInfo.RecordType, "null TypeUsage");
 
            Plan[] plans = _lastPlans ?? (_lastPlans = new Plan[MaxPlanCount]);
 
            // find an existing plan in circular buffer
            int index = _lastPlanIndex - 1;
            for (int i = 0; i < MaxPlanCount; ++i)
            {
                index = (index + 1) % MaxPlanCount;
                if (null == plans[index])
                {
                    break;
                }
                if (plans[index].Key == recordInfo.RecordType)
                {
                    _lastPlanIndex = index;
                    return plans[index];
                }
            }
            Debug.Assert(0 <= index, "negative index");
            Debug.Assert(index != _lastPlanIndex || (null == plans[index]), "index wrapped around");
 
            // create a new plan
            ObjectTypeMapping mapping = System.Data.Common.Internal.Materialization.Util.GetObjectMapping(recordInfo.RecordType.EdmType, _workspace);
            Debug.Assert(null != mapping, "null ObjectTypeMapping");
 
            Debug.Assert(Helper.IsComplexType(recordInfo.RecordType.EdmType),
                         "IExtendedDataRecord is not ComplexType");
 
            _lastPlanIndex = index;
            plans[index] = new Plan(recordInfo.RecordType, mapping, recordInfo.FieldMetadata);
            return plans[index];
        }
 
        private sealed class Plan
        {
            internal readonly TypeUsage Key;
            internal readonly Delegate ClrType;
            internal readonly PlanEdmProperty[] Properties;
 
            internal Plan(TypeUsage key, ObjectTypeMapping mapping, System.Collections.ObjectModel.ReadOnlyCollection<FieldMetadata> fields)
            {
                Debug.Assert(null != mapping, "null ObjectTypeMapping");
                Debug.Assert(null != fields, "null FieldMetadata");
 
                Key = key;
                Debug.Assert(!Helper.IsEntityType(mapping.ClrType), "Expecting complex type");
                ClrType = LightweightCodeGenerator.GetConstructorDelegateForType((ClrComplexType)mapping.ClrType);
                Properties = new PlanEdmProperty[fields.Count];
 
                int lastOrdinal = -1;
                for (int i = 0; i < Properties.Length; ++i)
                {
                    FieldMetadata field = fields[i];
 
                    Debug.Assert(unchecked((uint)field.Ordinal) < unchecked((uint)fields.Count), "FieldMetadata.Ordinal out of range of Fields.Count");
                    Debug.Assert(lastOrdinal < field.Ordinal, "FieldMetadata.Ordinal is not increasing");
                    lastOrdinal = field.Ordinal;
 
                    Properties[i] = new PlanEdmProperty(lastOrdinal, mapping.GetPropertyMap(field.FieldType.Name).ClrProperty);
                }
            }
        }
 
        private struct PlanEdmProperty
        {
            internal readonly int Ordinal;
            internal readonly Func<object, object> GetExistingComplex;
            internal readonly Action<object, object> ClrProperty;
 
            internal PlanEdmProperty(int ordinal, EdmProperty property)
            {
                Debug.Assert(0 <= ordinal, "negative ordinal");
                Debug.Assert(null != property, "unsupported shadow state");
 
                this.Ordinal = ordinal;
                this.GetExistingComplex = Helper.IsComplexType(property.TypeUsage.EdmType)
                    ? LightweightCodeGenerator.GetGetterDelegateForProperty(property) : null;
                this.ClrProperty = LightweightCodeGenerator.GetSetterDelegateForProperty(property);
            }
        }
    }
}