// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Data; using System.Data.Metadata.Edm; using System.Data.Objects; using System.Linq; using System.Web.Http; namespace Microsoft.Web.Http.Data.EntityFramework { /// /// Internal utility functions for dealing with EF types and metadata /// internal static class ObjectContextUtilities { /// /// Retrieves the corresponding to the given CLR type (where the /// type is an entity or complex type). /// /// /// If no mapping exists for , but one does exist for one of its base /// types, we will return the mapping for the base type. /// /// The /// The CLR type /// The corresponding to that CLR type, or null if the Type /// is not mapped. public static StructuralType GetEdmType(MetadataWorkspace workspace, Type clrType) { if (workspace == null) { throw Error.ArgumentNull("workspace"); } if (clrType == null) { throw Error.ArgumentNull("clrType"); } if (clrType.IsPrimitive || clrType == typeof(object)) { // want to avoid loading searching system assemblies for // types we know aren't entity or complex types return null; } // We first locate the EdmType in "OSpace", which matches the name and namespace of the CLR type EdmType edmType = null; do { if (!workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType)) { // If EF could not find this type, it could be because it is not loaded into // its current workspace. In this case, we explicitly load the assembly containing // the CLR type and try again. workspace.LoadFromAssembly(clrType.Assembly); workspace.TryGetType(clrType.Name, clrType.Namespace, DataSpace.OSpace, out edmType); } } while (edmType == null && (clrType = clrType.BaseType) != typeof(object) && clrType != null); // Next we locate the StructuralType from the EdmType. // This 2-step process is necessary when the types CLR namespace does not match Edm namespace. // Look at the EdmEntityTypeAttribute on the generated entity classes to see this Edm namespace. StructuralType structuralType = null; if (edmType != null && (edmType.BuiltInTypeKind == BuiltInTypeKind.EntityType || edmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)) { workspace.TryGetEdmSpaceType((StructuralType)edmType, out structuralType); } return structuralType; } /// /// Method used to return the current of the specified /// entity. /// /// The /// The entity to return the for /// The current of the specified entity public static EntityState GetEntityState(ObjectContext context, object entity) { if (context == null) { throw Error.ArgumentNull("context"); } if (entity == null) { throw Error.ArgumentNull("entity"); } ObjectStateEntry stateEntry = null; if (!context.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry)) { return EntityState.Detached; } return stateEntry.State; } /// /// Determines if the specified EdmMember is a concurrency timestamp. /// /// Since EF doesn't expose "timestamp" as a first class /// concept, we use the below criteria to infer this for ourselves. /// /// The member to check. /// True or false. public static bool IsConcurrencyTimestamp(EdmMember member) { Facet facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "ConcurrencyMode"); if (facet == null || facet.Value == null || (ConcurrencyMode)facet.Value != ConcurrencyMode.Fixed) { return false; } facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "FixedLength"); if (facet == null || facet.Value == null || !((bool)facet.Value)) { return false; } facet = member.TypeUsage.Facets.SingleOrDefault(p => p.Name == "MaxLength"); if (facet == null || facet.Value == null || (int)facet.Value != 8) { return false; } MetadataProperty md = ObjectContextUtilities.GetStoreGeneratedPattern(member); if (md == null || facet.Value == null || (string)md.Value != "Computed") { return false; } return true; } /// /// Gets the property value from the edm member. /// /// The EdmMember from which to get the StoreGeneratedPattern value. /// The value. public static MetadataProperty GetStoreGeneratedPattern(EdmMember member) { MetadataProperty md; member.MetadataProperties.TryGetValue("http://schemas.microsoft.com/ado/2009/02/edm/annotation:StoreGeneratedPattern", ignoreCase: true, item: out md); return md; } public static ObjectStateEntry AttachAsModifiedInternal(TEntity current, TEntity original, ObjectContext objectContext) { ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(current); stateEntry.ApplyOriginalValues(original); // For any members that don't have RoundtripOriginal applied, EF can't determine modification // state by doing value comparisons. To avoid losing updates in these cases, we must explicitly // mark such members as modified. PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(TEntity)); AttributeCollection attributes = TypeDescriptor.GetAttributes(typeof(TEntity)); bool isRoundtripType = attributes[typeof(RoundtripOriginalAttribute)] != null; foreach (var fieldMetadata in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata) { string memberName = stateEntry.CurrentValues.GetName(fieldMetadata.Ordinal); PropertyDescriptor property = properties[memberName]; // TODO: below we need to replace ExcludeAttribute logic with corresponding // DataContractMember/IgnoreDataMember logic if (property != null && (property.Attributes[typeof(RoundtripOriginalAttribute)] == null && !isRoundtripType) /* && property.Attributes[typeof(ExcludeAttribute)] == null */) { stateEntry.SetModifiedProperty(memberName); } } return stateEntry; } } }