536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
788 lines
38 KiB
C#
788 lines
38 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="vs.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner Microsoft
|
|
// @backupOwner Microsoft
|
|
//---------------------------------------------------------------------
|
|
|
|
namespace System.Data.Metadata.Edm
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Data.Entity;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
internal sealed class ObjectItemConventionAssemblyLoader : ObjectItemAssemblyLoader
|
|
{
|
|
// for root entities, entities with no base type, we will additionally look
|
|
// at properties on the clr base hierarchy.
|
|
private const BindingFlags RootEntityPropertyReflectionBindingFlags = PropertyReflectionBindingFlags & ~BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy;
|
|
|
|
private new MutableAssemblyCacheEntry CacheEntry { get { return (MutableAssemblyCacheEntry)base.CacheEntry; } }
|
|
private List<Action> _referenceResolutions = new List<Action>();
|
|
|
|
internal ObjectItemConventionAssemblyLoader(Assembly assembly, ObjectItemLoadingSessionData sessionData)
|
|
: base(assembly, new MutableAssemblyCacheEntry(), sessionData)
|
|
{
|
|
Debug.Assert(Create == sessionData.ObjectItemAssemblyLoaderFactory, "Why is there a different factory creating this class");
|
|
SessionData.RegisterForLevel1PostSessionProcessing(this);
|
|
}
|
|
|
|
protected override void LoadTypesFromAssembly()
|
|
{
|
|
foreach (Type type in EntityUtil.GetTypesSpecial(SourceAssembly))
|
|
{
|
|
EdmType cspaceType;
|
|
if (TryGetCSpaceTypeMatch(type, out cspaceType))
|
|
{
|
|
if (type.IsValueType && !type.IsEnum)
|
|
{
|
|
SessionData.LoadMessageLogger.LogLoadMessage(Strings.Validator_OSpace_Convention_Struct(cspaceType.FullName, type.FullName), cspaceType);
|
|
continue;
|
|
}
|
|
|
|
EdmType ospaceType;
|
|
if (TryCreateType(type, cspaceType, out ospaceType))
|
|
{
|
|
Debug.Assert(ospaceType is StructuralType || Helper.IsEnumType(ospaceType), "Only StructuralType or EnumType expected.");
|
|
|
|
CacheEntry.TypesInAssembly.Add(ospaceType);
|
|
// check for duplicates so we don't cause an ArgumentException,
|
|
// Mapping will do the actual error for the duplicate type later
|
|
if (!SessionData.CspaceToOspace.ContainsKey(cspaceType))
|
|
{
|
|
SessionData.CspaceToOspace.Add(cspaceType, ospaceType);
|
|
}
|
|
else
|
|
{
|
|
// at this point there is already a Clr Type that is structurally matched to this CSpace type, we throw exception
|
|
EdmType previousOSpaceType = SessionData.CspaceToOspace[cspaceType];
|
|
SessionData.EdmItemErrors.Add(
|
|
new EdmItemError(Strings.Validator_OSpace_Convention_AmbiguousClrType(cspaceType.Name, previousOSpaceType.ClrType.FullName, type.FullName), previousOSpaceType));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SessionData.TypesInLoading.Count == 0)
|
|
{
|
|
Debug.Assert(CacheEntry.ClosureAssemblies.Count == 0, "How did we get closure assemblies?");
|
|
|
|
// since we didn't find any types, don't lock into convention based
|
|
SessionData.ObjectItemAssemblyLoaderFactory = null;
|
|
}
|
|
}
|
|
|
|
|
|
protected override void AddToAssembliesLoaded()
|
|
{
|
|
SessionData.AssembliesLoaded.Add(SourceAssembly, CacheEntry);
|
|
}
|
|
|
|
private bool TryGetCSpaceTypeMatch(Type type, out EdmType cspaceType)
|
|
{
|
|
// brute force try and find a matching name
|
|
KeyValuePair<EdmType, int> pair;
|
|
if (SessionData.ConventionCSpaceTypeNames.TryGetValue(type.Name, out pair))
|
|
{
|
|
if (pair.Value == 1)
|
|
{
|
|
// we found a type match
|
|
cspaceType = pair.Key;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(pair.Value > 1, "how did we get a negative count of types in the dictionary?");
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_MultipleTypesWithSameName(type.Name), pair.Key));
|
|
}
|
|
}
|
|
|
|
cspaceType = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a structural or enum OSpace type based on CLR type and CSpace type.
|
|
/// </summary>
|
|
/// <param name="type">CLR type.</param>
|
|
/// <param name="cspaceType">CSpace Type</param>
|
|
/// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
|
|
/// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
|
|
private bool TryCreateType(Type type, EdmType cspaceType, out EdmType newOSpaceType)
|
|
{
|
|
Debug.Assert(type != null, "type != null");
|
|
Debug.Assert(cspaceType != null, "cspaceType != null");
|
|
Debug.Assert(cspaceType is StructuralType || Helper.IsEnumType(cspaceType), "Structural or enum type expected");
|
|
|
|
newOSpaceType = null;
|
|
|
|
// if one of the types is an enum while the other is not there is no match
|
|
if (Helper.IsEnumType(cspaceType) ^ type.IsEnum)
|
|
{
|
|
SessionData.LoadMessageLogger.LogLoadMessage(
|
|
Strings.Validator_OSpace_Convention_SSpaceOSpaceTypeMismatch(cspaceType.FullName, cspaceType.FullName),
|
|
cspaceType);
|
|
return false;
|
|
}
|
|
|
|
if(Helper.IsEnumType(cspaceType))
|
|
{
|
|
return TryCreateEnumType(type, (EnumType)cspaceType, out newOSpaceType);
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(cspaceType is StructuralType);
|
|
return TryCreateStructuralType(type, (StructuralType)cspaceType, out newOSpaceType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a structural OSpace type based on CLR type and CSpace type.
|
|
/// </summary>
|
|
/// <param name="type">CLR type.</param>
|
|
/// <param name="cspaceType">CSpace Type</param>
|
|
/// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
|
|
/// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
|
|
private bool TryCreateStructuralType(Type type, StructuralType cspaceType, out EdmType newOSpaceType)
|
|
{
|
|
Debug.Assert(type != null, "type != null");
|
|
Debug.Assert(cspaceType != null, "cspaceType != null");
|
|
|
|
List<Action> referenceResolutionListForCurrentType = new List<Action>();
|
|
newOSpaceType = null;
|
|
Debug.Assert(TypesMatchByConvention(type, cspaceType), "The types passed as parameters don't match by convention.");
|
|
|
|
StructuralType ospaceType;
|
|
if (Helper.IsEntityType(cspaceType))
|
|
{
|
|
ospaceType = new ClrEntityType(type, cspaceType.NamespaceName, cspaceType.Name);
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(Helper.IsComplexType(cspaceType), "Invalid type attribute encountered");
|
|
ospaceType = new ClrComplexType(type, cspaceType.NamespaceName, cspaceType.Name);
|
|
}
|
|
|
|
if (cspaceType.BaseType != null)
|
|
{
|
|
if (TypesMatchByConvention(type.BaseType, cspaceType.BaseType))
|
|
{
|
|
TrackClosure(type.BaseType);
|
|
referenceResolutionListForCurrentType.Add(
|
|
() => ospaceType.BaseType = ResolveBaseType((StructuralType)cspaceType.BaseType, type));
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_BaseTypeIncompatible(type.BaseType.FullName, type.FullName, cspaceType.BaseType.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Load the properties for this type
|
|
if (!TryCreateMembers(type, (StructuralType)cspaceType, ospaceType, referenceResolutionListForCurrentType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Add this to the known type map so we won't try to load it again
|
|
SessionData.TypesInLoading.Add(type.FullName, ospaceType);
|
|
|
|
// we only add the referenceResolution to the list unless we structrually matched this type
|
|
foreach (var referenceResolution in referenceResolutionListForCurrentType)
|
|
{
|
|
this._referenceResolutions.Add(referenceResolution);
|
|
}
|
|
|
|
newOSpaceType = ospaceType;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates new enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/>
|
|
/// </summary>
|
|
/// <param name="enumType">CLR type to create OSpace type from.</param>
|
|
/// <param name="cspaceEnumType">CSpace type used to get namespace and name for the newly created OSpace type.</param>
|
|
/// <param name="newOSpaceType">
|
|
/// New enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/> or null
|
|
/// if the type could not be built.
|
|
/// </param>
|
|
/// <returns><c>true</c> if the type was built successfully.<c>false</c> otherwise.</returns>
|
|
private bool TryCreateEnumType(Type enumType, EnumType cspaceEnumType, out EdmType newOSpaceType)
|
|
{
|
|
Debug.Assert(enumType != null, "enumType != null");
|
|
Debug.Assert(enumType.IsEnum, "enum type expected");
|
|
Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
|
|
Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
|
|
Debug.Assert(TypesMatchByConvention(enumType, cspaceEnumType), "The types passed as parameters don't match by convention.");
|
|
|
|
newOSpaceType = null;
|
|
|
|
// Check if the OSpace and CSpace enum type match
|
|
if (!UnderlyingEnumTypesMatch(enumType, cspaceEnumType) || !EnumMembersMatch(enumType, cspaceEnumType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
newOSpaceType = new ClrEnumType(enumType, cspaceEnumType.NamespaceName, cspaceEnumType.Name);
|
|
SessionData.TypesInLoading.Add(enumType.FullName, newOSpaceType);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies whether underlying types of CLR and EDM types match
|
|
/// </summary>
|
|
/// <param name="enumType">OSpace CLR enum type.</param>
|
|
/// <param name="cspaceEnumType">CSpace EDM enum type.</param>
|
|
/// <returns><c>true</c> if types match. <c>false</c> otherwise.</returns>
|
|
private bool UnderlyingEnumTypesMatch(Type enumType, EnumType cspaceEnumType)
|
|
{
|
|
Debug.Assert(enumType != null, "enumType != null");
|
|
Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
|
|
Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
|
|
Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
|
|
|
|
// Note that TryGetPrimitiveType() will return false not only for types that are not primitive
|
|
// but also for CLR primitive types that are valid underlying enum types in CLR but are not
|
|
// a valid Edm primitive types (e.g. ulong)
|
|
PrimitiveType underlyingEnumType;
|
|
if (!ClrProviderManifest.Instance.TryGetPrimitiveType(enumType.GetEnumUnderlyingType(), out underlyingEnumType))
|
|
{
|
|
SessionData.LoadMessageLogger.LogLoadMessage(
|
|
Strings.Validator_UnsupportedEnumUnderlyingType(enumType.GetEnumUnderlyingType().FullName),
|
|
cspaceEnumType);
|
|
|
|
return false;
|
|
}
|
|
else if (underlyingEnumType.PrimitiveTypeKind != cspaceEnumType.UnderlyingType.PrimitiveTypeKind)
|
|
{
|
|
SessionData.LoadMessageLogger.LogLoadMessage(
|
|
Strings.Validator_OSpace_Convention_NonMatchingUnderlyingTypes, cspaceEnumType);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies whether enum members of CLR and EDM types match.
|
|
/// </summary>
|
|
/// <param name="enumType">OSpace CLR enum type.</param>
|
|
/// <param name="cspaceEnumType">CSpace EDM enum type.</param>
|
|
/// <returns><c>true</c> if members match. <c>false</c> otherwise.</returns>
|
|
private bool EnumMembersMatch(Type enumType, EnumType cspaceEnumType)
|
|
{
|
|
Debug.Assert(enumType != null, "enumType != null");
|
|
Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
|
|
Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
|
|
Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
|
|
Debug.Assert(cspaceEnumType.UnderlyingType.ClrEquivalentType == enumType.GetEnumUnderlyingType(), "underlying types should have already been checked");
|
|
|
|
var enumUnderlyingType = enumType.GetEnumUnderlyingType();
|
|
|
|
var cspaceSortedEnumMemberEnumerator = cspaceEnumType.Members.OrderBy(m => m.Name).GetEnumerator();
|
|
var ospaceSortedEnumMemberNamesEnumerator = enumType.GetEnumNames().OrderBy(n => n).GetEnumerator();
|
|
|
|
// no checks required if edm enum type does not have any members
|
|
if (!cspaceSortedEnumMemberEnumerator.MoveNext())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
while (ospaceSortedEnumMemberNamesEnumerator.MoveNext())
|
|
{
|
|
if (cspaceSortedEnumMemberEnumerator.Current.Name == ospaceSortedEnumMemberNamesEnumerator.Current &&
|
|
cspaceSortedEnumMemberEnumerator.Current.Value.Equals(
|
|
Convert.ChangeType(
|
|
Enum.Parse(enumType, ospaceSortedEnumMemberNamesEnumerator.Current), enumUnderlyingType, CultureInfo.InvariantCulture)))
|
|
{
|
|
if (!cspaceSortedEnumMemberEnumerator.MoveNext())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
SessionData.LoadMessageLogger.LogLoadMessage(
|
|
System.Data.Entity.Strings.Mapping_Enum_OCMapping_MemberMismatch(
|
|
enumType.FullName,
|
|
cspaceSortedEnumMemberEnumerator.Current.Name,
|
|
cspaceSortedEnumMemberEnumerator.Current.Value,
|
|
cspaceEnumType.FullName), cspaceEnumType);
|
|
|
|
return false;
|
|
}
|
|
|
|
internal override void OnLevel1SessionProcessing()
|
|
{
|
|
CreateRelationships();
|
|
|
|
foreach (Action resolve in _referenceResolutions)
|
|
{
|
|
resolve();
|
|
}
|
|
|
|
base.OnLevel1SessionProcessing();
|
|
}
|
|
|
|
private EdmType ResolveBaseType(StructuralType baseCSpaceType, Type type)
|
|
{
|
|
EdmType ospaceType;
|
|
bool foundValue = SessionData.CspaceToOspace.TryGetValue(baseCSpaceType, out ospaceType);
|
|
if (!foundValue)
|
|
{
|
|
string message =
|
|
SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
|
|
Strings.Validator_OSpace_Convention_BaseTypeNotLoaded(type, baseCSpaceType),
|
|
baseCSpaceType);
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
|
|
}
|
|
|
|
Debug.Assert(!foundValue || ospaceType is StructuralType, "Structural type expected (if found).");
|
|
|
|
return ospaceType;
|
|
}
|
|
|
|
private bool TryCreateMembers(Type type, StructuralType cspaceType, StructuralType ospaceType, List<Action> referenceResolutionListForCurrentType)
|
|
{
|
|
BindingFlags flags = cspaceType.BaseType == null ? RootEntityPropertyReflectionBindingFlags : PropertyReflectionBindingFlags;
|
|
|
|
PropertyInfo[] clrProperties = type.GetProperties(flags);
|
|
|
|
// required properties scalar properties first
|
|
if (!TryFindAndCreatePrimitiveProperties(type, cspaceType, ospaceType, clrProperties))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!TryFindAndCreateEnumProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryFindComplexProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryFindNavigationProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool TryFindComplexProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
|
|
{
|
|
List<KeyValuePair<EdmProperty, PropertyInfo>> typeClosureToTrack =
|
|
new List<KeyValuePair<EdmProperty, PropertyInfo>>();
|
|
foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(m => Helper.IsComplexType(m.TypeUsage.EdmType)))
|
|
{
|
|
PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
|
|
if (clrProperty != null)
|
|
{
|
|
typeClosureToTrack.Add(
|
|
new KeyValuePair<EdmProperty, PropertyInfo>(
|
|
cspaceProperty, clrProperty));
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach (var typeToTrack in typeClosureToTrack)
|
|
{
|
|
TrackClosure(typeToTrack.Value.PropertyType);
|
|
// prevent the lifting of these closure variables
|
|
var ot = ospaceType;
|
|
var cp = typeToTrack.Key;
|
|
var clrp = typeToTrack.Value;
|
|
referenceResolutionListForCurrentType.Add(() => CreateAndAddComplexType(type, ot, cp, clrp));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool TryFindNavigationProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
|
|
{
|
|
List<KeyValuePair<NavigationProperty, PropertyInfo>> typeClosureToTrack =
|
|
new List<KeyValuePair<NavigationProperty, PropertyInfo>>();
|
|
foreach (NavigationProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<NavigationProperty>())
|
|
{
|
|
PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => NonPrimitiveMemberMatchesByConvention(p, cspaceProperty));
|
|
if (clrProperty != null)
|
|
{
|
|
bool needsSetter = cspaceProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many;
|
|
if (clrProperty.CanRead && (!needsSetter || clrProperty.CanWrite))
|
|
{
|
|
typeClosureToTrack.Add(
|
|
new KeyValuePair<NavigationProperty, PropertyInfo>(
|
|
cspaceProperty, clrProperty));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(
|
|
cspaceProperty.Name, type.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach (var typeToTrack in typeClosureToTrack)
|
|
{
|
|
TrackClosure(typeToTrack.Value.PropertyType);
|
|
|
|
// keep from lifting these closure variables
|
|
var ct = cspaceType;
|
|
var ot = ospaceType;
|
|
var cp = typeToTrack.Key;
|
|
var clrp = typeToTrack.Value;
|
|
|
|
referenceResolutionListForCurrentType.Add(() => CreateAndAddNavigationProperty(ct, ot, cp, clrp));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void TrackClosure(Type type)
|
|
{
|
|
|
|
if (SourceAssembly != type.Assembly &&
|
|
!CacheEntry.ClosureAssemblies.Contains(type.Assembly) &&
|
|
!(type.IsGenericType &&
|
|
(
|
|
EntityUtil.IsAnICollection(type) || // EntityCollection<>, List<>, ICollection<>
|
|
type.GetGenericTypeDefinition() == typeof(System.Data.Objects.DataClasses.EntityReference<>) ||
|
|
type.GetGenericTypeDefinition() == typeof(System.Nullable<>)
|
|
)
|
|
)
|
|
)
|
|
{
|
|
CacheEntry.ClosureAssemblies.Add(type.Assembly);
|
|
}
|
|
|
|
if (type.IsGenericType)
|
|
{
|
|
foreach (Type genericArgument in type.GetGenericArguments())
|
|
{
|
|
TrackClosure(genericArgument);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateAndAddComplexType(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
|
|
{
|
|
EdmType propertyType;
|
|
if (SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out propertyType))
|
|
{
|
|
Debug.Assert(propertyType is StructuralType, "Structural type expected.");
|
|
|
|
EdmProperty property = new EdmProperty(cspaceProperty.Name, TypeUsage.Create(propertyType, new FacetValues { Nullable = false }), clrProperty, type.TypeHandle);
|
|
ospaceType.AddMember(property);
|
|
}
|
|
else
|
|
{
|
|
string message =
|
|
SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
|
|
Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
|
|
cspaceProperty.TypeUsage.EdmType);
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
|
|
}
|
|
|
|
}
|
|
|
|
private void CreateAndAddNavigationProperty(StructuralType cspaceType, StructuralType ospaceType, NavigationProperty cspaceProperty, PropertyInfo clrProperty)
|
|
{
|
|
EdmType ospaceRelationship;
|
|
if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.RelationshipType, out ospaceRelationship))
|
|
{
|
|
Debug.Assert(ospaceRelationship is StructuralType, "Structural type expected.");
|
|
|
|
bool foundTarget = false;
|
|
EdmType targetType = null;
|
|
if (Helper.IsCollectionType(cspaceProperty.TypeUsage.EdmType))
|
|
{
|
|
EdmType findType;
|
|
foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)((CollectionType)cspaceProperty.TypeUsage.EdmType).TypeUsage.EdmType, out findType);
|
|
if (foundTarget)
|
|
{
|
|
Debug.Assert(findType is StructuralType, "Structural type expected.");
|
|
|
|
targetType = findType.GetCollectionType();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EdmType findType;
|
|
foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out findType);
|
|
if (foundTarget)
|
|
{
|
|
Debug.Assert(findType is StructuralType, "Structural type expected.");
|
|
|
|
targetType = findType;
|
|
}
|
|
}
|
|
|
|
|
|
Debug.Assert(foundTarget, "Since the relationship will only be created if it can find the types for both ends, we will never fail to find one of the ends");
|
|
|
|
NavigationProperty navigationProperty = new NavigationProperty(cspaceProperty.Name, TypeUsage.Create(targetType), clrProperty);
|
|
navigationProperty.RelationshipType = (RelationshipType)ospaceRelationship;
|
|
|
|
// we can use First because o-space relationships are created directly from
|
|
// c-space relationship
|
|
navigationProperty.ToEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.ToEndMember.Name);
|
|
navigationProperty.FromEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.FromEndMember.Name);
|
|
ospaceType.AddMember(navigationProperty);
|
|
}
|
|
else
|
|
{
|
|
EntityTypeBase missingType = cspaceProperty.RelationshipType.RelationshipEndMembers.Select(e => ((RefType)e.TypeUsage.EdmType).ElementType).First(e => e != cspaceType);
|
|
string message =
|
|
SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
|
|
Strings.Validator_OSpace_Convention_RelationshipNotLoaded(cspaceProperty.RelationshipType.FullName, missingType.FullName),
|
|
missingType);
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
|
|
}
|
|
}
|
|
|
|
private bool TryFindAndCreatePrimitiveProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties)
|
|
{
|
|
foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsPrimitiveType(p.TypeUsage.EdmType)))
|
|
{
|
|
PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
|
|
if (clrProperty != null)
|
|
{
|
|
PrimitiveType propertyType;
|
|
if (TryGetPrimitiveType(clrProperty.PropertyType, out propertyType))
|
|
{
|
|
if (clrProperty.CanRead && clrProperty.CanWrite)
|
|
{
|
|
AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_NonPrimitiveTypeProperty(clrProperty.Name, type.FullName, clrProperty.PropertyType.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool TryFindAndCreateEnumProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
|
|
{
|
|
var typeClosureToTrack = new List<KeyValuePair<EdmProperty, PropertyInfo>>();
|
|
|
|
foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsEnumType(p.TypeUsage.EdmType)))
|
|
{
|
|
PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
|
|
if (clrProperty != null)
|
|
{
|
|
typeClosureToTrack.Add(new KeyValuePair<EdmProperty, PropertyInfo>(cspaceProperty, clrProperty));
|
|
}
|
|
else
|
|
{
|
|
string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
|
|
SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach (var typeToTrack in typeClosureToTrack)
|
|
{
|
|
TrackClosure(typeToTrack.Value.PropertyType);
|
|
// prevent the lifting of these closure variables
|
|
var ot = ospaceType;
|
|
var cp = typeToTrack.Key;
|
|
var clrp = typeToTrack.Value;
|
|
referenceResolutionListForCurrentType.Add(() => CreateAndAddEnumProperty(type, ot, cp, clrp));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an Enum property based on <paramref name="clrProperty"/>and adds it to the parent structural type.
|
|
/// </summary>
|
|
/// <param name="type">CLR type owning <paramref name="clrProperty"/>.</param>
|
|
/// <param name="ospaceType">OSpace type the created property will be added to.</param>
|
|
/// <param name="cspaceProperty">Corresponding property from CSpace.</param>
|
|
/// <param name="clrProperty">CLR property used to build an Enum property.</param>
|
|
private void CreateAndAddEnumProperty(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
|
|
{
|
|
EdmType propertyType;
|
|
if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.TypeUsage.EdmType, out propertyType))
|
|
{
|
|
if (clrProperty.CanRead && clrProperty.CanWrite)
|
|
{
|
|
AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
|
|
}
|
|
else
|
|
{
|
|
string message =
|
|
SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
|
|
Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName),
|
|
cspaceProperty.TypeUsage.EdmType);
|
|
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string message =
|
|
SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
|
|
Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
|
|
cspaceProperty.TypeUsage.EdmType);
|
|
|
|
SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
|
|
}
|
|
}
|
|
|
|
private void CreateRelationships()
|
|
{
|
|
if (SessionData.ConventionBasedRelationshipsAreLoaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
SessionData.ConventionBasedRelationshipsAreLoaded = true;
|
|
|
|
// find all the relationships
|
|
foreach (AssociationType cspaceAssociation in SessionData.EdmItemCollection.GetItems<AssociationType>())
|
|
{
|
|
Debug.Assert(cspaceAssociation.RelationshipEndMembers.Count == 2, "Relationships are assumed to have exactly two ends");
|
|
|
|
if (SessionData.CspaceToOspace.ContainsKey(cspaceAssociation))
|
|
{
|
|
// don't try to load relationships that we already know about
|
|
continue;
|
|
}
|
|
|
|
EdmType[] ospaceEndTypes = new EdmType[2];
|
|
if (SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[0]), out ospaceEndTypes[0]) &&
|
|
SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[1]), out ospaceEndTypes[1]))
|
|
{
|
|
Debug.Assert(ospaceEndTypes[0] is StructuralType);
|
|
Debug.Assert(ospaceEndTypes[1] is StructuralType);
|
|
|
|
// if we can find both ends of the relationship, then create it
|
|
|
|
AssociationType ospaceAssociation = new AssociationType(cspaceAssociation.Name, cspaceAssociation.NamespaceName, cspaceAssociation.IsForeignKey, DataSpace.OSpace);
|
|
for (int i = 0; i < cspaceAssociation.RelationshipEndMembers.Count; i++)
|
|
{
|
|
EntityType ospaceEndType = (EntityType)ospaceEndTypes[i];
|
|
RelationshipEndMember cspaceEnd = cspaceAssociation.RelationshipEndMembers[i];
|
|
|
|
ospaceAssociation.AddKeyMember(new AssociationEndMember(cspaceEnd.Name, ospaceEndType.GetReferenceType(), cspaceEnd.RelationshipMultiplicity));
|
|
}
|
|
CacheEntry.TypesInAssembly.Add(ospaceAssociation);
|
|
SessionData.TypesInLoading.Add(ospaceAssociation.FullName, ospaceAssociation);
|
|
SessionData.CspaceToOspace.Add(cspaceAssociation, ospaceAssociation);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private static StructuralType GetRelationshipEndType(RelationshipEndMember relationshipEndMember)
|
|
{
|
|
return ((RefType)relationshipEndMember.TypeUsage.EdmType).ElementType;
|
|
}
|
|
|
|
private static bool MemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
|
|
{
|
|
return clrProperty.Name == cspaceMember.Name;
|
|
}
|
|
|
|
private static bool NonPrimitiveMemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
|
|
{
|
|
return !clrProperty.PropertyType.IsValueType && !clrProperty.PropertyType.IsAssignableFrom(typeof(string)) && clrProperty.Name == cspaceMember.Name;
|
|
}
|
|
|
|
internal static bool SessionContainsConventionParameters(ObjectItemLoadingSessionData sessionData)
|
|
{
|
|
return sessionData.EdmItemCollection != null;
|
|
}
|
|
|
|
internal static bool TypesMatchByConvention(Type type, EdmType cspaceType)
|
|
{
|
|
return type.Name == cspaceType.Name;
|
|
}
|
|
|
|
private void AddScalarMember(Type type, PropertyInfo clrProperty, StructuralType ospaceType, EdmProperty cspaceProperty, EdmType propertyType)
|
|
{
|
|
Debug.Assert(type != null, "type != null");
|
|
Debug.Assert(clrProperty != null, "clrProperty != null");
|
|
Debug.Assert(clrProperty.CanRead && clrProperty.CanWrite, "The clr property has to have a setter and a getter.");
|
|
Debug.Assert(ospaceType != null, "ospaceType != null");
|
|
Debug.Assert(cspaceProperty != null, "cspaceProperty != null");
|
|
Debug.Assert(propertyType != null, "propertyType != null");
|
|
Debug.Assert(Helper.IsScalarType(propertyType), "Property has to be primitive or enum.");
|
|
|
|
var cspaceType = cspaceProperty.DeclaringType;
|
|
|
|
bool isKeyMember = Helper.IsEntityType(cspaceType) && ((EntityType)cspaceType).KeyMemberNames.Contains(clrProperty.Name);
|
|
|
|
// the property is nullable only if it is not a key and can actually be set to null (i.e. is not a value type or is a nullable value type)
|
|
bool nullableFacetValue = !isKeyMember && (!clrProperty.PropertyType.IsValueType || Nullable.GetUnderlyingType(clrProperty.PropertyType) != null);
|
|
|
|
EdmProperty ospaceProperty =
|
|
new EdmProperty(
|
|
cspaceProperty.Name,
|
|
TypeUsage.Create(propertyType, new FacetValues { Nullable = nullableFacetValue }),
|
|
clrProperty,
|
|
type.TypeHandle);
|
|
|
|
if (isKeyMember)
|
|
{
|
|
((EntityType)ospaceType).AddKeyMember(ospaceProperty);
|
|
}
|
|
else
|
|
{
|
|
ospaceType.AddMember(ospaceProperty);
|
|
}
|
|
}
|
|
|
|
internal static ObjectItemAssemblyLoader Create(Assembly assembly, ObjectItemLoadingSessionData sessionData)
|
|
{
|
|
if (!ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
|
|
{
|
|
return new ObjectItemConventionAssemblyLoader(assembly, sessionData);
|
|
}
|
|
else
|
|
{
|
|
// we were loading in convention mode, and ran into an assembly that can't be loaded by convention
|
|
sessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_AttributeAssemblyReferenced(assembly.FullName), null));
|
|
return new ObjectItemNoOpAssemblyLoader(assembly, sessionData);
|
|
}
|
|
}
|
|
}
|
|
}
|