|
//---------------------------------------------------------------------
// <copyright file="EntityType.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Security.Cryptography;
using System.Globalization;
namespace System.Data.Metadata.Edm
{
/// <summary>
/// concrete Representation the Entity Type
/// </summary>
public class EntityType : EntityTypeBase
{
#region Constructors
/// <summary>
/// Initializes a new instance of Entity Type
/// </summary>
/// <param name="name">name of the entity type</param>
/// <param name="namespaceName">namespace of the entity type</param>
/// <param name="version">version of the entity type</param>
/// <param name="dataSpace">dataspace in which the EntityType belongs to</param>
/// <exception cref="System.ArgumentNullException">Thrown if either name, namespace or version arguments are null</exception>
internal EntityType(string name, string namespaceName, DataSpace dataSpace)
: base(name, namespaceName, dataSpace)
{
}
/// <param name="name">name of the entity type</param>
/// <param name="namespaceName">namespace of the entity type</param>
/// <param name="version">version of the entity type</param>
/// <param name="dataSpace">dataspace in which the EntityType belongs to</param>
/// <param name="members">members of the entity type [property and navigational property]</param>
/// <param name="keyMemberNames">key members for the type</param>
/// <exception cref="System.ArgumentNullException">Thrown if either name, namespace or version arguments are null</exception>
internal EntityType(string name,
string namespaceName,
DataSpace dataSpace,
IEnumerable<string> keyMemberNames,
IEnumerable<EdmMember> members)
: base(name, namespaceName, dataSpace)
{
//--- first add the properties
if (null != members)
{
CheckAndAddMembers(members, this);
}
//--- second add the key members
if (null != keyMemberNames)
{
//Validation should make sure that base type of this type does not have keymembers when this type has keymembers.
CheckAndAddKeyMembers(keyMemberNames);
}
}
#endregion
#region Fields
/// <summary>cached dynamic method to construct a CLR instance</summary>
private RefType _referenceType;
private ReadOnlyMetadataCollection<EdmProperty> _properties;
private RowType _keyRow;
private Dictionary<EdmMember, string> _memberSql;
private object _memberSqlLock = new object();
#endregion
#region Methods
/// <summary>
/// Returns the kind of the type
/// </summary>
public override BuiltInTypeKind BuiltInTypeKind { get { return BuiltInTypeKind.EntityType; } }
/// <summary>
/// Validates a EdmMember object to determine if it can be added to this type's
/// Members collection. If this method returns without throwing, it is assumed
/// the member is valid.
/// </summary>
/// <param name="member">The member to validate</param>
/// <exception cref="System.ArgumentException">Thrown if the member is not a EdmProperty</exception>
internal override void ValidateMemberForAdd(EdmMember member)
{
Debug.Assert(Helper.IsEdmProperty(member) || Helper.IsNavigationProperty(member),
"Only members of type Property may be added to Entity types.");
}
/// <summary>
/// Get SQL description of a member of this entity type.
/// Requires: member must belong to this type
/// </summary>
/// <param name="member">Member for which to retrieve SQL</param>
/// <param name="sql">Outputs SQL describing this member</param>
/// <returns>Whether sql is cached for this member</returns>
internal bool TryGetMemberSql(EdmMember member, out string sql)
{
Debug.Assert(Members.Contains(member));
sql = null;
return null != _memberSql && _memberSql.TryGetValue(member, out sql);
}
/// <summary>
/// Sets SQL describing a member of this entity type.
/// Requires: member must belong to this type
/// </summary>
/// <param name="member">Member for which to set SQL</param>
/// <param name="sql">SQL describing this member</param>
internal void SetMemberSql(EdmMember member, string sql)
{
Debug.Assert(Members.Contains(member));
// initialize dictionary on first use
lock (_memberSqlLock)
{
if (null == _memberSql)
{
_memberSql = new Dictionary<EdmMember, string>();
}
_memberSql[member] = sql;
}
}
#endregion
#region Properties
/// <summary>
/// Returns the list of Navigation Properties for this entity type
/// </summary>
public ReadOnlyMetadataCollection<NavigationProperty> NavigationProperties
{
get
{
return new FilteredReadOnlyMetadataCollection<NavigationProperty, EdmMember>(
((ReadOnlyMetadataCollection<EdmMember>)this.Members), Helper.IsNavigationProperty);
}
}
/// <summary>
/// Returns just the properties from the collection
/// of members on this type
/// </summary>
public ReadOnlyMetadataCollection<EdmProperty> Properties
{
get
{
Debug.Assert(IsReadOnly, "this is a wrapper around this.Members, don't call it during metadata loading, only call it after the metadata is set to readonly");
if (null == _properties)
{
Interlocked.CompareExchange(ref _properties,
new FilteredReadOnlyMetadataCollection<EdmProperty, EdmMember>(
this.Members, Helper.IsEdmProperty), null);
}
return _properties;
}
}
#endregion // Properties
/// <summary>
/// Returns the Reference type pointing to this entity type
/// </summary>
/// <returns></returns>
public RefType GetReferenceType()
{
if (_referenceType == null)
{
Interlocked.CompareExchange<RefType>(ref _referenceType, new RefType(this), null);
}
return _referenceType;
}
internal RowType GetKeyRowType(MetadataWorkspace metadataWorkspace)
{
if (_keyRow == null)
{
List<EdmProperty> keyProperties = new List<EdmProperty>(KeyMembers.Count);
foreach (EdmMember keyMember in KeyMembers)
{
keyProperties.Add(new EdmProperty(keyMember.Name, Helper.GetModelTypeUsage(keyMember)));
}
Interlocked.CompareExchange<RowType>(ref _keyRow, new RowType(keyProperties), null);
}
return _keyRow;
}
/// <summary>
/// Attempts to get the property name for the ----oication between the two given end
/// names. Note that this property may not exist if a navigation property is defined
/// in one direction but not in the other.
/// </summary>
/// <param name="relationshipType">the relationship for which a nav property is required</param>
/// <param name="fromName">the 'from' end of the association</param>
/// <param name="toName">the 'to' end of the association</param>
/// <param name="navigationProperty">the property name, or null if none was found</param>
/// <returns>true if a property was found, false otherwise</returns>
internal bool TryGetNavigationProperty(string relationshipType, string fromName, string toName, out NavigationProperty navigationProperty)
{
// This is a linear search but it's probably okay because the number of entries
// is generally small and this method is only called to generate code during lighweight
// code gen.
foreach (NavigationProperty navProperty in NavigationProperties)
{
if (navProperty.RelationshipType.FullName == relationshipType &&
navProperty.FromEndMember.Name == fromName &&
navProperty.ToEndMember.Name == toName)
{
navigationProperty = navProperty;
return true;
}
}
navigationProperty = null;
return false;
}
}
internal sealed class ClrEntityType : EntityType
{
/// <summary>cached CLR type handle, allowing the Type reference to be GC'd</summary>
private readonly System.RuntimeTypeHandle _type;
/// <summary>cached dynamic method to construct a CLR instance</summary>
private Delegate _constructor;
private readonly string _cspaceTypeName;
private readonly string _cspaceNamespaceName;
private string _hash;
/// <summary>
/// Initializes a new instance of Complex Type with properties from the type.
/// </summary>
/// <param name="type">The CLR type to construct from</param>
internal ClrEntityType(Type type, string cspaceNamespaceName, string cspaceTypeName)
: base(EntityUtil.GenericCheckArgumentNull(type, "type").Name, type.Namespace ?? string.Empty,
DataSpace.OSpace)
{
System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(cspaceNamespaceName) &&
!String.IsNullOrEmpty(cspaceTypeName), "Mapping information must never be null");
_type = type.TypeHandle;
_cspaceNamespaceName = cspaceNamespaceName;
_cspaceTypeName = cspaceNamespaceName + "." + cspaceTypeName;
this.Abstract = type.IsAbstract;
}
/// <summary>cached dynamic method to construct a CLR instance</summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Delegate Constructor
{
get { return _constructor; }
set
{
// It doesn't matter which delegate wins, but only one should be jitted
Interlocked.CompareExchange(ref _constructor, value, null);
}
}
/// <summary>
/// </summary>
internal override System.Type ClrType
{
get { return Type.GetTypeFromHandle(_type); }
}
internal string CSpaceTypeName { get { return _cspaceTypeName; } }
internal string CSpaceNamespaceName { get { return _cspaceNamespaceName; } }
/// <summary>
/// Gets a collision resistent (SHA256) hash of the information used to build
/// a proxy for this type. This hash is very, very unlikely to be the same for two
/// proxies generated from the same CLR type but with different metadata, and is
/// guarenteed to be the same for proxies generated from the same metadata. This
/// means that when EntityType comparison fails because of metadata eviction,
/// the hash can be used to determine whether or not a proxy is of the correct type.
/// </summary>
internal string HashedDescription
{
get
{
if (_hash == null)
{
Interlocked.CompareExchange(ref _hash, BuildEntityTypeHash(), null);
}
return _hash;
}
}
/// <summary>
/// Creates an SHA256 hash of a description of all the metadata relevant to the creation of a proxy type
/// for this entity type.
/// </summary>
private string BuildEntityTypeHash()
{
var hash = System.Data.Common.Utils.MetadataHelper.CreateSHA256HashAlgorithm()
.ComputeHash(Encoding.ASCII.GetBytes(BuildEntityTypeDescription()));
// convert num bytes to num hex digits
var builder = new StringBuilder(hash.Length * 2);
foreach (byte bite in hash)
{
builder.Append(bite.ToString("X2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
/// <summary>
/// Creates a description of all the metadata relevant to the creation of a proxy type
/// for this entity type.
/// </summary>
private string BuildEntityTypeDescription()
{
var builder = new StringBuilder(512);
Debug.Assert(ClrType != null, "Expecting non-null CLRType of o-space EntityType.");
builder.Append("CLR:").Append(ClrType.FullName);
builder.Append("Conceptual:").Append(CSpaceTypeName);
var navProps = new SortedSet<string>();
foreach (var navProperty in NavigationProperties)
{
navProps.Add(navProperty.Name + "*" +
navProperty.FromEndMember.Name + "*" +
navProperty.FromEndMember.RelationshipMultiplicity + "*" +
navProperty.ToEndMember.Name + "*" +
navProperty.ToEndMember.RelationshipMultiplicity + "*");
}
builder.Append("NavProps:");
foreach (var navProp in navProps)
{
builder.Append(navProp);
}
var keys = new SortedSet<string>();
foreach (var member in KeyMemberNames)
{
keys.Add(member);
}
builder.Append("Keys:");
foreach (var key in keys)
{
builder.Append(key + "*");
}
var scalars = new SortedSet<string>();
foreach (var member in Members)
{
if (!keys.Contains(member.Name))
{
scalars.Add(member.Name + "*");
}
}
builder.Append("Scalars:");
foreach (var scalar in scalars)
{
builder.Append(scalar + "*");
}
return builder.ToString();
}
}
}
|