/// <summary> /// Map domain instance /// </summary> public object MapDomainInstance(Type tDomain, Type tModel, object domainInstance, bool useCache = true, HashSet <Guid> keyStack = null) { ClassMap classMap = this.m_mapFile.GetModelClassMap(tModel, tDomain); if (domainInstance == null) { return(null); } else { var cType = tModel; while (cType != null && classMap == null || !tDomain.IsAssignableFrom(Type.GetType(classMap.DomainClass))) { cType = cType.BaseType; if (cType != null) { classMap = this.m_mapFile.GetModelClassMap(cType); } } // work up the tree } // Now the property maps object retVal = Activator.CreateInstance(tModel); // Key? if (classMap == null) { return(retVal); } // Cache lookup var idEnt = retVal as IIdentifiedEntity; var vidEnt = retVal as IVersionedEntity; PropertyMap iKeyMap = null; if (idEnt != null) { classMap.TryGetModelProperty("Key", out iKeyMap); } if (iKeyMap != null) { object keyValue = tDomain.GetRuntimeProperty(iKeyMap.DomainName).GetValue(domainInstance); while (iKeyMap.Via != null) { keyValue = keyValue.GetType().GetRuntimeProperty(iKeyMap.Via.DomainName).GetValue(keyValue); iKeyMap = iKeyMap.Via; } if (keyValue is byte[]) { keyValue = new Guid(keyValue as byte[]); } // Set key vaue idEnt.Key = (Guid)keyValue; var cache = FireMappingToModel(this, (Guid)keyValue, retVal as IdentifiedData); if (cache != null && useCache) { return(cache); } } // Classifier value String classifierValue = null; String classPropertyName = String.Empty; if (!m_domainClassPropertyName.TryGetValue(tModel, out classPropertyName)) { classPropertyName = tModel.GetCustomAttribute <ClassifierAttribute>()?.ClassifierProperty; lock (m_domainClassPropertyName) if (!m_domainClassPropertyName.ContainsKey(tModel)) { m_domainClassPropertyName.Add(tModel, classPropertyName); } } if (classPropertyName != null) { // Key value classPropertyName = tModel.GetRuntimeProperty(classPropertyName)?.GetCustomAttribute <SerializationReferenceAttribute>()?.RedirectProperty ?? classPropertyName; if (classMap.TryGetModelProperty(classPropertyName ?? "____XXX", out iKeyMap)) { object keyValue = tDomain.GetRuntimeProperty(iKeyMap.DomainName).GetValue(domainInstance); while (iKeyMap.Via != null) { keyValue = keyValue.GetType().GetRuntimeProperty(iKeyMap.Via.DomainName).GetValue(keyValue); iKeyMap = iKeyMap.Via; } classifierValue = keyValue?.ToString(); } } // Are we currently processing this? if (idEnt != null) { if (keyStack == null) { keyStack = new HashSet <Guid>(); } if (idEnt.Key.HasValue) { if (keyStack.Contains(idEnt.Key.Value)) { return(null); } else { keyStack.Add(idEnt.Key.Value); } } } // Properties // Properties PropertyInfo[] properties = null; Dictionary <String, PropertyInfo[]> propertyClassMap = null; if (!s_modelPropertyCache.TryGetValue(tModel, out propertyClassMap)) { lock (s_modelPropertyCache) { if (!s_modelPropertyCache.TryGetValue(tModel, out propertyClassMap)) { propertyClassMap = new Dictionary <string, PropertyInfo[]>(); } if (!s_modelPropertyCache.ContainsKey(tModel)) { s_modelPropertyCache.Add(tModel, propertyClassMap); } } } if (!propertyClassMap.TryGetValue(classifierValue ?? String.Empty, out properties)) { lock (s_modelPropertyCache) { properties = tModel.GetRuntimeProperties().Where(m => m != null && m.GetCustomAttribute <DataIgnoreAttribute>() == null && (primitives.Contains(m.PropertyType) || m.PropertyType.IsEnum || m.GetCustomAttributes <AutoLoadAttribute>().Any(o => o.ClassCode == classifierValue || o.ClassCode == null)) && m.CanWrite).ToArray(); if (!propertyClassMap.ContainsKey(classifierValue ?? String.Empty)) { propertyClassMap.Add(classifierValue ?? String.Empty, properties); } } } // Iterate the properties and map foreach (var modelPropertyInfo in properties) { // Map property PropertyMap propMap = null; classMap.TryGetModelProperty(modelPropertyInfo.Name, out propMap); if (propMap?.DontLoad == true) { continue; } var propInfo = tDomain.GetRuntimeProperty(propMap?.DomainName ?? modelPropertyInfo.Name); if (propInfo == null) { #if VERBOSE_DEBUG Debug.WriteLine("Unmapped property ({0}[{1}]).{2}", typeof(TDomain).Name, idEnt.Key, modelPropertyInfo.Name); #endif continue; } var originalValue = propInfo.GetValue(domainInstance); #if VERBOSE_DEBUG Debug.WriteLine("Value property ({0}[{1}]).{2} = {3}", typeof(TDomain).Name, idEnt.Key, modelPropertyInfo.Name, originalValue); #endif // Property info try { if (originalValue == null) { continue; } } catch (Exception e) // HACK: For some reason, some LINQ providers will return NULL on EntityReferences with no value { Debug.WriteLine(e.ToString()); } // Traversal stuff PropertyInfo modelProperty = propMap == null ? modelPropertyInfo : tModel.GetRuntimeProperty(propMap.ModelName);; object sourceObject = domainInstance; PropertyInfo sourceProperty = propInfo; // Go through the via elements in the object map. This code traces a path // through the domain class instantiating any necessary associative entity // Example when a model entity is really two or three tables in the DB.. // This piece of code does whatever is necessary to traverse the data model, // kinda reminds me of a song: // 🎶 Ah for just one time, I would take the northwest passage // To find the hand of Franklin reaching for the Beaufort Sea. // Tracing one warm line, through a land so wide and savage // And make a northwest passage to the sea. 🎶 if (propMap != null) { var via = propMap.Via; List <PropertyMap> viaWalk = new List <PropertyMap>(); while (via?.DontLoad == false) { viaWalk.Add(via); via = via.Via; } sourceProperty = propInfo; foreach (var p in viaWalk.Select(o => o)) { if (!(sourceObject is IList)) { sourceObject = sourceProperty.GetValue(sourceObject); } sourceProperty = this.ExtractDomainType(sourceProperty.PropertyType).GetRuntimeProperty(p.DomainName); } } // validate property type if (propMap?.DontLoad == true) { continue; } #if VERBOSE_DEBUG Debug.WriteLine("Mapping property ({0}[{1}]).{2} = {3}", typeof(TDomain).Name, idEnt.Key, modelPropertyInfo.Name, originalValue); #endif // Set value object pValue = null; //DebugWriteLine("Unmapped property ({0}).{1}", typeof(TDomain).Name, propInfo.Name); if (sourceProperty.PropertyType == typeof(byte[]) && modelProperty.PropertyType.StripNullable() == typeof(Guid)) // Guid to BA { modelProperty.SetValue(retVal, new Guid((byte[])sourceProperty.GetValue(sourceObject))); } else if (modelProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType)) { modelProperty.SetValue(retVal, sourceProperty.GetValue(sourceObject)); } else if (sourceProperty.PropertyType == typeof(String) && modelProperty.PropertyType == typeof(Type)) { modelProperty.SetValue(retVal, Type.GetType(sourceProperty.GetValue(sourceObject) as String)); } else if (MapUtil.TryConvert(originalValue, modelProperty.PropertyType, out pValue)) { modelProperty.SetValue(retVal, pValue); } // Handles when a map is a list for example doing a VIA over a version relationship else if (originalValue is IList) { var modelInstance = Activator.CreateInstance(modelProperty.PropertyType) as IList; modelProperty.SetValue(retVal, modelInstance); var instanceMapFunction = typeof(ModelMapper).GetGenericMethod("MapDomainInstance", new Type[] { sourceProperty.PropertyType.GenericTypeArguments[0], modelProperty.PropertyType.GenericTypeArguments[0] }, new Type[] { sourceProperty.PropertyType.GenericTypeArguments[0], typeof(bool), typeof(HashSet <Guid>) }); foreach (var itm in originalValue as IList) { // Traverse? var instance = itm; var via = propMap?.Via; while (via != null) { instance = instance?.GetType().GetRuntimeProperty(via.DomainName)?.GetValue(instance); if (instance is IList) { var parm = Expression.Parameter(instance.GetType()); Expression aggregateExpr = parm; if (!String.IsNullOrEmpty(via.OrderBy)) { aggregateExpr = parm.Sort(via.OrderBy, via.SortOrder); } aggregateExpr = aggregateExpr.Aggregate(via.Aggregate); // Get the generic method for LIST to be widdled down instance = Expression.Lambda(aggregateExpr, parm).Compile().DynamicInvoke(instance); } via = via.Via; } modelInstance.Add(instanceMapFunction.Invoke(this, new object[] { instance, useCache, keyStack })); } } // Flat map list 1..1 else if (typeof(IList).IsAssignableFrom(modelProperty.PropertyType) && typeof(IList).IsAssignableFrom(sourceProperty.PropertyType)) { var modelInstance = Activator.CreateInstance(modelProperty.PropertyType) as IList; modelProperty.SetValue(retVal, modelInstance); var instanceMapFunction = typeof(ModelMapper).GetGenericMethod("MapDomainInstance", new Type[] { sourceProperty.PropertyType.GenericTypeArguments[0], modelProperty.PropertyType.GenericTypeArguments[0] }, new Type[] { sourceProperty.PropertyType.GenericTypeArguments[0], typeof(bool), typeof(HashSet <Guid>) }); var listValue = sourceProperty.GetValue(sourceObject); // Is this list a versioned association?? if (tDomain.GetRuntimeProperty("VersionSequenceId") != null && sourceProperty.PropertyType.GenericTypeArguments[0].GetRuntimeProperty("EffectiveVersionSequenceId") != null) // Filter!!! Yay! { var parm = Expression.Parameter(listValue.GetType()); Expression aggregateExpr = null; aggregateExpr = parm.IsActive(domainInstance); listValue = Expression.Lambda(aggregateExpr, parm).Compile().DynamicInvoke(listValue); } foreach (var itm in listValue as IEnumerable) { modelInstance.Add(instanceMapFunction.Invoke(this, new object[] { itm, useCache, keyStack })); } } else if (m_mapFile.GetModelClassMap(modelProperty.PropertyType) != null) { // TODO: Clean this up var instance = originalValue; //sourceProperty.GetValue(sourceObject); var via = propMap?.Via; while (via != null) { instance = instance?.GetType().GetRuntimeProperty(via.DomainName)?.GetValue(instance); if (instance is IList) { var parm = Expression.Parameter(instance.GetType()); Expression aggregateExpr = parm; if (!String.IsNullOrEmpty(via.OrderBy)) { aggregateExpr = parm.Sort(via.OrderBy, via.SortOrder); } aggregateExpr = aggregateExpr.Aggregate(via.Aggregate); // Get the generic method for LIST to be widdled down instance = Expression.Lambda(aggregateExpr, parm).Compile().DynamicInvoke(instance); } via = via.Via; } if (instance != null) { var instanceMapFunction = typeof(ModelMapper).GetGenericMethod("MapDomainInstance", new Type[] { instance?.GetType(), modelProperty.PropertyType }, new Type[] { instance?.GetType(), typeof(bool), typeof(HashSet <Guid>) }); modelProperty.SetValue(retVal, instanceMapFunction.Invoke(this, new object[] { instance, useCache, keyStack })); } } } #if VERBOSE_DEBUG Debug.WriteLine("Leaving: {0}>{1}", typeof(TDomain).FullName, typeof(TModel).FullName); #endif if (idEnt != null && useCache) { keyStack.Remove(idEnt.Key.Value); FireMappedToModel(this, vidEnt?.VersionKey ?? idEnt?.Key ?? Guid.Empty, retVal as IdentifiedData); } // (retVal as IdentifiedData).SetDelayLoad(true); return(retVal); }
/// <summary> /// Map model instance /// </summary> public TDomain MapModelInstance <TModel, TDomain>(TModel modelInstance) where TDomain : new() { ClassMap classMap = this.m_mapFile.GetModelClassMap(typeof(TModel), typeof(TDomain)); if (classMap == null) { classMap = this.m_mapFile.GetModelClassMap(typeof(TModel)); } if (classMap == null || modelInstance == null) { return(default(TDomain)); } // Now the property maps TDomain retVal = new TDomain(); // Properties PropertyInfo[] properties = null; Dictionary <String, PropertyInfo[]> propertyClassMap = null; if (!s_modelPropertyCache.TryGetValue(typeof(TModel), out propertyClassMap)) { lock (s_modelPropertyCache) { if (!s_modelPropertyCache.TryGetValue(typeof(TModel), out propertyClassMap)) { propertyClassMap = new Dictionary <string, PropertyInfo[]>(); } if (!s_modelPropertyCache.ContainsKey(typeof(TModel))) { s_modelPropertyCache.Add(typeof(TModel), propertyClassMap); } } } if (!propertyClassMap.TryGetValue(String.Empty, out properties)) { lock (s_modelPropertyCache) { properties = typeof(TModel).GetRuntimeProperties().Where(m => m != null && m.GetCustomAttribute <DataIgnoreAttribute>() == null && (primitives.Contains(m.PropertyType) || m.PropertyType.IsEnum) && m.CanWrite).ToArray(); if (!propertyClassMap.ContainsKey(String.Empty)) { propertyClassMap.Add(String.Empty, properties); } } } // Iterate through properties foreach (var propInfo in properties) { var propValue = propInfo.GetValue(modelInstance); // Property info if (propValue == null) { continue; } if (!propInfo.PropertyType.IsPrimitive && propInfo.PropertyType != typeof(Guid) && (!propInfo.PropertyType.IsGenericType || propInfo.PropertyType.GetGenericTypeDefinition() != typeof(Nullable <>)) && propInfo.PropertyType != typeof(String) && propInfo.PropertyType != typeof(DateTime) && propInfo.PropertyType != typeof(DateTimeOffset) && propInfo.PropertyType != typeof(Type) && propInfo.PropertyType != typeof(Decimal) && propInfo.PropertyType != typeof(byte[]) && !propInfo.PropertyType.IsEnum) { continue; } // Map property PropertyMap propMap = null; classMap.TryGetModelProperty(propInfo.Name, out propMap); PropertyInfo domainProperty = null; Object targetObject = retVal; // Set if (propMap == null) { domainProperty = typeof(TDomain).GetRuntimeProperty(propInfo.Name); } else { domainProperty = typeof(TDomain).GetRuntimeProperty(propMap.DomainName); } object domainValue = null; // Set value if (domainProperty == null || !domainProperty.CanWrite) { continue; } //Debug.WriteLine ("Unmapped property ({0}).{1}", typeof(TModel).Name, propInfo.Name); else if (domainProperty.PropertyType == typeof(byte[]) && propInfo.PropertyType.StripNullable() == typeof(Guid)) { domainProperty.SetValue(targetObject, ((Guid)propValue).ToByteArray()); } else if ( (domainProperty.PropertyType == typeof(DateTime) || domainProperty.PropertyType == typeof(DateTime?)) && (propInfo.PropertyType == typeof(DateTimeOffset) || propInfo.PropertyType == typeof(DateTimeOffset?))) { domainProperty.SetValue(targetObject, ((DateTimeOffset)propValue).DateTime); } else if (domainProperty.PropertyType.IsAssignableFrom(propInfo.PropertyType)) { domainProperty.SetValue(targetObject, propValue); } else if (propInfo.PropertyType == typeof(Type) && domainProperty.PropertyType == typeof(String)) { domainProperty.SetValue(targetObject, (propValue as Type).AssemblyQualifiedName); } else if (MapUtil.TryConvert(propValue, domainProperty.PropertyType, out domainValue)) { domainProperty.SetValue(targetObject, domainValue); } } return(retVal); }