|
//---------------------------------------------------------------------
// <copyright file="EntityProxyFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Objects.Internal
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Security;
using System.Security.Permissions;
using System.Threading;
/// <summary>
/// Factory for creating proxy classes that can intercept calls to a class' members.
/// </summary>
internal class EntityProxyFactory
{
private const string ProxyTypeNameFormat = "System.Data.Entity.DynamicProxies.{0}_{1}";
internal const string ResetFKSetterFlagFieldName = "_resetFKSetterFlag";
internal const string CompareByteArraysFieldName = "_compareByteArrays";
/// <summary>
/// A hook such that test code can change the AssemblyBuilderAccess of the
/// proxy assembly through reflection into the EntityProxyFactory.
/// </summary>
private static AssemblyBuilderAccess s_ProxyAssemblyBuilderAccess = AssemblyBuilderAccess.Run;
/// <summary>
/// Dictionary of proxy class type information, keyed by the pair of the CLR type and EntityType CSpaceName of the type being proxied.
/// A null value for a particular EntityType name key records the fact that
/// no proxy Type could be created for the specified type.
/// </summary>
private static Dictionary<Tuple<Type, string>, EntityProxyTypeInfo> s_ProxyNameMap = new Dictionary<Tuple<Type, string>, EntityProxyTypeInfo>();
/// <summary>
/// Dictionary of proxy class type information, keyed by the proxy type
/// </summary>
private static Dictionary<Type, EntityProxyTypeInfo> s_ProxyTypeMap = new Dictionary<Type, EntityProxyTypeInfo>();
private static Dictionary<Assembly, ModuleBuilder> s_ModuleBuilders = new Dictionary<Assembly, ModuleBuilder>();
private static ReaderWriterLockSlim s_TypeMapLock = new ReaderWriterLockSlim();
/// <summary>
/// The runtime assembly of the proxy types.
/// This is not the same as the AssemblyBuilder used to create proxy types.
/// </summary>
private static HashSet<Assembly> ProxyRuntimeAssemblies = new HashSet<Assembly>();
private static ModuleBuilder GetDynamicModule(EntityType ospaceEntityType)
{
Assembly assembly = ospaceEntityType.ClrType.Assembly;
ModuleBuilder moduleBuilder;
if (!s_ModuleBuilders.TryGetValue(assembly, out moduleBuilder))
{
AssemblyName assemblyName = new AssemblyName(String.Format(CultureInfo.InvariantCulture, "EntityFrameworkDynamicProxies-{0}", assembly.FullName));
assemblyName.Version = new Version(1, 0, 0, 0);
// Mark assembly as security transparent, meaning it cannot cause an elevation of privilege.
// This also means the assembly cannot satisfy a link demand. Instead link demands become full demands.
ConstructorInfo securityTransparentAttributeConstructor = typeof(SecurityTransparentAttribute).GetConstructor(Type.EmptyTypes);
// Mark assembly with [SecurityRules(SecurityRuleSet.Level1)]. In memory, the assembly will inherit
// this automatically from SDE, but when persisted it needs this attribute to be considered Level1.
ConstructorInfo securityRulesAttributeConstructor = typeof(SecurityRulesAttribute).GetConstructor(new Type[] { typeof(SecurityRuleSet) });
CustomAttributeBuilder[] attributeBuilders = new CustomAttributeBuilder[] {
new CustomAttributeBuilder(securityTransparentAttributeConstructor, new object[0]),
new CustomAttributeBuilder(securityRulesAttributeConstructor, new object[1] { SecurityRuleSet.Level1 })
};
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, s_ProxyAssemblyBuilderAccess, attributeBuilders);
if (s_ProxyAssemblyBuilderAccess == AssemblyBuilderAccess.RunAndSave)
{
// Make the module persistable if the AssemblyBuilderAccess is changed to be RunAndSave.
moduleBuilder = assemblyBuilder.DefineDynamicModule("EntityProxyModule", "EntityProxyModule.dll");
}
else
{
moduleBuilder = assemblyBuilder.DefineDynamicModule("EntityProxyModule");
}
s_ModuleBuilders.Add(assembly, moduleBuilder);
}
return moduleBuilder;
}
internal static bool TryGetProxyType(Type clrType, string entityTypeName, out EntityProxyTypeInfo proxyTypeInfo)
{
s_TypeMapLock.EnterReadLock();
try
{
return s_ProxyNameMap.TryGetValue(new Tuple<Type, string>(clrType, entityTypeName), out proxyTypeInfo);
}
finally
{
s_TypeMapLock.ExitReadLock();
}
}
internal static bool TryGetProxyType(Type proxyType, out EntityProxyTypeInfo proxyTypeInfo)
{
s_TypeMapLock.EnterReadLock();
try
{
return s_ProxyTypeMap.TryGetValue(proxyType, out proxyTypeInfo);
}
finally
{
s_TypeMapLock.ExitReadLock();
}
}
internal static bool TryGetProxyWrapper(object instance, out IEntityWrapper wrapper)
{
Debug.Assert(instance != null, "the instance should not be null");
wrapper = null;
EntityProxyTypeInfo proxyTypeInfo;
if (IsProxyType(instance.GetType()) &&
TryGetProxyType(instance.GetType(), out proxyTypeInfo))
{
wrapper = proxyTypeInfo.GetEntityWrapper(instance);
}
return wrapper != null;
}
/// <summary>
/// Return proxy type information for the specified O-Space EntityType.
/// </summary>
/// <param name="ospaceEntityType">
/// EntityType in O-Space that represents the CLR type to be proxied.
/// Must not be null.
/// </param>
/// <returns>
/// A non-null EntityProxyTypeInfo instance that contains information about the type of proxy for
/// the specified O-Space EntityType; or null if no proxy can be created for the specified type.
/// </returns>
internal static EntityProxyTypeInfo GetProxyType(ClrEntityType ospaceEntityType)
{
Debug.Assert(ospaceEntityType != null, "ospaceEntityType must be non-null");
Debug.Assert(ospaceEntityType.DataSpace == DataSpace.OSpace, "ospaceEntityType.DataSpace must be OSpace");
EntityProxyTypeInfo proxyTypeInfo = null;
// Check if an entry for the proxy type already exists.
if (TryGetProxyType(ospaceEntityType.ClrType, ospaceEntityType.CSpaceTypeName, out proxyTypeInfo))
{
if (proxyTypeInfo != null)
{
proxyTypeInfo.ValidateType(ospaceEntityType);
}
return proxyTypeInfo;
}
// No entry found, may need to create one.
// Acquire an upgradeable read lock so that:
// 1. Other readers aren't blocked while the second existence check is performed.
// 2. Other threads that may have also detected the absence of an entry block while the first thread handles proxy type creation.
s_TypeMapLock.EnterUpgradeableReadLock();
try
{
return TryCreateProxyType(ospaceEntityType);
}
finally
{
s_TypeMapLock.ExitUpgradeableReadLock();
}
}
/// <summary>
/// A mechanism to lookup AssociationType metadata for proxies for a given entity and association information
/// </summary>
/// <param name="wrappedEntity">The entity instance used to lookup the proxy type</param>
/// <param name="relationshipName">The name of the relationship (FullName or Name)</param>
/// <param name="targetRoleName">Target role of the relationship</param>
/// <param name="associationType">The AssociationType for that property</param>
/// <returns>True if an AssociationType is found in proxy metadata, false otherwise</returns>
internal static bool TryGetAssociationTypeFromProxyInfo(IEntityWrapper wrappedEntity, string relationshipName, string targetRoleName, out AssociationType associationType)
{
EntityProxyTypeInfo proxyInfo = null;
associationType = null;
return (EntityProxyFactory.TryGetProxyType(wrappedEntity.Entity.GetType(), out proxyInfo) && proxyInfo != null &&
proxyInfo.TryGetNavigationPropertyAssociationType(relationshipName, targetRoleName, out associationType));
}
/// <summary>
/// Enumerate list of supplied O-Space EntityTypes,
/// and generate a proxy type for each EntityType (if possible for the particular type).
/// </summary>
/// <param name="ospaceEntityType">
/// Enumeration of O-Space EntityType objects.
/// Must not be null.
/// In addition, the elements of the enumeration must not be null.
/// </param>
internal static void TryCreateProxyTypes(IEnumerable<EntityType> ospaceEntityTypes)
{
Debug.Assert(ospaceEntityTypes != null, "ospaceEntityTypes must be non-null");
// Acquire an upgradeable read lock for the duration of the enumeration so that:
// 1. Other readers aren't blocked while existence checks are performed.
// 2. Other threads that may have detected the absence of an entry block while the first thread handles proxy type creation.
s_TypeMapLock.EnterUpgradeableReadLock();
try
{
foreach (EntityType ospaceEntityType in ospaceEntityTypes)
{
Debug.Assert(ospaceEntityType != null, "Null EntityType element reference present in enumeration.");
TryCreateProxyType(ospaceEntityType);
}
}
finally
{
s_TypeMapLock.ExitUpgradeableReadLock();
}
}
private static EntityProxyTypeInfo TryCreateProxyType(EntityType ospaceEntityType)
{
Debug.Assert(s_TypeMapLock.IsUpgradeableReadLockHeld, "EntityProxyTypeInfo.TryCreateProxyType method was called without first acquiring an upgradeable read lock from s_TypeMapLock.");
EntityProxyTypeInfo proxyTypeInfo;
ClrEntityType clrEntityType = (ClrEntityType)ospaceEntityType;
Tuple<Type, string> proxyIdentiy = new Tuple<Type, string>(clrEntityType.ClrType, clrEntityType.HashedDescription);
if (!s_ProxyNameMap.TryGetValue(proxyIdentiy, out proxyTypeInfo) && CanProxyType(ospaceEntityType))
{
ModuleBuilder moduleBuilder = GetDynamicModule(ospaceEntityType);
proxyTypeInfo = BuildType(moduleBuilder, clrEntityType);
s_TypeMapLock.EnterWriteLock();
try
{
s_ProxyNameMap[proxyIdentiy] = proxyTypeInfo;
if (proxyTypeInfo != null)
{
// If there is a proxy type, create the reverse lookup
s_ProxyTypeMap[proxyTypeInfo.ProxyType] = proxyTypeInfo;
}
}
finally
{
s_TypeMapLock.ExitWriteLock();
}
}
return proxyTypeInfo;
}
/// <summary>
/// Determine if the specified type represents a known proxy type.
/// </summary>
/// <param name="type">
/// The Type to be examined.
/// </param>
/// <returns>
/// True if the type is a known proxy type; otherwise false.
/// </returns>
internal static bool IsProxyType(Type type)
{
Debug.Assert(type != null, "type is null, was this intended?");
return type != null && ProxyRuntimeAssemblies.Contains(type.Assembly);
}
/// <summary>
/// Return an enumerable of the current set of CLR proxy types.
/// </summary>
/// <returns>
/// Enumerable of the current set of CLR proxy types.
/// This value will never be null.
/// </returns>
/// <remarks>
/// The enumerable is based on a shapshot of the current list of types.
/// </remarks>
internal static IEnumerable<Type> GetKnownProxyTypes()
{
s_TypeMapLock.EnterReadLock();
try
{
var proxyTypes = from info in s_ProxyNameMap.Values
where info != null
select info.ProxyType;
return proxyTypes.ToArray();
}
finally
{
s_TypeMapLock.ExitReadLock();
}
}
public Func<object, object> CreateBaseGetter(Type declaringType, PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null, "Null propertyInfo");
ParameterExpression Object_Parameter = Expression.Parameter(typeof(object), "instance");
Func<object, object> nonProxyGetter = Expression.Lambda<Func<object, object>>(
Expression.PropertyOrField(
Expression.Convert(Object_Parameter, declaringType),
propertyInfo.Name),
Object_Parameter).Compile();
string propertyName = propertyInfo.Name;
return (entity) =>
{
Type type = entity.GetType();
if (IsProxyType(type))
{
object value;
if (TryGetBasePropertyValue(type, propertyName, entity, out value))
{
return value;
}
}
return nonProxyGetter(entity);
};
}
private static bool TryGetBasePropertyValue(Type proxyType, string propertyName, object entity, out object value)
{
EntityProxyTypeInfo typeInfo;
value = null;
if (TryGetProxyType(proxyType, out typeInfo) && typeInfo.ContainsBaseGetter(propertyName))
{
value = typeInfo.BaseGetter(entity, propertyName);
return true;
}
return false;
}
public Action<object, object> CreateBaseSetter(Type declaringType, PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null, "Null propertyInfo");
Action<object, object> nonProxySetter = LightweightCodeGenerator.CreateNavigationPropertySetter(declaringType, propertyInfo);
string propertyName = propertyInfo.Name;
return (entity, value) =>
{
Type type = entity.GetType();
if (IsProxyType(type))
{
if (TrySetBasePropertyValue(type, propertyName, entity, value))
{
return;
}
}
nonProxySetter(entity, value);
};
}
private static bool TrySetBasePropertyValue(Type proxyType, string propertyName, object entity, object value)
{
EntityProxyTypeInfo typeInfo;
if (TryGetProxyType(proxyType, out typeInfo) && typeInfo.ContainsBaseSetter(propertyName))
{
typeInfo.BaseSetter(entity, propertyName, value);
return true;
}
return false;
}
/// <summary>
/// Build a CLR proxy type for the supplied EntityType.
/// </summary>
/// <param name="ospaceEntityType">
/// EntityType in O-Space that represents the CLR type to be proxied.
/// </param>
/// <returns>
/// EntityProxyTypeInfo object that contains the constructed proxy type,
/// along with any behaviors associated with that type;
/// or null if a proxy type cannot be constructed for the specified EntityType.
/// </returns>
private static EntityProxyTypeInfo BuildType(ModuleBuilder moduleBuilder, ClrEntityType ospaceEntityType)
{
Debug.Assert(s_TypeMapLock.IsUpgradeableReadLockHeld, "EntityProxyTypeInfo.BuildType method was called without first acquiring an upgradeable read lock from s_TypeMapLock.");
EntityProxyTypeInfo proxyTypeInfo;
ProxyTypeBuilder proxyTypeBuilder = new ProxyTypeBuilder(ospaceEntityType);
Type proxyType = proxyTypeBuilder.CreateType(moduleBuilder);
if (proxyType != null)
{
// Set the runtime assembly of the proxy types if it hasn't already been set.
// This is used by the IsProxyType method.
Assembly typeAssembly = proxyType.Assembly;
if (!ProxyRuntimeAssemblies.Contains(typeAssembly))
{
ProxyRuntimeAssemblies.Add(typeAssembly);
AddAssemblyToResolveList(typeAssembly);
}
proxyTypeInfo = new EntityProxyTypeInfo(proxyType, ospaceEntityType,
proxyTypeBuilder.CreateInitalizeCollectionMethod(proxyType),
proxyTypeBuilder.BaseGetters, proxyTypeBuilder.BaseSetters);
foreach (EdmMember member in proxyTypeBuilder.LazyLoadMembers)
{
InterceptMember(member, proxyType, proxyTypeInfo);
}
SetResetFKSetterFlagDelegate(proxyType, proxyTypeInfo);
SetCompareByteArraysDelegate(proxyType, proxyTypeInfo);
}
else
{
proxyTypeInfo = null;
}
return proxyTypeInfo;
}
/// <summary>
/// In order for deserialization of proxy objects to succeed in this AppDomain,
/// an assembly resolve handler must be added to the AppDomain to resolve the dynamic assembly,
/// since it is not present in a location discoverable by fusion.
/// </summary>
/// <param name="assembly">Proxy assembly to be resolved.</param>
[SecuritySafeCritical]
private static void AddAssemblyToResolveList(Assembly assembly)
{
if (ProxyRuntimeAssemblies.Contains(assembly)) // If the assembly is not a known proxy assembly, ignore it.
{
ResolveEventHandler resolveHandler = new ResolveEventHandler((sender, args) => args.Name == assembly.FullName ? assembly : null);
AppDomain.CurrentDomain.AssemblyResolve += resolveHandler;
}
}
/// <summary>
/// Construct an interception delegate for the specified proxy member.
/// </summary>
/// <param name="member">
/// EdmMember that specifies the member to be intercepted.
/// </param>
/// <param name="proxyType">
/// Type of the proxy.
/// </param>
/// <param name="lazyLoadBehavior">
/// LazyLoadBehavior object that supplies the behavior to load related ends.
/// </param>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void InterceptMember(EdmMember member, Type proxyType, EntityProxyTypeInfo proxyTypeInfo)
{
PropertyInfo property = EntityUtil.GetTopProperty(proxyType, member.Name);
Debug.Assert(property != null, String.Format(CultureInfo.CurrentCulture, "Expected property {0} to be defined on proxy type {1}", member.Name, proxyType.FullName));
FieldInfo interceptorField = proxyType.GetField(LazyLoadImplementor.GetInterceptorFieldName(member.Name), BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic);
Debug.Assert(interceptorField != null, String.Format(CultureInfo.CurrentCulture, "Expected interceptor field for property {0} to be defined on proxy type {1}", member.Name, proxyType.FullName));
Delegate interceptorDelegate = typeof(LazyLoadBehavior).GetMethod("GetInterceptorDelegate", BindingFlags.NonPublic | BindingFlags.Static).
MakeGenericMethod(proxyType, property.PropertyType).
Invoke(null, new object[] { member, proxyTypeInfo.EntityWrapperDelegate }) as Delegate;
AssignInterceptionDelegate(interceptorDelegate, interceptorField);
}
/// <summary>
/// Set the interceptor on a proxy member.
/// </summary>
/// <param name="interceptorDelegate">
/// Delegate to be set
/// </param>
/// <param name="interceptorField">
/// Field define on the proxy type to store the reference to the interception delegate.
/// </param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2128")]
[SecuritySafeCritical]
[ReflectionPermission(SecurityAction.Assert, MemberAccess = true)]
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void AssignInterceptionDelegate(Delegate interceptorDelegate, FieldInfo interceptorField)
{
interceptorField.SetValue(null, interceptorDelegate);
}
/// <summary>
/// Sets a delegate onto the _resetFKSetterFlag field such that it can be executed to make
/// a call into the state manager to reset the InFKSetter flag.
/// </summary>
private static void SetResetFKSetterFlagDelegate(Type proxyType, EntityProxyTypeInfo proxyTypeInfo)
{
var resetFKSetterFlagField = proxyType.GetField(ResetFKSetterFlagFieldName, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic);
Debug.Assert(resetFKSetterFlagField != null, "Expected resetFKSetterFlagField to be defined on the proxy type.");
var resetFKSetterFlagDelegate = GetResetFKSetterFlagDelegate(proxyTypeInfo.EntityWrapperDelegate);
AssignInterceptionDelegate(resetFKSetterFlagDelegate, resetFKSetterFlagField);
}
/// <summary>
/// Returns the delegate that takes a proxy instance and uses it to reset the InFKSetter flag maintained
/// by the state manager of the context associated with the proxy instance.
/// </summary>
private static Action<object> GetResetFKSetterFlagDelegate(Func<object, object> getEntityWrapperDelegate)
{
return (proxy) =>
{
Debug.Assert(getEntityWrapperDelegate != null, "entityWrapperDelegate must not be null");
ResetFKSetterFlag(getEntityWrapperDelegate(proxy));
};
}
/// <summary>
/// Called in the finally clause of each overridden property setter to ensure that the flag
/// indicating that we are in an FK setter is cleared. Note that the wrapped entity is passed as
/// an obejct becayse IEntityWrapper is an internal type and is therefore not accessable to
/// the proxy type. Once we're in the framework it is cast back to an IEntityWrapper.
/// </summary>
private static void ResetFKSetterFlag(object wrappedEntityAsObject)
{
Debug.Assert(wrappedEntityAsObject == null || wrappedEntityAsObject is IEntityWrapper, "wrappedEntityAsObject must be an IEntityWrapper");
var wrappedEntity = (IEntityWrapper)wrappedEntityAsObject; // We want an exception if the cast fails.
if (wrappedEntity != null && wrappedEntity.Context != null)
{
wrappedEntity.Context.ObjectStateManager.EntityInvokingFKSetter = null;
}
}
/// <summary>
/// Sets a delegate onto the _compareByteArrays field such that it can be executed to check
/// whether two byte arrays are the same by value comparison.
/// </summary>
private static void SetCompareByteArraysDelegate(Type proxyType, EntityProxyTypeInfo proxyTypeInfo)
{
var compareByteArraysField = proxyType.GetField(CompareByteArraysFieldName, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic);
Debug.Assert(compareByteArraysField != null, "Expected compareByteArraysField to be defined on the proxy type.");
AssignInterceptionDelegate(new Func<object, object, bool>(ByValueEqualityComparer.Default.Equals), compareByteArraysField);
}
/// <summary>
/// Return boolean that specifies if the specified type can be proxied.
/// </summary>
/// <param name="ospaceEntityType">O-space EntityType</param>
/// <returns>
/// True if the class is not abstract or sealed, does not implement IEntityWithRelationships,
/// and has a public or protected default constructor; otherwise false.
/// </returns>
/// <remarks>
/// While it is technically possible to derive from an abstract type
/// in order to create a proxy, we avoid this so that the proxy type
/// has the same "concreteness" of the type being proxied.
/// The check for IEntityWithRelationships ensures that codegen'ed
/// entities that derive from EntityObject as well as properly
/// constructed IPOCO entities will not be proxied.
///
/// </remarks>
private static bool CanProxyType(EntityType ospaceEntityType)
{
TypeAttributes access = ospaceEntityType.ClrType.Attributes & TypeAttributes.VisibilityMask;
ConstructorInfo ctor = ospaceEntityType.ClrType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, Type.EmptyTypes, null);
bool accessableCtor = ctor != null && (((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public) ||
((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family) ||
((ctor.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem));
return (!(ospaceEntityType.Abstract ||
ospaceEntityType.ClrType.IsSealed ||
typeof(IEntityWithRelationships).IsAssignableFrom(ospaceEntityType.ClrType) ||
!accessableCtor) &&
access == TypeAttributes.Public);
}
private static bool CanProxyMethod(MethodInfo method)
{
bool result = false;
if (method != null)
{
MethodAttributes access = method.Attributes & MethodAttributes.MemberAccessMask;
result = method.IsVirtual &&
!method.IsFinal &&
(access == MethodAttributes.Public ||
access == MethodAttributes.Family ||
access == MethodAttributes.FamORAssem);
}
return result;
}
internal static bool CanProxyGetter(PropertyInfo clrProperty)
{
Debug.Assert(clrProperty != null, "clrProperty should have a value");
return CanProxyMethod(clrProperty.GetGetMethod(true));
}
internal static bool CanProxySetter(PropertyInfo clrProperty)
{
Debug.Assert(clrProperty != null, "clrProperty should have a value");
return CanProxyMethod(clrProperty.GetSetMethod(true));
}
private class ProxyTypeBuilder
{
private TypeBuilder _typeBuilder;
private BaseProxyImplementor _baseImplementor;
private IPOCOImplementor _ipocoImplementor;
private LazyLoadImplementor _lazyLoadImplementor;
private DataContractImplementor _dataContractImplementor;
private ISerializableImplementor _iserializableImplementor;
private ClrEntityType _ospaceEntityType;
private ModuleBuilder _moduleBuilder;
private List<FieldBuilder> _serializedFields = new List<FieldBuilder>(3);
public ProxyTypeBuilder(ClrEntityType ospaceEntityType)
{
_ospaceEntityType = ospaceEntityType;
_baseImplementor = new BaseProxyImplementor();
_ipocoImplementor = new IPOCOImplementor(ospaceEntityType);
_lazyLoadImplementor = new LazyLoadImplementor(ospaceEntityType);
_dataContractImplementor = new DataContractImplementor(ospaceEntityType);
_iserializableImplementor = new ISerializableImplementor(ospaceEntityType);
}
public Type BaseType
{
get { return _ospaceEntityType.ClrType; }
}
public DynamicMethod CreateInitalizeCollectionMethod(Type proxyType)
{
return _ipocoImplementor.CreateInitalizeCollectionMethod(proxyType);
}
public List<PropertyInfo> BaseGetters
{
get
{
return _baseImplementor.BaseGetters;
}
}
public List<PropertyInfo> BaseSetters
{
get
{
return _baseImplementor.BaseSetters;
}
}
public IEnumerable<EdmMember> LazyLoadMembers
{
get { return _lazyLoadImplementor.Members; }
}
public Type CreateType(ModuleBuilder moduleBuilder)
{
_moduleBuilder = moduleBuilder;
bool hadProxyProperties = false;
if (_iserializableImplementor.TypeIsSuitable)
{
foreach (EdmMember member in _ospaceEntityType.Members)
{
if (_ipocoImplementor.CanProxyMember(member) ||
_lazyLoadImplementor.CanProxyMember(member))
{
PropertyInfo baseProperty = EntityUtil.GetTopProperty(BaseType, member.Name);
PropertyBuilder propertyBuilder = TypeBuilder.DefineProperty(member.Name, System.Reflection.PropertyAttributes.None, baseProperty.PropertyType, Type.EmptyTypes);
if (!_ipocoImplementor.EmitMember(TypeBuilder, member, propertyBuilder, baseProperty, _baseImplementor))
{
EmitBaseSetter(TypeBuilder, propertyBuilder, baseProperty);
}
if (!_lazyLoadImplementor.EmitMember(TypeBuilder, member, propertyBuilder, baseProperty, _baseImplementor))
{
EmitBaseGetter(TypeBuilder, propertyBuilder, baseProperty);
}
hadProxyProperties = true;
}
}
if (_typeBuilder != null)
{
_baseImplementor.Implement(TypeBuilder, RegisterInstanceField);
_iserializableImplementor.Implement(TypeBuilder, _serializedFields);
}
}
return hadProxyProperties ? TypeBuilder.CreateType() : null;
}
private TypeBuilder TypeBuilder
{
get
{
if (_typeBuilder == null)
{
TypeAttributes proxyTypeAttributes = TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed;
if ((BaseType.Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable)
{
proxyTypeAttributes |= TypeAttributes.Serializable;
}
// If the type as a long name, then use only the first part of it so that there is no chance that the generated
// name will be too long. Note that the full name always gets used to compute the hash.
string baseName = BaseType.Name.Length <= 20 ? BaseType.Name : BaseType.Name.Substring(0, 20);
string proxyTypeName = String.Format(CultureInfo.InvariantCulture, ProxyTypeNameFormat, baseName, _ospaceEntityType.HashedDescription);
_typeBuilder = _moduleBuilder.DefineType(proxyTypeName, proxyTypeAttributes, BaseType, _ipocoImplementor.Interfaces);
_typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName);
Action<FieldBuilder, bool> registerField = RegisterInstanceField;
_ipocoImplementor.Implement(_typeBuilder, registerField);
_lazyLoadImplementor.Implement(_typeBuilder, registerField);
// WCF data contract serialization is not compatible with types that implement ISerializable.
if (!_iserializableImplementor.TypeImplementsISerializable)
{
_dataContractImplementor.Implement(_typeBuilder, registerField);
}
}
return _typeBuilder;
}
}
private void EmitBaseGetter(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, PropertyInfo baseProperty)
{
if (CanProxyGetter(baseProperty))
{
MethodInfo baseGetter = baseProperty.GetGetMethod(true);
const MethodAttributes getterAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodAttributes getterAccess = baseGetter.Attributes & MethodAttributes.MemberAccessMask;
// Define a property getter override in the proxy type
MethodBuilder getterBuilder = typeBuilder.DefineMethod("get_" + baseProperty.Name, getterAccess | getterAttributes, baseProperty.PropertyType, Type.EmptyTypes);
ILGenerator gen = getterBuilder.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, baseGetter);
gen.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getterBuilder);
}
}
private void EmitBaseSetter(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, PropertyInfo baseProperty)
{
if (CanProxySetter(baseProperty))
{
MethodInfo baseSetter = baseProperty.GetSetMethod(true); ;
const MethodAttributes methodAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodAttributes methodAccess = baseSetter.Attributes & MethodAttributes.MemberAccessMask;
MethodBuilder setterBuilder = typeBuilder.DefineMethod("set_" + baseProperty.Name, methodAccess | methodAttributes, null, new Type[] { baseProperty.PropertyType });
ILGenerator generator = setterBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, baseSetter);
generator.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setterBuilder);
}
}
private void RegisterInstanceField(FieldBuilder field, bool serializable)
{
if (serializable)
{
_serializedFields.Add(field);
}
else
{
MarkAsNotSerializable(field);
}
}
private static readonly ConstructorInfo s_NonSerializedAttributeConstructor = typeof(NonSerializedAttribute).GetConstructor(Type.EmptyTypes);
private static readonly ConstructorInfo s_IgnoreDataMemberAttributeConstructor = typeof(IgnoreDataMemberAttribute).GetConstructor(Type.EmptyTypes);
private static readonly ConstructorInfo s_XmlIgnoreAttributeConstructor = typeof(System.Xml.Serialization.XmlIgnoreAttribute).GetConstructor(Type.EmptyTypes);
private static readonly ConstructorInfo s_ScriptIgnoreAttributeConstructor = TryGetScriptIgnoreAttributeType().GetConstructor(Type.EmptyTypes);
private static Type TryGetScriptIgnoreAttributeType()
{
try
{
var scriptIgnoreAttributeAssembly = Assembly.Load(AssemblyRef.SystemWebExtensions);
return scriptIgnoreAttributeAssembly.GetType(@"System.Web.Script.Serialization.ScriptIgnoreAttribute");
}
catch
{
}
// We should not assert in EF6, at least when produce a build compatible with .NET 4.0 client SKU
Debug.Assert(false, "Unable to find ScriptIgnoreAttribute type");
return null;
}
private static void MarkAsNotSerializable(FieldBuilder field)
{
object[] emptyArray = new object[0];
field.SetCustomAttribute(new CustomAttributeBuilder(s_NonSerializedAttributeConstructor, emptyArray));
if (field.IsPublic)
{
field.SetCustomAttribute(new CustomAttributeBuilder(s_IgnoreDataMemberAttributeConstructor, emptyArray));
field.SetCustomAttribute(new CustomAttributeBuilder(s_XmlIgnoreAttributeConstructor, emptyArray));
if (s_ScriptIgnoreAttributeConstructor != null)
{
field.SetCustomAttribute(new CustomAttributeBuilder(s_ScriptIgnoreAttributeConstructor, emptyArray));
}
}
}
}
}
internal class LazyLoadImplementor
{
HashSet<EdmMember> _members;
public LazyLoadImplementor(EntityType ospaceEntityType)
{
CheckType(ospaceEntityType);
}
public IEnumerable<EdmMember> Members
{
get { return _members; }
}
private void CheckType(EntityType ospaceEntityType)
{
_members = new HashSet<EdmMember>();
foreach (EdmMember member in ospaceEntityType.Members)
{
PropertyInfo clrProperty = EntityUtil.GetTopProperty(ospaceEntityType.ClrType, member.Name);
if (clrProperty != null &&
EntityProxyFactory.CanProxyGetter(clrProperty) &&
LazyLoadBehavior.IsLazyLoadCandidate(ospaceEntityType, member))
{
_members.Add(member);
}
}
}
public bool CanProxyMember(EdmMember member)
{
return _members.Contains(member);
}
public void Implement(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
// Add instance field to store IEntityWrapper instance
// The field is typed as object, for two reasons:
// 1. The practical one, IEntityWrapper is internal and not accessible from the dynamic assembly.
// 2. We purposely want the wrapper field to be opaque on the proxy type.
FieldBuilder wrapperField = typeBuilder.DefineField(EntityProxyTypeInfo.EntityWrapperFieldName, typeof(object), FieldAttributes.Public);
registerField(wrapperField, false);
}
public bool EmitMember(TypeBuilder typeBuilder, EdmMember member, PropertyBuilder propertyBuilder, PropertyInfo baseProperty, BaseProxyImplementor baseImplementor)
{
if (_members.Contains(member))
{
MethodInfo baseGetter = baseProperty.GetGetMethod(true);
const MethodAttributes getterAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodAttributes getterAccess = baseGetter.Attributes & MethodAttributes.MemberAccessMask;
// Define field to store interceptor Func
// Signature of interceptor Func delegate is as follows:
//
// bool intercept(ProxyType proxy, PropertyType propertyValue)
//
// where
// PropertyType is the type of the Property, such as ICollection<Customer>,
// ProxyType is the type of the proxy object,
// propertyValue is the value returned from the proxied type's property getter.
Type interceptorType = typeof(Func<,,>).MakeGenericType(typeBuilder, baseProperty.PropertyType, typeof(bool));
MethodInfo interceptorInvoke = TypeBuilder.GetMethod(interceptorType, typeof(Func<,,>).GetMethod("Invoke"));
FieldBuilder interceptorField = typeBuilder.DefineField(GetInterceptorFieldName(baseProperty.Name), interceptorType, FieldAttributes.Private | FieldAttributes.Static);
// Define a property getter override in the proxy type
MethodBuilder getterBuilder = typeBuilder.DefineMethod("get_" + baseProperty.Name, getterAccess | getterAttributes, baseProperty.PropertyType, Type.EmptyTypes);
ILGenerator generator = getterBuilder.GetILGenerator();
// Emit instructions for the following call:
// T value = base.SomeProperty;
// if(this._interceptorForSomeProperty(this, value))
// { return value; }
// return base.SomeProperty;
// where _interceptorForSomeProperty represents the interceptor Func field.
Label lableTrue = generator.DefineLabel();
generator.DeclareLocal(baseProperty.PropertyType); // T value
generator.Emit(OpCodes.Ldarg_0); // call base.SomeProperty
generator.Emit(OpCodes.Call, baseGetter); // call to base property getter
generator.Emit(OpCodes.Stloc_0); // value = result
generator.Emit(OpCodes.Ldarg_0); // load this
generator.Emit(OpCodes.Ldfld, interceptorField); // load this._interceptor
generator.Emit(OpCodes.Ldarg_0); // load this
generator.Emit(OpCodes.Ldloc_0); // load value
generator.Emit(OpCodes.Callvirt, interceptorInvoke); // call to interceptor delegate with (this, value)
generator.Emit(OpCodes.Brtrue_S, lableTrue); // if true, just return
generator.Emit(OpCodes.Ldarg_0); // else, call the base propertty getter again
generator.Emit(OpCodes.Call, baseGetter); // call to base property getter
generator.Emit(OpCodes.Ret);
generator.MarkLabel(lableTrue);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getterBuilder);
baseImplementor.AddBasePropertyGetter(baseProperty);
return true;
}
return false;
}
internal static string GetInterceptorFieldName(string memberName)
{
return "ef_proxy_interceptorFor" + memberName;
}
}
internal class BaseProxyImplementor
{
private readonly List<PropertyInfo> _baseGetters;
private readonly List<PropertyInfo> _baseSetters;
public BaseProxyImplementor()
{
_baseGetters = new List<PropertyInfo>();
_baseSetters = new List<PropertyInfo>();
}
public List<PropertyInfo> BaseGetters
{
get { return _baseGetters; }
}
public List<PropertyInfo> BaseSetters
{
get { return _baseSetters; }
}
public void AddBasePropertyGetter(PropertyInfo baseProperty)
{
_baseGetters.Add(baseProperty);
}
public void AddBasePropertySetter(PropertyInfo baseProperty)
{
_baseSetters.Add(baseProperty);
}
public void Implement(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
if (_baseGetters.Count > 0)
{
ImplementBaseGetter(typeBuilder);
}
if (_baseSetters.Count > 0)
{
ImplementBaseSetter(typeBuilder);
}
}
static readonly MethodInfo s_StringEquals = typeof(string).GetMethod("op_Equality", new Type[] { typeof(string), typeof(string) });
static readonly ConstructorInfo s_InvalidOperationConstructor = typeof(InvalidOperationException).GetConstructor(Type.EmptyTypes);
private void ImplementBaseGetter(TypeBuilder typeBuilder)
{
// Define a property getter in the proxy type
MethodBuilder getterBuilder = typeBuilder.DefineMethod("GetBasePropertyValue", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(object), new Type[] { typeof(string) });
ILGenerator gen = getterBuilder.GetILGenerator();
Label[] labels = new Label[_baseGetters.Count];
for (int i = 0; i < _baseGetters.Count; i++)
{
labels[i] = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldstr, _baseGetters[i].Name);
gen.Emit(OpCodes.Call, s_StringEquals);
gen.Emit(OpCodes.Brfalse_S, labels[i]);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, _baseGetters[i].GetGetMethod(true));
gen.Emit(OpCodes.Ret);
gen.MarkLabel(labels[i]);
}
gen.Emit(OpCodes.Newobj, s_InvalidOperationConstructor);
gen.Emit(OpCodes.Throw);
}
private void ImplementBaseSetter(TypeBuilder typeBuilder)
{
MethodBuilder setterBuilder = typeBuilder.DefineMethod("SetBasePropertyValue", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(void), new Type[] { typeof(string), typeof(object) });
ILGenerator gen = setterBuilder.GetILGenerator();
Label[] labels = new Label[_baseSetters.Count];
for (int i = 0; i < _baseSetters.Count; i++)
{
labels[i] = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldstr, _baseSetters[i].Name);
gen.Emit(OpCodes.Call, s_StringEquals);
gen.Emit(OpCodes.Brfalse_S, labels[i]);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Castclass, _baseSetters[i].PropertyType);
gen.Emit(OpCodes.Call, _baseSetters[i].GetSetMethod(true));
gen.Emit(OpCodes.Ret);
gen.MarkLabel(labels[i]);
}
gen.Emit(OpCodes.Newobj, s_InvalidOperationConstructor);
gen.Emit(OpCodes.Throw);
}
}
internal class IPOCOImplementor
{
private EntityType _ospaceEntityType;
FieldBuilder _changeTrackerField;
FieldBuilder _relationshipManagerField;
FieldBuilder _resetFKSetterFlagField;
FieldBuilder _compareByteArraysField;
MethodBuilder _entityMemberChanging;
MethodBuilder _entityMemberChanged;
MethodBuilder _getRelationshipManager;
private List<KeyValuePair<NavigationProperty, PropertyInfo>> _referenceProperties;
private List<KeyValuePair<NavigationProperty, PropertyInfo>> _collectionProperties;
private bool _implementIEntityWithChangeTracker;
private bool _implementIEntityWithRelationships;
private HashSet<EdmMember> _scalarMembers;
private HashSet<EdmMember> _relationshipMembers;
static readonly MethodInfo s_EntityMemberChanging = typeof(IEntityChangeTracker).GetMethod("EntityMemberChanging", new Type[] { typeof(string) });
static readonly MethodInfo s_EntityMemberChanged = typeof(IEntityChangeTracker).GetMethod("EntityMemberChanged", new Type[] { typeof(string) });
static readonly MethodInfo s_CreateRelationshipManager = typeof(RelationshipManager).GetMethod("Create", new Type[] { typeof(IEntityWithRelationships) });
static readonly MethodInfo s_GetRelationshipManager = typeof(IEntityWithRelationships).GetProperty("RelationshipManager").GetGetMethod();
static readonly MethodInfo s_GetRelatedReference = typeof(RelationshipManager).GetMethod("GetRelatedReference", new Type[] { typeof(string), typeof(string) });
static readonly MethodInfo s_GetRelatedCollection = typeof(RelationshipManager).GetMethod("GetRelatedCollection", new Type[] { typeof(string), typeof(string) });
static readonly MethodInfo s_GetRelatedEnd = typeof(RelationshipManager).GetMethod("GetRelatedEnd", new Type[] { typeof(string), typeof(string) });
static readonly MethodInfo s_ObjectEquals = typeof(object).GetMethod("Equals", new Type[] { typeof(object), typeof(object) });
static readonly ConstructorInfo s_InvalidOperationConstructor = typeof(InvalidOperationException).GetConstructor(new Type[] { typeof(string) });
static readonly MethodInfo s_IEntityWrapper_GetEntity = typeof(IEntityWrapper).GetProperty("Entity").GetGetMethod();
static readonly MethodInfo s_Action_Invoke = typeof(Action<object>).GetMethod("Invoke", new Type[] { typeof(object) });
static readonly MethodInfo s_Func_object_object_bool_Invoke = typeof(Func<object, object, bool>).GetMethod("Invoke", new Type[] { typeof(object), typeof(object) });
private static readonly ConstructorInfo s_BrowsableAttributeConstructor = typeof(BrowsableAttribute).GetConstructor(new Type[] { typeof(bool) });
public IPOCOImplementor(EntityType ospaceEntityType)
{
Type baseType = ospaceEntityType.ClrType;
_referenceProperties = new List<KeyValuePair<NavigationProperty, PropertyInfo>>();
_collectionProperties = new List<KeyValuePair<NavigationProperty, PropertyInfo>>();
_implementIEntityWithChangeTracker = (null == baseType.GetInterface(typeof(IEntityWithChangeTracker).Name));
_implementIEntityWithRelationships = (null == baseType.GetInterface(typeof(IEntityWithRelationships).Name));
CheckType(ospaceEntityType);
_ospaceEntityType = ospaceEntityType;
}
private void CheckType(EntityType ospaceEntityType)
{
_scalarMembers = new HashSet<EdmMember>();
_relationshipMembers = new HashSet<EdmMember>();
foreach (EdmMember member in ospaceEntityType.Members)
{
PropertyInfo clrProperty = EntityUtil.GetTopProperty(ospaceEntityType.ClrType, member.Name);
if (clrProperty != null && EntityProxyFactory.CanProxySetter(clrProperty))
{
if (member.BuiltInTypeKind == BuiltInTypeKind.EdmProperty)
{
if (_implementIEntityWithChangeTracker)
{
_scalarMembers.Add(member);
}
}
else if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
{
if (_implementIEntityWithRelationships)
{
NavigationProperty navProperty = (NavigationProperty)member;
RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
if (multiplicity == RelationshipMultiplicity.Many)
{
if (clrProperty.PropertyType.IsGenericType &&
clrProperty.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
_relationshipMembers.Add(member);
}
}
else
{
_relationshipMembers.Add(member);
}
}
}
}
}
if (ospaceEntityType.Members.Count != _scalarMembers.Count + _relationshipMembers.Count)
{
_scalarMembers.Clear();
_relationshipMembers.Clear();
_implementIEntityWithChangeTracker = false;
_implementIEntityWithRelationships = false;
}
}
public void Implement(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
if (_implementIEntityWithChangeTracker)
{
ImplementIEntityWithChangeTracker(typeBuilder, registerField);
}
if (_implementIEntityWithRelationships)
{
ImplementIEntityWithRelationships(typeBuilder, registerField);
}
_resetFKSetterFlagField = typeBuilder.DefineField(EntityProxyFactory.ResetFKSetterFlagFieldName, typeof(Action<object>), FieldAttributes.Private| FieldAttributes.Static);
_compareByteArraysField = typeBuilder.DefineField(EntityProxyFactory.CompareByteArraysFieldName, typeof(Func<object, object, bool>), FieldAttributes.Private | FieldAttributes.Static);
}
public Type[] Interfaces
{
get
{
List<Type> types = new List<Type>();
if (_implementIEntityWithChangeTracker) { types.Add(typeof(IEntityWithChangeTracker)); }
if (_implementIEntityWithRelationships) { types.Add(typeof(IEntityWithRelationships)); }
return types.ToArray();
}
}
public DynamicMethod CreateInitalizeCollectionMethod(Type proxyType)
{
if (_collectionProperties.Count > 0)
{
DynamicMethod initializeEntityCollections = LightweightCodeGenerator.CreateDynamicMethod(proxyType.Name + "_InitializeEntityCollections", typeof(IEntityWrapper), new Type[] { typeof(IEntityWrapper) });
ILGenerator generator = initializeEntityCollections.GetILGenerator();
generator.DeclareLocal(proxyType);
generator.DeclareLocal(typeof(RelationshipManager));
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, s_IEntityWrapper_GetEntity);
generator.Emit(OpCodes.Castclass, proxyType);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Callvirt, s_GetRelationshipManager);
generator.Emit(OpCodes.Stloc_1);
foreach (KeyValuePair<NavigationProperty, PropertyInfo> navProperty in _collectionProperties)
{
// Update Constructor to initialize this property
MethodInfo getRelatedCollection = s_GetRelatedCollection.MakeGenericMethod(EntityUtil.GetCollectionElementType(navProperty.Value.PropertyType));
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldloc_1);
generator.Emit(OpCodes.Ldstr, navProperty.Key.RelationshipType.FullName);
generator.Emit(OpCodes.Ldstr, navProperty.Key.ToEndMember.Name);
generator.Emit(OpCodes.Callvirt, getRelatedCollection);
generator.Emit(OpCodes.Callvirt, navProperty.Value.GetSetMethod(true));
}
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ret);
return initializeEntityCollections;
}
return null;
}
public bool CanProxyMember(EdmMember member)
{
return _scalarMembers.Contains(member) || _relationshipMembers.Contains(member);
}
public bool EmitMember(TypeBuilder typeBuilder, EdmMember member, PropertyBuilder propertyBuilder, PropertyInfo baseProperty, BaseProxyImplementor baseImplementor)
{
if (_scalarMembers.Contains(member))
{
bool isKeyMember = _ospaceEntityType.KeyMembers.Contains(member.Identity);
EmitScalarSetter(typeBuilder, propertyBuilder, baseProperty, isKeyMember);
return true;
}
else if (_relationshipMembers.Contains(member))
{
Debug.Assert(member != null, "member is null");
Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty);
NavigationProperty navProperty = member as NavigationProperty;
if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
{
EmitCollectionProperty(typeBuilder, propertyBuilder, baseProperty, navProperty);
}
else
{
EmitReferenceProperty(typeBuilder, propertyBuilder, baseProperty, navProperty);
}
baseImplementor.AddBasePropertySetter(baseProperty);
return true;
}
return false;
}
private void EmitScalarSetter(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, PropertyInfo baseProperty, bool isKeyMember)
{
MethodInfo baseSetter = baseProperty.GetSetMethod(true);
const MethodAttributes methodAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodAttributes methodAccess = baseSetter.Attributes & MethodAttributes.MemberAccessMask;
MethodBuilder setterBuilder = typeBuilder.DefineMethod("set_" + baseProperty.Name, methodAccess | methodAttributes, null, new Type[] { baseProperty.PropertyType });
ILGenerator generator = setterBuilder.GetILGenerator();
Label endOfMethod = generator.DefineLabel();
// If the CLR property represents a key member of the Entity Type,
// ignore attempts to set the key value to the same value.
if (isKeyMember)
{
MethodInfo baseGetter = baseProperty.GetGetMethod(true);
if (baseGetter != null)
{
// if (base.[Property] != value)
// {
// // perform set operation
// }
Type propertyType = baseProperty.PropertyType;
if (propertyType == typeof(int) || // signed integer types
propertyType == typeof(short) ||
propertyType == typeof(Int64) ||
propertyType == typeof(bool) || // boolean
propertyType == typeof(byte) ||
propertyType == typeof(UInt32) ||
propertyType == typeof(UInt64)||
propertyType == typeof(float) ||
propertyType == typeof(double) ||
propertyType.IsEnum)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, baseGetter);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Beq_S, endOfMethod);
}
else if (propertyType == typeof(byte[]))
{
// Byte arrays must be compared by value
generator.Emit(OpCodes.Ldsfld, _compareByteArraysField);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, baseGetter);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, s_Func_object_object_bool_Invoke);
generator.Emit(OpCodes.Brtrue_S, endOfMethod);
}
else
{
// Get the specific type's inequality method if it exists
MethodInfo op_inequality = propertyType.GetMethod("op_Inequality", new Type[] { propertyType, propertyType });
if (op_inequality != null)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, baseGetter);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, op_inequality);
generator.Emit(OpCodes.Brfalse_S, endOfMethod);
}
else
{
// Use object inequality
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, baseGetter);
if (propertyType.IsValueType)
{
generator.Emit(OpCodes.Box, propertyType);
}
generator.Emit(OpCodes.Ldarg_1);
if (propertyType.IsValueType)
{
generator.Emit(OpCodes.Box, propertyType);
}
generator.Emit(OpCodes.Call, s_ObjectEquals);
generator.Emit(OpCodes.Brtrue_S, endOfMethod);
}
}
}
}
// Creates code like this:
//
// try
// {
// MemberChanging(propertyName);
// base.Property_set(value);
// MemberChanged(propertyName);
// }
// finally
// {
// _resetFKSetterFlagField(this);
// }
//
// Note that the try/finally ensures that even if an exception causes
// the setting of the property to be aborted, we still clear the flag that
// indicates that we are in a property setter.
generator.BeginExceptionBlock();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldstr, baseProperty.Name);
generator.Emit(OpCodes.Call, _entityMemberChanging);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, baseSetter);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldstr, baseProperty.Name);
generator.Emit(OpCodes.Call, _entityMemberChanged);
generator.BeginFinallyBlock();
generator.Emit(OpCodes.Ldsfld, _resetFKSetterFlagField);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, s_Action_Invoke);
generator.EndExceptionBlock();
generator.MarkLabel(endOfMethod);
generator.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setterBuilder);
}
private void EmitReferenceProperty(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, PropertyInfo baseProperty, NavigationProperty navProperty)
{
const MethodAttributes methodAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodInfo baseSetter = baseProperty.GetSetMethod(true); ;
MethodAttributes methodAccess = baseSetter.Attributes & MethodAttributes.MemberAccessMask;
MethodInfo specificGetRelatedReference = s_GetRelatedReference.MakeGenericMethod(baseProperty.PropertyType);
MethodInfo specificEntityReferenceSetValue = typeof(EntityReference<>).MakeGenericType(baseProperty.PropertyType).GetMethod("set_Value"); ;
MethodBuilder setterBuilder = typeBuilder.DefineMethod("set_" + baseProperty.Name, methodAccess | methodAttributes, null, new Type[] { baseProperty.PropertyType });
ILGenerator generator = setterBuilder.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, _getRelationshipManager);
generator.Emit(OpCodes.Ldstr, navProperty.RelationshipType.FullName);
generator.Emit(OpCodes.Ldstr, navProperty.ToEndMember.Name);
generator.Emit(OpCodes.Callvirt, specificGetRelatedReference);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, specificEntityReferenceSetValue);
generator.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setterBuilder);
_referenceProperties.Add(new KeyValuePair<NavigationProperty,PropertyInfo>(navProperty, baseProperty));
}
private void EmitCollectionProperty(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, PropertyInfo baseProperty, NavigationProperty navProperty)
{
const MethodAttributes methodAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual;
MethodInfo baseSetter = baseProperty.GetSetMethod(true); ;
MethodAttributes methodAccess = baseSetter.Attributes & MethodAttributes.MemberAccessMask;
string cannotSetException = System.Data.Entity.Strings.EntityProxyTypeInfo_CannotSetEntityCollectionProperty(propertyBuilder.Name, typeBuilder.Name);
MethodBuilder setterBuilder = typeBuilder.DefineMethod("set_" + baseProperty.Name, methodAccess | methodAttributes, null, new Type[] { baseProperty.PropertyType });
ILGenerator generator = setterBuilder.GetILGenerator();
Label instanceEqual = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, _getRelationshipManager);
generator.Emit(OpCodes.Ldstr, navProperty.RelationshipType.FullName);
generator.Emit(OpCodes.Ldstr, navProperty.ToEndMember.Name);
generator.Emit(OpCodes.Callvirt, s_GetRelatedEnd);
generator.Emit(OpCodes.Beq_S, instanceEqual);
generator.Emit(OpCodes.Ldstr, cannotSetException);
generator.Emit(OpCodes.Newobj, s_InvalidOperationConstructor);
generator.Emit(OpCodes.Throw);
generator.MarkLabel(instanceEqual);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Call, baseProperty.GetSetMethod(true));
generator.Emit(OpCodes.Ret);
propertyBuilder.SetSetMethod(setterBuilder);
_collectionProperties.Add(new KeyValuePair<NavigationProperty, PropertyInfo>(navProperty, baseProperty));
}
#region Interface Implementation
private void ImplementIEntityWithChangeTracker(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
_changeTrackerField = typeBuilder.DefineField("_changeTracker", typeof(IEntityChangeTracker), FieldAttributes.Private);
registerField(_changeTrackerField, false);
// Implement EntityMemberChanging(string propertyName)
_entityMemberChanging = typeBuilder.DefineMethod("EntityMemberChanging", MethodAttributes.Private | MethodAttributes.HideBySig, typeof(void), new Type[] { typeof(string) });
ILGenerator generator = _entityMemberChanging.GetILGenerator();
Label methodEnd = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _changeTrackerField);
generator.Emit(OpCodes.Brfalse_S, methodEnd);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _changeTrackerField);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, s_EntityMemberChanging);
generator.MarkLabel(methodEnd);
generator.Emit(OpCodes.Ret);
// Implement EntityMemberChanged(string propertyName)
_entityMemberChanged = typeBuilder.DefineMethod("EntityMemberChanged", MethodAttributes.Private | MethodAttributes.HideBySig, typeof(void), new Type[] { typeof(string) });
generator = _entityMemberChanged.GetILGenerator();
methodEnd = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _changeTrackerField);
generator.Emit(OpCodes.Brfalse_S, methodEnd);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _changeTrackerField);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Callvirt, s_EntityMemberChanged);
generator.MarkLabel(methodEnd);
generator.Emit(OpCodes.Ret);
// Implement IEntityWithChangeTracker.SetChangeTracker(IEntityChangeTracker changeTracker)
MethodBuilder setChangeTracker = typeBuilder.DefineMethod("SetChangeTracker", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final,
typeof(void), new Type[] { typeof(IEntityChangeTracker) });
generator = setChangeTracker.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Stfld, _changeTrackerField);
generator.Emit(OpCodes.Ret);
}
private void ImplementIEntityWithRelationships(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
_relationshipManagerField = typeBuilder.DefineField("_relationshipManager", typeof(RelationshipManager), FieldAttributes.Private);
registerField(_relationshipManagerField, true);
PropertyBuilder relationshipManagerProperty = typeBuilder.DefineProperty("RelationshipManager", System.Reflection.PropertyAttributes.None, typeof(RelationshipManager), Type.EmptyTypes);
// Implement IEntityWithRelationships.get_RelationshipManager
_getRelationshipManager = typeBuilder.DefineMethod("get_RelationshipManager", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.Final,
typeof(RelationshipManager), Type.EmptyTypes);
ILGenerator generator = _getRelationshipManager.GetILGenerator();
Label trueLabel = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _relationshipManagerField);
generator.Emit(OpCodes.Brtrue_S, trueLabel);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, s_CreateRelationshipManager);
generator.Emit(OpCodes.Stfld, _relationshipManagerField);
generator.MarkLabel(trueLabel);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, _relationshipManagerField);
generator.Emit(OpCodes.Ret);
relationshipManagerProperty.SetGetMethod(_getRelationshipManager);
}
#endregion
}
/// <summary>
/// Add a DataContractAttribute to the proxy type, based on one that may have been applied to the base type.
/// </summary>
/// <remarks>
/// <para>
/// From http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractattribute.aspx:
///
/// A data contract has two basic requirements: a stable name and a list of members.
/// The stable name consists of the namespace uniform resource identifier (URI) and the local name of the contract.
/// By default, when you apply the DataContractAttribute to a class,
/// it uses the class name as the local name and the class's namespace (prefixed with "http://schemas.datacontract.org/2004/07/")
/// as the namespace URI. You can override the defaults by setting the Name and Namespace properties.
/// You can also change the namespace by applying the ContractNamespaceAttribute to the namespace.
/// Use this capability when you have an existing type that processes data exactly as you require
/// but has a different namespace and class name from the data contract.
/// By overriding the default values, you can reuse your existing type and have the serialized data conform to the data contract.
/// </para>
/// <para>
/// The first attempt at WCF serialization of proxies involved adding a DataContractAttribute to the proxy type in such a way
/// so that the name and namespace of the proxy's data contract matched that of the base class.
/// This worked when serializing proxy objects for the root type of the DataContractSerializer,
/// but not for proxy objects of types derived from the root type.
///
/// Attempting to add the proxy type to the list of known types failed as well,
/// since the data contract of the proxy type did not match the base type as intended.
/// This was due to the fact that inheritance is captured in the data contract.
/// So while the proxy and base data contracts had the same members, the proxy data contract differed in that is declared itself
/// as an extension of the base data contract. So the data contracts were technically not equivalent.
///
/// The approach used instead is to allow proxy types to have their own DataContract.
/// Users then have at least two options available to them.
///
/// The first approach is to add the proxy types to the list of known types.
///
/// The second approach is to implement an IDataContractSurrogate that can map a proxy instance to a surrogate that does have a data contract
/// equivalent to the base type (you could use the base type itself for this purpose).
/// While more complex to implement, it allows services to hide the use of proxies from clients.
/// This can be quite useful in order to maximize potential interoperability.
/// </para>
/// </remarks>
internal sealed class DataContractImplementor
{
private static readonly ConstructorInfo s_DataContractAttributeConstructor = typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes);
private static readonly PropertyInfo[] s_DataContractProperties = new PropertyInfo[] {
typeof(DataContractAttribute).GetProperty("IsReference")
};
private readonly Type _baseClrType;
private readonly DataContractAttribute _dataContract;
internal DataContractImplementor(EntityType ospaceEntityType)
{
_baseClrType = ospaceEntityType.ClrType;
DataContractAttribute[] attributes = (DataContractAttribute[])_baseClrType.GetCustomAttributes(typeof(DataContractAttribute), false);
if (attributes.Length > 0)
{
_dataContract = attributes[0];
}
}
internal void Implement(TypeBuilder typeBuilder, Action<FieldBuilder, bool> registerField)
{
if (_dataContract != null)
{
// Use base data contract properties to help determine values of properties the proxy type's data contract.
object[] propertyValues = new object[] {
// IsReference
_dataContract.IsReference
};
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(s_DataContractAttributeConstructor, new object[0], s_DataContractProperties, propertyValues);
typeBuilder.SetCustomAttribute(attributeBuilder);
}
}
}
/// <summary>
/// This class determines if the proxied type implements ISerializable with the special serialization constructor.
/// If it does, it adds the appropriate members to the proxy type.
/// </summary>
internal sealed class ISerializableImplementor
{
private readonly Type _baseClrType;
private readonly bool _baseImplementsISerializable;
private readonly bool _canOverride;
private readonly MethodInfo _getObjectDataMethod;
private readonly ConstructorInfo _serializationConstructor;
internal ISerializableImplementor(EntityType ospaceEntityType)
{
_baseClrType = ospaceEntityType.ClrType;
_baseImplementsISerializable = _baseClrType.IsSerializable && typeof(ISerializable).IsAssignableFrom(_baseClrType);
if (_baseImplementsISerializable)
{
// Determine if interface implementation can be overridden.
// Fortunately, there's only one method to check.
InterfaceMapping mapping = _baseClrType.GetInterfaceMap(typeof(ISerializable));
_getObjectDataMethod = mapping.TargetMethods[0];
// Members that implement interfaces must be public, unless they are explicitly implemented, in which case they are private and sealed (at least for C#).
bool canOverrideMethod = (_getObjectDataMethod.IsVirtual && !_getObjectDataMethod.IsFinal) && _getObjectDataMethod.IsPublic;
if (canOverrideMethod)
{
// Determine if proxied type provides the special serialization constructor.
// In order for the proxy class to properly support ISerializable, this constructor must not be private.
_serializationConstructor = _baseClrType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null);
_canOverride = _serializationConstructor != null && (_serializationConstructor.IsPublic || _serializationConstructor.IsFamily || _serializationConstructor.IsFamilyOrAssembly);
}
Debug.Assert(!(_canOverride && (_getObjectDataMethod == null || _serializationConstructor == null)), "Both GetObjectData method and Serialization Constructor must be present when proxy overrides ISerializable implementation.");
}
}
internal bool TypeIsSuitable
{
get
{
// To be suitable,
// either proxied type doesn't implement ISerializable,
// or it does and it can be suitably overridden.
return !_baseImplementsISerializable || _canOverride;
}
}
internal bool TypeImplementsISerializable
{
get
{
return _baseImplementsISerializable;
}
}
internal void Implement(TypeBuilder typeBuilder, IEnumerable<FieldBuilder> serializedFields)
{
if (_baseImplementsISerializable && _canOverride)
{
PermissionSet serializationFormatterPermissions = new PermissionSet(null);
serializationFormatterPermissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));
Type[] parameterTypes = new Type[] { typeof(SerializationInfo), typeof(StreamingContext) };
MethodInfo getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) });
MethodInfo addValue = typeof(SerializationInfo).GetMethod("AddValue", new Type[] { typeof(string), typeof(object), typeof(Type) });
MethodInfo getValue = typeof(SerializationInfo).GetMethod("GetValue", new Type[] { typeof(string), typeof(Type) });
//
// Define GetObjectData method override
//
// [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
// public void GetObjectData(SerializationInfo info, StreamingContext context)
//
MethodBuilder proxyGetObjectData = typeBuilder.DefineMethod(_getObjectDataMethod.Name,
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual,
null,
parameterTypes);
proxyGetObjectData.AddDeclarativeSecurity(SecurityAction.Demand, serializationFormatterPermissions);
{
ILGenerator generator = proxyGetObjectData.GetILGenerator();
// Call SerializationInfo.AddValue to serialize each field value
foreach (FieldBuilder field in serializedFields)
{
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldstr, field.Name);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Ldtoken, field.FieldType);
generator.Emit(OpCodes.Call, getTypeFromHandle);
generator.Emit(OpCodes.Callvirt, addValue);
}
// Emit call to base method
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldarg_2);
generator.Emit(OpCodes.Call, _getObjectDataMethod);
generator.Emit(OpCodes.Ret);
}
//
// Define serialization constructor
//
// [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
// .ctor(SerializationInfo info, StreamingContext context)
//
MethodAttributes constructorAttributes = MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
constructorAttributes |= _serializationConstructor.IsPublic? MethodAttributes.Public : MethodAttributes.Private;
ConstructorBuilder proxyConstructor = typeBuilder.DefineConstructor(constructorAttributes, CallingConventions.Standard | CallingConventions.HasThis, parameterTypes);
proxyConstructor.AddDeclarativeSecurity(SecurityAction.Demand, serializationFormatterPermissions);
{
//Emit call to base serialization constructor
ILGenerator generator = proxyConstructor.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldarg_2);
generator.Emit(OpCodes.Call, _serializationConstructor);
// Call SerializationInfo.GetValue to retrieve the value of each field
foreach (FieldBuilder field in serializedFields)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldstr, field.Name);
generator.Emit(OpCodes.Ldtoken, field.FieldType);
generator.Emit(OpCodes.Call, getTypeFromHandle);
generator.Emit(OpCodes.Callvirt, getValue);
generator.Emit(OpCodes.Castclass, field.FieldType);
generator.Emit(OpCodes.Stfld, field);
}
generator.Emit(OpCodes.Ret);
}
}
}
}
}
|