// 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;
}
}
}