CharlesNRice
CharlesNRice

Reputation: 3259

Mapping from IEdmEntity to CLR

I'm trying to find a way to go from an IEdmEntity to the CLR Type in entity framework. From the casting to ObjectContext to get Metadata. I'm using the DataSpace.OCSpace to get access to the mapping. I believe that is correct but I might have the wrong DataSpace, the DataSpaces are not clear in my head of which does what, even after this blog http://blogs.msdn.com/b/alexj/archive/2009/04/03/tip-10-understanding-entity-framework-jargon.aspx.

In the end I get back System.Data.Entity.Core.Mapping.MappingBase objects which doesn't do much for me. From the debugger it seems I could get access to what I want but those classes are marked internal and I can't cast to them.

Am I making this too hard or is there no way to go from an IEdmModel from Entity Framework back to the CLR Types it maps to?

Adding code to try and make it more clear what I'm working with and trying to get out

    public Type GetIEdmEntityTypeToClrType(IEdmEntityTypeReference edmEntityType, DbContext context)
    {
        var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
        var fullname = edmEntityType.EntityDefinition().FullName();

        EntityType entityType;
        if (metadata.TryGetItem(fullname, DataSpace.CSSpace, out entityType))
        {
            //doesn't hit
        }
        if (metadata.TryGetItem(fullname, DataSpace.CSpace, out entityType))
        {
            //hits but can't get access to CLR Type that it's mapped too.
        }
        if (metadata.TryGetItem(fullname, DataSpace.OCSpace, out entityType))
        {
            //doesn't hit
        }
        if (metadata.TryGetItem(fullname, DataSpace.OSpace, out entityType))
        {
            //doesn't hit
        }
        if (metadata.TryGetItem(fullname, DataSpace.SSpace, out entityType))
        {
            //doesn't hit
        }

        return null;
    }

Upvotes: 9

Views: 3822

Answers (4)

rob3c
rob3c

Reputation: 2086

The *IEdm** interfaces you mentioned in both your question and answer are not used by Entity Framework per se (the EF6 NuGet package has no Microsoft.Data.Edm dependency), but are primarily used with OData service metadata (CSDL). Since entities declared in OData CSDL don't necessarily map to any particular CLR classes, you can only find their CLR types indirectly. (I think that confusion is why Andrew's EF-only answer assumed you had access to an EntityObject.)

Fortunately, when presenting EF entities via OData, there's normally a 1:1 correspondence between the full names of the entities in the CSDL of both the OData service and EF model. Assuming that's the case, your can search using edmEntityType.FullName as you did above, but you have to get the corresponding EF EntityType from the ObjectContext metadata first.

DataSpace.OCSpace in MetadataWorkspace was a reasonable place to look for the mapping, since that's where the Object Space <-> Conceptual Space mappings are stored. But as you discovered, while EF6's mapping API is supposedly public, ObjectTypeMapping and its related classes are still marked internal :(

However, it turns out that you don't need to do any ugly reflection hacks with the internal OCSpace classes! You can get the mapped CLR type directly from your 'hit' in CSpace like this:

var clrTypeMetadataPropName = @"http://schemas.microsoft.com/ado/2013/11/edm/customannotation:ClrType";

var clrType = (Type)
    ((IObjectContextAdapter)context).ObjectContext
    .MetadataWorkspace
    .GetItems<EntityType>(DataSpace.CSpace)
    .Single(s => s.FullName == edmEntityType.FullName())
    .MetadataProperties
    .Single(p => p.Name == clrTypeMetadataPropName )
    .Value;

Sure, it uses the 'internal' ClrType custom annotation key magic string, but everything is done through the current public API. I think that's as close as you can get to an 'official' solution until/unless the rest of the mapping API is made public.

Upvotes: 8

codeworx
codeworx

Reputation: 2745

This should work for entity and property types.

public static Type GetClrTypeFromCSpaceType(
    this MetadataWorkspace workspace, EdmType cType)
{
    var itemCollection = (ObjectItemCollection)workspace.GetItemCollection(DataSpace.OSpace);

    if (cType is StructuralType) {
        var osType = workspace.GetObjectSpaceType((StructuralType)cType);
        return itemCollection.GetClrType(osType);
    } else if (cType is EnumType) {
        var osType = workspace.GetObjectSpaceType((EnumType)cType);
        return itemCollection.GetClrType(osType);
    } else if (cType is PrimitiveType) {
        return ((PrimitiveType)cType).ClrEquivalentType;
    } else if (cType is CollectionType) {
        return workspace.GetClrTypeFromCSpaceType(((CollectionType)cType).TypeUsage.EdmType);
    } else if (cType is RefType) {
        return workspace.GetClrTypeFromCSpaceType(((RefType)cType).ElementType);
    } else if (cType is EdmFunction) {
        return workspace.GetClrTypeFromCSpaceType(((EdmFunction)cType).ReturnParameter.TypeUsage.EdmType);
    }

    return null;
}

usage

var entity = workspace.GetItems<EntityType>(DataSpace.CSpace).First();

var entityType = workspace.GetClrTypeFromCSpaceType(entity);
var propertyType = workspace.GetClrTypeFromCSpaceType(entity.Properties[0].TypeUsage.EdmType);

Upvotes: 4

CharlesNRice
CharlesNRice

Reputation: 3259

Here's what I have that works from my limited testing but really seems like a hack. Hoping someone else finds something better.

    public Type ConvertIEdmEntityTypeToClr(IEdmEntityType edmEntityType, DbContext context)
    {
        var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
        var oSpace = metadata.GetItemCollection(DataSpace.OSpace);
        var typeName = oSpace.GetItems<EntityType>().Select(e => e.FullName).FirstOrDefault(name =>
            {
                var fullname = name + ":" + edmEntityType.FullName();
                MappingBase map;
                return metadata.TryGetItem(fullname, DataSpace.OCSpace, out map);
            });

        return Type.GetType(typeName, false);
    }

Assumes that the OSpace Identity is the same as the CLR name. Also assumes that ID for the OCSpace is the two put together separated by a :.

Upvotes: 1

Andrew
Andrew

Reputation: 3796

I assume you are using Entity Framework 6, where Mapping API is not public. Please have a look at new release of Entity Framework 6.1 RTM:

http://blogs.msdn.com/b/adonet/archive/2014/03/17/ef6-1-0-rtm-available.aspx

More specifically at the Public Mapping API feature:

https://entityframework.codeplex.com/wikipage?title=Public%20Mapping%20API


You should play with metadataWorkspace to get information about entity framework types and their mapping, for example all simple properties of your entity and their CLR types can be retrieved like this:

 EntityObject entity = null; //your entity
 MetadataWorkspace metadataWorkspace = dataContext.MetadataWorkspace;

 Type currentEntityType = entity.GetType();
 EntityType entityType = metadataWorkspace.GetItem<EntityType>(currentEntityType.FullName, DataSpace.OSpace);
 var simpleProperties = entityType.Properties.Where(p => p.DeclaringType == entityType && p.TypeUsage.EdmType is SimpleType);

 foreach (EdmProperty simpleProperty in simpleProperties)
     {
        Console.WriteLine(string.Format("Name: {0} Type: {1}", simpleProperty.Name,simpleProperty.TypeUsage));
     }

Upvotes: 1

Related Questions