Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Information about an Association
/// </summary>
internal sealed class AssociationInfo
{
/// <summary>
/// The name of the association
/// </summary>
public string Name { get; set; }
/// <summary>
/// The key members on the FK side of the association
/// </summary>
public string[] ThisKey { get; set; }
/// <summary>
/// The key members on the non-FK side of the association
/// </summary>
public string[] OtherKey { get; set; }
/// <summary>
/// The foreign key role name for this association
/// </summary>
public string FKRole { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this association can have a
/// multiplicity of zero
/// </summary>
public bool IsRequired { get; set; }
}
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data.Entity;
using System.Web.Http;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Attribute applied to a <see cref="DbDataController{DbContext}"/> that exposes LINQ to Entities mapped
/// Types.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class DbMetadataProviderAttribute : MetadataProviderAttribute
{
private Type _dbContextType;
/// <summary>
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
/// DbContext will be inferred from the <see cref="DataController"/> the
/// attribute is applied to.
/// </summary>
public DbMetadataProviderAttribute()
: base(typeof(LinqToEntitiesMetadataProvider))
{
}
/// <summary>
/// Constructs an attribute for the specified LINQ To Entities
/// DbContext Type.
/// </summary>
/// <param name="dbContextType">The LINQ To Entities ObjectContext Type.</param>
public DbMetadataProviderAttribute(Type dbContextType)
: base(typeof(LinqToEntitiesMetadataProvider))
{
_dbContextType = dbContextType;
}
/// <summary>
/// The Linq To Entities DbContext Type.
/// </summary>
public Type DbContextType
{
get { return _dbContextType; }
}
/// <summary>
/// This method creates an instance of the <see cref="MetadataProvider"/>.
/// </summary>
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
/// <param name="parent">The existing parent metadata provider.</param>
/// <returns>The metadata provider.</returns>
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
{
if (controllerType == null)
{
throw Error.ArgumentNull("controllerType");
}
if (_dbContextType == null)
{
_dbContextType = GetContextType(controllerType);
}
if (!typeof(DbContext).IsAssignableFrom(_dbContextType))
{
throw Error.InvalidOperation(Resource.InvalidDbMetadataProviderSpecification, _dbContextType);
}
return new LinqToEntitiesMetadataProvider(_dbContextType, parent, true);
}
/// <summary>
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
/// </summary>
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
/// <returns>The type of the object context.</returns>
private static Type GetContextType(Type dataControllerType)
{
Type efDataControllerType = dataControllerType.BaseType;
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(DbDataController<>))
{
if (efDataControllerType == typeof(object))
{
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(DbMetadataProviderAttribute).Name, dataControllerType.Name, typeof(DbDataController<>).Name);
}
efDataControllerType = efDataControllerType.BaseType;
}
return efDataControllerType.GetGenericArguments()[0];
}
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Metadata.Edm;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
internal class LinqToEntitiesMetadataProvider : MetadataProvider
{
private static ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext> _tdpContextMap = new ConcurrentDictionary<Type, LinqToEntitiesTypeDescriptionContext>();
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
private readonly bool _isDbContext;
private Dictionary<Type, ICustomTypeDescriptor> _descriptors = new Dictionary<Type, ICustomTypeDescriptor>();
public LinqToEntitiesMetadataProvider(Type contextType, MetadataProvider parent, bool isDbContext)
: base(parent)
{
_isDbContext = isDbContext;
_typeDescriptionContext = _tdpContextMap.GetOrAdd(contextType, type =>
{
// create and cache a context for this provider type
return new LinqToEntitiesTypeDescriptionContext(contextType, _isDbContext);
});
}
/// <summary>
/// Returns a custom type descriptor for the specified type (either an entity or complex type).
/// </summary>
/// <param name="objectType">Type of object for which we need the descriptor</param>
/// <param name="parent">The parent type descriptor</param>
/// <returns>Custom type description for the specified type</returns>
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, ICustomTypeDescriptor parent)
{
// No need to deal with concurrency... Worst case scenario we have multiple
// instances of this thing.
ICustomTypeDescriptor td = null;
if (!_descriptors.TryGetValue(objectType, out td))
{
// call into base so the TDs are chained
parent = base.GetTypeDescriptor(objectType, parent);
StructuralType edmType = _typeDescriptionContext.GetEdmType(objectType);
if (edmType != null &&
(edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
{
// only add an LTE TypeDescriptor if the type is an EF Entity or ComplexType
td = new LinqToEntitiesTypeDescriptor(_typeDescriptionContext, edmType, parent);
}
else
{
td = parent;
}
_descriptors[objectType] = td;
}
return td;
}
public override bool LookUpIsEntityType(Type type)
{
StructuralType edmType = _typeDescriptionContext.GetEdmType(type);
if (edmType != null && edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data.Objects;
using System.Web.Http;
using Microsoft.Web.Http.Data.Metadata;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Attribute applied to a <see cref="DataController"/> that exposes LINQ to Entities mapped
/// Types.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class LinqToEntitiesMetadataProviderAttribute : MetadataProviderAttribute
{
private Type _objectContextType;
/// <summary>
/// Default constructor. Using this constructor, the Type of the LINQ To Entities
/// ObjectContext will be inferred from the <see cref="DataController"/> the
/// attribute is applied to.
/// </summary>
public LinqToEntitiesMetadataProviderAttribute()
: base(typeof(LinqToEntitiesMetadataProvider))
{
}
/// <summary>
/// Constructs an attribute for the specified LINQ To Entities
/// ObjectContext Type.
/// </summary>
/// <param name="objectContextType">The LINQ To Entities ObjectContext Type.</param>
public LinqToEntitiesMetadataProviderAttribute(Type objectContextType)
: base(typeof(LinqToEntitiesMetadataProvider))
{
_objectContextType = objectContextType;
}
/// <summary>
/// The Linq To Entities ObjectContext Type.
/// </summary>
public Type ObjectContextType
{
get { return _objectContextType; }
}
/// <summary>
/// This method creates an instance of the <see cref="MetadataProvider"/>.
/// </summary>
/// <param name="controllerType">The <see cref="DataController"/> Type to create a metadata provider for.</param>
/// <param name="parent">The existing parent metadata provider.</param>
/// <returns>The metadata provider.</returns>
public override MetadataProvider CreateProvider(Type controllerType, MetadataProvider parent)
{
if (controllerType == null)
{
throw Error.ArgumentNull("controllerType");
}
if (_objectContextType == null)
{
_objectContextType = GetContextType(controllerType);
}
if (!typeof(ObjectContext).IsAssignableFrom(_objectContextType))
{
throw Error.InvalidOperation(Resource.InvalidLinqToEntitiesMetadataProviderSpecification, _objectContextType);
}
return new LinqToEntitiesMetadataProvider(_objectContextType, parent, false);
}
/// <summary>
/// Extracts the context type from the specified <paramref name="dataControllerType"/>.
/// </summary>
/// <param name="dataControllerType">A LINQ to Entities data controller type.</param>
/// <returns>The type of the object context.</returns>
private static Type GetContextType(Type dataControllerType)
{
Type efDataControllerType = dataControllerType.BaseType;
while (!efDataControllerType.IsGenericType || efDataControllerType.GetGenericTypeDefinition() != typeof(LinqToEntitiesDataController<>))
{
if (efDataControllerType == typeof(object))
{
throw Error.InvalidOperation(Resource.InvalidMetadataProviderSpecification, typeof(LinqToEntitiesMetadataProviderAttribute).Name, dataControllerType.Name, typeof(LinqToEntitiesDataController<>).Name);
}
efDataControllerType = efDataControllerType.BaseType;
}
return efDataControllerType.GetGenericArguments()[0];
}
}
}

View File

@@ -0,0 +1,150 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Data.Metadata.Edm;
using System.Globalization;
using System.Linq;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Metadata context for LINQ To Entities controllers
/// </summary>
internal class LinqToEntitiesTypeDescriptionContext : TypeDescriptionContextBase
{
private readonly Type _contextType;
private readonly bool _isDbContext;
private ConcurrentDictionary<string, AssociationInfo> _associationMap = new ConcurrentDictionary<string, AssociationInfo>();
private MetadataWorkspace _metadataWorkspace;
/// <summary>
/// Constructor that accepts a LINQ To Entities context type
/// </summary>
/// <param name="contextType">The ObjectContext Type</param>
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
public LinqToEntitiesTypeDescriptionContext(Type contextType, bool isDbContext)
{
if (contextType == null)
{
throw Error.ArgumentNull("contextType");
}
_contextType = contextType;
_isDbContext = isDbContext;
}
/// <summary>
/// Gets the MetadataWorkspace for the context
/// </summary>
public MetadataWorkspace MetadataWorkspace
{
get
{
if (_metadataWorkspace == null)
{
// we only support embedded mappings
_metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspace(_contextType, _isDbContext);
}
return _metadataWorkspace;
}
}
/// <summary>
/// Returns the <see cref="StructuralType"/> that corresponds to the given CLR type
/// </summary>
/// <param name="clrType">The CLR type</param>
/// <returns>The StructuralType that corresponds to the given CLR type</returns>
public StructuralType GetEdmType(Type clrType)
{
return ObjectContextUtilities.GetEdmType(MetadataWorkspace, clrType);
}
/// <summary>
/// Returns the association information for the specified navigation property.
/// </summary>
/// <param name="navigationProperty">The navigation property to return association information for</param>
/// <returns>The association info</returns>
internal AssociationInfo GetAssociationInfo(NavigationProperty navigationProperty)
{
return _associationMap.GetOrAdd(navigationProperty.RelationshipType.FullName, associationName =>
{
AssociationType associationType = (AssociationType)navigationProperty.RelationshipType;
if (!associationType.ReferentialConstraints.Any())
{
// We only support EF models where FK info is part of the model.
throw Error.NotSupported(Resource.LinqToEntitiesProvider_UnableToRetrieveAssociationInfo, associationName);
}
string toRoleName = associationType.ReferentialConstraints[0].ToRole.Name;
AssociationInfo associationInfo = new AssociationInfo()
{
FKRole = toRoleName,
Name = GetAssociationName(navigationProperty, toRoleName),
ThisKey = associationType.ReferentialConstraints[0].ToProperties.Select(p => p.Name).ToArray(),
OtherKey = associationType.ReferentialConstraints[0].FromProperties.Select(p => p.Name).ToArray(),
IsRequired = associationType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.One
};
return associationInfo;
});
}
/// <summary>
/// Creates an AssociationAttribute for the specified navigation property
/// </summary>
/// <param name="navigationProperty">The navigation property that corresponds to the association (it identifies the end points)</param>
/// <returns>A new AssociationAttribute that describes the given navigation property association</returns>
internal AssociationAttribute CreateAssociationAttribute(NavigationProperty navigationProperty)
{
AssociationInfo assocInfo = GetAssociationInfo(navigationProperty);
bool isForeignKey = navigationProperty.FromEndMember.Name == assocInfo.FKRole;
string thisKey;
string otherKey;
if (isForeignKey)
{
thisKey = String.Join(",", assocInfo.ThisKey);
otherKey = String.Join(",", assocInfo.OtherKey);
}
else
{
otherKey = String.Join(",", assocInfo.ThisKey);
thisKey = String.Join(",", assocInfo.OtherKey);
}
AssociationAttribute assocAttrib = new AssociationAttribute(assocInfo.Name, thisKey, otherKey);
assocAttrib.IsForeignKey = isForeignKey;
return assocAttrib;
}
/// <summary>
/// Returns a unique association name for the specified navigation property.
/// </summary>
/// <param name="navigationProperty">The navigation property</param>
/// <param name="foreignKeyRoleName">The foreign key role name for the property's association</param>
/// <returns>A unique association name for the specified navigation property.</returns>
private string GetAssociationName(NavigationProperty navigationProperty, string foreignKeyRoleName)
{
RelationshipEndMember fromMember = navigationProperty.FromEndMember;
RelationshipEndMember toMember = navigationProperty.ToEndMember;
RefType toRefType = toMember.TypeUsage.EdmType as RefType;
EntityType toEntityType = toRefType.ElementType as EntityType;
RefType fromRefType = fromMember.TypeUsage.EdmType as RefType;
EntityType fromEntityType = fromRefType.ElementType as EntityType;
bool isForeignKey = navigationProperty.FromEndMember.Name == foreignKeyRoleName;
string fromTypeName = isForeignKey ? fromEntityType.Name : toEntityType.Name;
string toTypeName = isForeignKey ? toEntityType.Name : fromEntityType.Name;
// names are always formatted non-FK side type name followed by FK side type name
string associationName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", toTypeName, fromTypeName);
associationName = MakeUniqueName(associationName, _associationMap.Values.Select(p => p.Name));
return associationName;
}
}
}

View File

@@ -0,0 +1,274 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// CustomTypeDescriptor for LINQ To Entities
/// </summary>
internal class LinqToEntitiesTypeDescriptor : TypeDescriptorBase
{
private readonly LinqToEntitiesTypeDescriptionContext _typeDescriptionContext;
private readonly StructuralType _edmType;
private readonly EdmMember _timestampMember;
private readonly HashSet<EdmMember> _foreignKeyMembers;
private readonly bool _keyIsEditable;
/// <summary>
/// Constructor taking a metadata context, an structural type, and a parent custom type descriptor
/// </summary>
/// <param name="typeDescriptionContext">The <see cref="LinqToEntitiesTypeDescriptionContext"/> context.</param>
/// <param name="edmType">The <see cref="StructuralType"/> type (can be an entity or complex type).</param>
/// <param name="parent">The parent custom type descriptor.</param>
public LinqToEntitiesTypeDescriptor(LinqToEntitiesTypeDescriptionContext typeDescriptionContext, StructuralType edmType, ICustomTypeDescriptor parent)
: base(parent)
{
_typeDescriptionContext = typeDescriptionContext;
_edmType = edmType;
EdmMember[] timestampMembers = _edmType.Members.Where(p => ObjectContextUtilities.IsConcurrencyTimestamp(p)).ToArray();
if (timestampMembers.Length == 1)
{
_timestampMember = timestampMembers[0];
}
if (edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
// if any FK member of any association is also part of the primary key, then the key cannot be marked
// Editable(false)
EntityType entityType = (EntityType)edmType;
_foreignKeyMembers = new HashSet<EdmMember>(entityType.NavigationProperties.SelectMany(p => p.GetDependentProperties()));
foreach (EdmProperty foreignKeyMember in _foreignKeyMembers)
{
if (entityType.KeyMembers.Contains(foreignKeyMember))
{
_keyIsEditable = true;
break;
}
}
}
}
/// <summary>
/// Gets the metadata context
/// </summary>
public LinqToEntitiesTypeDescriptionContext TypeDescriptionContext
{
get { return _typeDescriptionContext; }
}
/// <summary>
/// Gets the Edm type
/// </summary>
private StructuralType EdmType
{
get { return _edmType; }
}
/// <summary>
/// Returns a collection of all the <see cref="Attribute"/>s we infer from the metadata associated
/// with the metadata member corresponding to the given property descriptor
/// </summary>
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine</param>
/// <returns>A collection of attributes inferred from the metadata in the given descriptor.</returns>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "TODO refactor")]
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "TODO refactor")]
protected override IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd)
{
List<Attribute> attributes = new List<Attribute>();
// Exclude any EntityState, EntityReference, etc. members
if (ShouldExcludeEntityMember(pd))
{
// for these members, we don't want to do any attribute inference
return attributes.ToArray();
}
EditableAttribute editableAttribute = null;
bool inferRoundtripOriginalAttribute = false;
bool hasKeyAttribute = (pd.Attributes[typeof(KeyAttribute)] != null);
bool isEntity = EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
if (isEntity)
{
EntityType entityType = (EntityType)EdmType;
EdmMember keyMember = entityType.KeyMembers.SingleOrDefault(k => k.Name == pd.Name);
if (keyMember != null && !hasKeyAttribute)
{
attributes.Add(new KeyAttribute());
hasKeyAttribute = true;
}
}
EdmProperty member = EdmType.Members.SingleOrDefault(p => p.Name == pd.Name) as EdmProperty;
if (member != null)
{
if (hasKeyAttribute)
{
// key members must always be roundtripped
inferRoundtripOriginalAttribute = true;
// key members that aren't also FK members are non-editable (but allow an initial value)
if (!_keyIsEditable)
{
editableAttribute = new EditableAttribute(false) { AllowInitialValue = true };
}
}
// Check if the member is DB generated and add the DatabaseGeneratedAttribute to it if not already present.
if (pd.Attributes[typeof(DatabaseGeneratedAttribute)] == null)
{
MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member);
if (md != null)
{
if ((string)md.Value == "Computed")
{
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Computed));
}
else if ((string)md.Value == "Identity")
{
attributes.Add(new DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity));
}
}
}
// Add implicit ConcurrencyCheck attribute to metadata if ConcurrencyMode is anything other than ConcurrencyMode.None
Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode");
if (facet != null && facet.Value != null && (ConcurrencyMode)facet.Value != ConcurrencyMode.None &&
pd.Attributes[typeof(ConcurrencyCheckAttribute)] == null)
{
attributes.Add(new ConcurrencyCheckAttribute());
inferRoundtripOriginalAttribute = true;
}
bool isStringType = pd.PropertyType == typeof(string) || pd.PropertyType == typeof(char[]);
// Add Required attribute to metdata if the member cannot be null and it is either a reference type or a Nullable<T>
if (!member.Nullable && (!pd.PropertyType.IsValueType || IsNullableType(pd.PropertyType)) &&
pd.Attributes[typeof(RequiredAttribute)] == null)
{
attributes.Add(new RequiredAttribute());
}
if (isStringType &&
pd.Attributes[typeof(StringLengthAttribute)] == null)
{
facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength");
if (facet != null && facet.Value != null && facet.Value.GetType() == typeof(int))
{
// need to test for Type int, since the value can also be of type
// System.Data.Metadata.Edm.EdmConstants.Unbounded
int maxLength = (int)facet.Value;
attributes.Add(new StringLengthAttribute(maxLength));
}
}
bool hasTimestampAttribute = (pd.Attributes[typeof(TimestampAttribute)] != null);
if (_timestampMember == member && !hasTimestampAttribute)
{
attributes.Add(new TimestampAttribute());
hasTimestampAttribute = true;
}
// All members marked with TimestampAttribute (inferred or explicit) need to
// have [Editable(false)] and [RoundtripOriginal] applied
if (hasTimestampAttribute)
{
inferRoundtripOriginalAttribute = true;
if (editableAttribute == null)
{
editableAttribute = new EditableAttribute(false);
}
}
// Add RTO to this member if required. If this type has a timestamp
// member that member should be the ONLY member we apply RTO to.
// Dont apply RTO if it is an association member.
bool isForeignKeyMember = _foreignKeyMembers != null && _foreignKeyMembers.Contains(member);
if ((_timestampMember == null || _timestampMember == member) &&
(inferRoundtripOriginalAttribute || isForeignKeyMember) &&
pd.Attributes[typeof(AssociationAttribute)] == null)
{
if (pd.Attributes[typeof(RoundtripOriginalAttribute)] == null)
{
attributes.Add(new RoundtripOriginalAttribute());
}
}
}
// Add the Editable attribute if required
if (editableAttribute != null && pd.Attributes[typeof(EditableAttribute)] == null)
{
attributes.Add(editableAttribute);
}
if (isEntity)
{
AddAssociationAttributes(pd, attributes);
}
return attributes.ToArray();
}
/// <summary>
/// Determines whether the specified property is an Entity member that
/// should be excluded.
/// </summary>
/// <param name="pd">The property to check.</param>
/// <returns>True if the property should be excluded, false otherwise.</returns>
internal static bool ShouldExcludeEntityMember(PropertyDescriptor pd)
{
// exclude EntityState members
if (pd.PropertyType == typeof(EntityState) &&
(pd.ComponentType == typeof(EntityObject) || typeof(IEntityChangeTracker).IsAssignableFrom(pd.ComponentType)))
{
return true;
}
// exclude entity reference properties
if (typeof(EntityReference).IsAssignableFrom(pd.PropertyType))
{
return true;
}
return false;
}
/// <summary>
/// Add AssociationAttribute if required for the specified property
/// </summary>
/// <param name="pd">The property</param>
/// <param name="attributes">The list of attributes to append to</param>
private void AddAssociationAttributes(PropertyDescriptor pd, List<Attribute> attributes)
{
EntityType entityType = (EntityType)EdmType;
NavigationProperty navProperty = entityType.NavigationProperties.Where(n => n.Name == pd.Name).SingleOrDefault();
if (navProperty != null)
{
bool isManyToMany = navProperty.RelationshipType.RelationshipEndMembers[0].RelationshipMultiplicity == RelationshipMultiplicity.Many &&
navProperty.RelationshipType.RelationshipEndMembers[1].RelationshipMultiplicity == RelationshipMultiplicity.Many;
if (!isManyToMany)
{
AssociationAttribute assocAttrib = (AssociationAttribute)pd.Attributes[typeof(System.ComponentModel.DataAnnotations.AssociationAttribute)];
if (assocAttrib == null)
{
assocAttrib = TypeDescriptionContext.CreateAssociationAttribute(navProperty);
attributes.Add(assocAttrib);
}
}
}
}
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
internal class MetadataPropertyDescriptorWrapper : PropertyDescriptor
{
private readonly PropertyDescriptor _descriptor;
public MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] attrs)
: base(descriptor, attrs)
{
_descriptor = descriptor;
}
public override Type ComponentType
{
get { return _descriptor.ComponentType; }
}
public override bool IsReadOnly
{
get { return _descriptor.IsReadOnly; }
}
public override Type PropertyType
{
get { return _descriptor.PropertyType; }
}
public override bool SupportsChangeEvents
{
get { return _descriptor.SupportsChangeEvents; }
}
public override void AddValueChanged(object component, EventHandler handler)
{
_descriptor.AddValueChanged(component, handler);
}
public override bool CanResetValue(object component)
{
return _descriptor.CanResetValue(component);
}
public override object GetValue(object component)
{
return _descriptor.GetValue(component);
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
_descriptor.RemoveValueChanged(component, handler);
}
public override void ResetValue(object component)
{
_descriptor.ResetValue(component);
}
public override void SetValue(object component, object value)
{
_descriptor.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return _descriptor.ShouldSerializeValue(component);
}
}
}

View File

@@ -0,0 +1,210 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Data.Mapping;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Http;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// EF metadata utilities class.
/// </summary>
internal static class MetadataWorkspaceUtilities
{
/// <summary>
/// Creates a metadata workspace for the specified context.
/// </summary>
/// <param name="contextType">The type of the object context.</param>
/// <param name="isDbContext">Set to <c>true</c> if context is a database context.</param>
/// <returns>The metadata workspace.</returns>
public static MetadataWorkspace CreateMetadataWorkspace(Type contextType, bool isDbContext)
{
MetadataWorkspace metadataWorkspace = null;
if (!isDbContext)
{
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(ObjectContext));
}
else
{
metadataWorkspace = MetadataWorkspaceUtilities.CreateMetadataWorkspaceFromResources(contextType, typeof(System.Data.Entity.DbContext));
if (metadataWorkspace == null && typeof(System.Data.Entity.DbContext).IsAssignableFrom(contextType))
{
if (contextType.GetConstructor(Type.EmptyTypes) == null)
{
throw Error.InvalidOperation(Resource.DefaultCtorNotFound, contextType.FullName);
}
try
{
System.Data.Entity.DbContext dbContext = Activator.CreateInstance(contextType) as System.Data.Entity.DbContext;
ObjectContext objectContext = (dbContext as System.Data.Entity.Infrastructure.IObjectContextAdapter).ObjectContext;
metadataWorkspace = objectContext.MetadataWorkspace;
}
catch (Exception efException)
{
throw Error.InvalidOperation(efException, Resource.MetadataWorkspaceNotFound, contextType.FullName);
}
}
}
if (metadataWorkspace == null)
{
throw Error.InvalidOperation(Resource.LinqToEntitiesProvider_UnableToRetrieveMetadata, contextType.Name);
}
else
{
return metadataWorkspace;
}
}
/// <summary>
/// Creates the MetadataWorkspace for the given context type and base context type.
/// </summary>
/// <param name="contextType">The type of the context.</param>
/// <param name="baseContextType">The base context type (DbContext or ObjectContext).</param>
/// <returns>The generated <see cref="MetadataWorkspace"/></returns>
public static MetadataWorkspace CreateMetadataWorkspaceFromResources(Type contextType, Type baseContextType)
{
// get the set of embedded mapping resources for the target assembly and create
// a metadata workspace info for each group
IEnumerable<string> metadataResourcePaths = FindMetadataResources(contextType.Assembly);
IEnumerable<MetadataWorkspaceInfo> workspaceInfos = GetMetadataWorkspaceInfos(metadataResourcePaths);
// Search for the correct EntityContainer by name and if found, create
// a comlete MetadataWorkspace and return it
foreach (var workspaceInfo in workspaceInfos)
{
EdmItemCollection edmItemCollection = new EdmItemCollection(workspaceInfo.Csdl);
Type currentType = contextType;
while (currentType != baseContextType && currentType != typeof(object))
{
EntityContainer container;
if (edmItemCollection.TryGetEntityContainer(currentType.Name, out container))
{
StoreItemCollection store = new StoreItemCollection(workspaceInfo.Ssdl);
StorageMappingItemCollection mapping = new StorageMappingItemCollection(edmItemCollection, store, workspaceInfo.Msl);
MetadataWorkspace workspace = new MetadataWorkspace();
workspace.RegisterItemCollection(edmItemCollection);
workspace.RegisterItemCollection(store);
workspace.RegisterItemCollection(mapping);
workspace.RegisterItemCollection(new ObjectItemCollection());
return workspace;
}
currentType = currentType.BaseType;
}
}
return null;
}
/// <summary>
/// Gets the specified resource paths as metadata workspace info objects.
/// </summary>
/// <param name="resourcePaths">The metadata resource paths.</param>
/// <returns>The metadata workspace info objects.</returns>
private static IEnumerable<MetadataWorkspaceInfo> GetMetadataWorkspaceInfos(IEnumerable<string> resourcePaths)
{
// for file paths, you would want to group without the path or the extension like Path.GetFileNameWithoutExtension, but resource names can contain
// forbidden path chars, so don't use it on resource names
foreach (var group in resourcePaths.GroupBy(p => p.Substring(0, p.LastIndexOf('.')), StringComparer.InvariantCultureIgnoreCase))
{
yield return MetadataWorkspaceInfo.Create(group);
}
}
/// <summary>
/// Find all the EF metadata resources.
/// </summary>
/// <param name="assembly">The assembly to find the metadata resources in.</param>
/// <returns>The metadata paths that were found.</returns>
private static IEnumerable<string> FindMetadataResources(Assembly assembly)
{
List<string> result = new List<string>();
foreach (string name in assembly.GetManifestResourceNames())
{
if (MetadataWorkspaceInfo.IsMetadata(name))
{
result.Add(String.Format(CultureInfo.InvariantCulture, "res://{0}/{1}", assembly.FullName, name));
}
}
return result;
}
/// <summary>
/// Represents the paths for a single metadata workspace.
/// </summary>
private class MetadataWorkspaceInfo
{
private const string CsdlExtension = ".csdl";
private const string MslExtension = ".msl";
private const string SsdlExtension = ".ssdl";
public MetadataWorkspaceInfo(string csdlPath, string mslPath, string ssdlPath)
{
if (csdlPath == null)
{
throw Error.ArgumentNull("csdlPath");
}
if (mslPath == null)
{
throw Error.ArgumentNull("mslPath");
}
if (ssdlPath == null)
{
throw Error.ArgumentNull("ssdlPath");
}
Csdl = csdlPath;
Msl = mslPath;
Ssdl = ssdlPath;
}
public string Csdl { get; private set; }
public string Msl { get; private set; }
public string Ssdl { get; private set; }
public static MetadataWorkspaceInfo Create(IEnumerable<string> paths)
{
string csdlPath = null;
string mslPath = null;
string ssdlPath = null;
foreach (string path in paths)
{
if (path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase))
{
csdlPath = path;
}
else if (path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase))
{
mslPath = path;
}
else if (path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase))
{
ssdlPath = path;
}
}
return new MetadataWorkspaceInfo(csdlPath, mslPath, ssdlPath);
}
public static bool IsMetadata(string path)
{
return path.EndsWith(CsdlExtension, StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(MslExtension, StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(SsdlExtension, StringComparison.OrdinalIgnoreCase);
}
}
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// Base class for LTS and EF type description contexts
/// </summary>
internal abstract class TypeDescriptionContextBase
{
/// <summary>
/// Given a suggested name and a collection of existing names, this method
/// creates a unique name by appending a numerix suffix as required.
/// </summary>
/// <param name="suggested">The desired name</param>
/// <param name="existing">Collection of existing names</param>
/// <returns>The unique name</returns>
protected static string MakeUniqueName(string suggested, IEnumerable<string> existing)
{
int i = 1;
string currSuggestion = suggested;
while (existing.Contains(currSuggestion))
{
currSuggestion = suggested + (i++).ToString(CultureInfo.InvariantCulture);
}
return currSuggestion;
}
}
}

View File

@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Microsoft.Web.Http.Data.EntityFramework.Metadata
{
/// <summary>
/// CustomTypeDescriptor base type shared by LINQ To SQL and LINQ To Entities
/// </summary>
internal abstract class TypeDescriptorBase : CustomTypeDescriptor
{
private PropertyDescriptorCollection _properties;
/// <summary>
/// Main constructor that accepts the parent custom type descriptor
/// </summary>
/// <param name="parent">The parent custom type descriptor.</param>
public TypeDescriptorBase(ICustomTypeDescriptor parent)
: base(parent)
{
}
/// <summary>
/// Override of the <see cref="CustomTypeDescriptor.GetProperties()"/> to obtain the list
/// of properties for this type.
/// </summary>
/// <remarks>
/// This method is overridden so that it can merge this class's parent attributes with those
/// it infers from the DAL-specific attributes.
/// </remarks>
/// <returns>A list of properties for this type</returns>
public sealed override PropertyDescriptorCollection GetProperties()
{
// No need to lock anything... Worst case scenario we create the properties multiple times.
if (_properties == null)
{
// Get properties from our parent
PropertyDescriptorCollection originalCollection = base.GetProperties();
bool customDescriptorsCreated = false;
List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();
// for every property exposed by our parent, see if we have additional metadata to add
foreach (PropertyDescriptor propDescriptor in originalCollection)
{
Attribute[] newMetadata = GetMemberAttributes(propDescriptor).ToArray();
if (newMetadata.Length > 0)
{
tempPropertyDescriptors.Add(new MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata));
customDescriptorsCreated = true;
}
else
{
tempPropertyDescriptors.Add(propDescriptor);
}
}
if (customDescriptorsCreated)
{
_properties = new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);
}
else
{
_properties = originalCollection;
}
}
return _properties;
}
/// <summary>
/// Abstract method specific DAL implementations must override to return the
/// list of RIA <see cref="Attribute"/>s implied by their DAL-specific attributes
/// </summary>
/// <param name="pd">A <see cref="PropertyDescriptor"/> to examine.</param>
/// <returns>A list of RIA attributes implied by the DAL specific attributes</returns>
protected abstract IEnumerable<Attribute> GetMemberAttributes(PropertyDescriptor pd);
/// <summary>
/// Returns <c>true</c> if the given type is a <see cref="Nullable"/>
/// </summary>
/// <param name="type">The type to test</param>
/// <returns><c>true</c> if the given type is a nullable type</returns>
public static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
}