18

I have application which uses EF-CodeFirst 5 (dll ver 4.4.0.0, on .net 4.0).

I need to be able to read entity metadata, so that I can, for a given entry type get following information:

  • which properties are one-many relations (referenced entities)
  • which properties are many-one relations (collections of entities referencing current one)
  • also nice but not absolutely necessary: which properties are many-many relations (collections of relations)

I can get this info by writing foreach loops on lists of properties and then "recognizing" them by relying on all of the references being virtual, but I feel that is not "proper" way. I know that EdmxWriter can provide that information in xml format, but it does so by accessing InternalContext which is not publicly accessible and I want to get strongly typed lists/arrays directly, without using that xml. Which API should I use (if there is one for this, it seems that I cannot find it)?

Goran Obradovic
  • 8,951
  • 9
  • 50
  • 79
  • 1
    There are now plans to improve the Metadata API: https://entityframework.codeplex.com/workitem/1471 More here: http://romiller.com/2013/09/24/ef-code-first-mapping-between-types-tables/ – Colin Sep 26 '13 at 05:20

1 Answers1

27

Gorane, this should get you started...
(I haven't played much with it - it takes a bit of experimenting in the debugger to see which properties / info and how to get it)

using (var db = new MyDbContext())
{
    var objectContext = ((IObjectContextAdapter)db).ObjectContext;
    var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
    foreach (var set in container.BaseEntitySets)
    {
        // set.ElementType.
        foreach (var metaproperty in set.MetadataProperties)
        {
            // metaproperty.
        }
    }

    // ...or... 

    var keyName = objectContext
        .MetadataWorkspace
        .GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace)
        .BaseEntitySets
        .First(meta => meta.ElementType.Name == "Question")
        .ElementType
        .KeyMembers
        .Select(k => k.Name)
        .FirstOrDefault();
}

and more specifically...

foreach (var set in container.BaseEntitySets)
{
    var dependents = ((EntitySet)(set)).ForeignKeyDependents;
    var principals = ((EntitySet)(set)).ForeignKeyPrincipals;
    var navigationProperties = ((EntityType)(set.ElementType)).NavigationProperties;
    foreach (var nav in navigationProperties)
    {
        // nav.RelationshipType;
    }
}

Some of these properties seem to not be exposed to 'general public' so you'd need to use reflection - or find some smarter way - but a good deal of info is in there.



And some more info in these links...

How to get first EntityKey Name for an Entity in EF4

How can I extract the database table and column name for a property on an EF4 entity?


EDIT: Using your list of navigationProperties as starting point, I got everything I needed like this:

        ManyToManyReferences = navigationProperties.Where(np =>
            np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
            np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
            .ToList();

        OneToManyReferences = navigationProperties.Where(np =>
            (np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
            np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne) &&
            np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
            .ToList();

        ManyToOneReferences = navigationProperties.Where(np =>
            np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
            (np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One ||
            np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
            .Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
            .ToList();

        OneToOneReferences = navigationProperties.Where(np =>
            np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One &&
            np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One)
            .Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
            .ToList();

CreateLambdaExpression method is not my courtesy, credits go to Jon Skeet, code was created with help of this answer

Here is my CreateLambdaExpression method:

public static Expression<Func<TEntity, object>> CreateLambdaExpression<TEntity>(string propertyName)
{
    ParameterExpression parameter = Expression.Parameter(typeof (TEntity), typeof (TEntity).Name);
    Expression property = Expression.Property(parameter, propertyName);

    return Expression.Lambda<Func<TEntity, object>>(property, new[] {parameter});
}
Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • 1
    Thanks, this got me started, and I got what I need (without reflexion), expanded your answer to show how. – Goran Obradovic Mar 31 '13 at 20:06
  • 1
    that's great, thank you too - I'm going to keep this in my links - I had a few uses for that before - but never did 'elaborate' on that in too many details, this is very helpful piece of code. And a good question. – NSGaga-mostly-inactive Mar 31 '13 at 20:08
  • 1
    It seems that `ForeignKeyDependents` and `ForeignKeyPrincipals` is marked `internal` in `EntitySet` for EF6. – Kirk Woll Nov 16 '14 at 21:04
  • @GoranObradovic: What purpose does `CreateLambdaExpression` serve here? If you just want filtered lists of `NavigationProperties`, is it needed at all? If I could see the expression tree you ended up with I'd be able to understand it better. Could you edit the answer again to include the final `CreateLambdaExpression` code you ended up with? It's still unclear to me. Thanks. – InteXX Feb 20 '15 at 04:19
  • @InteXX answer already had link to another answer that gave me that method, but I included my simplified version I ended up using, it is just selector for property with given name. Anyway, try to use newer EF versions, this one sucks :) – Goran Obradovic Feb 20 '15 at 07:58
  • @GoranObradovic: I would absolutely LOVE to do this with my current EF6.1 DbContext. I thought this was the way to work Metadata with 6.1. Apparently not? I'm not finding documentation. – InteXX Feb 20 '15 at 10:25
  • @GoranObradovic: Got it, thanks. In fact I've been using some of that. Here's what I'm [trying to do](http://stackoverflow.com/q/28630331/722393). – InteXX Feb 20 '15 at 13:41
  • Instead of finding the primary key, or navigation properties, is there a way to figure out which properties have (unique) indexes on them? – Rudey Apr 12 '19 at 18:58