Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Code First: How can I determine the foreign key property used for a navigation property at runtime?

I have an Entity Framework Code First DbContext with the following entities configured. In this example class Bar is a child of class Foo.

public class Foo
{
    public Guid Id { get; set; }

    public virtual ICollection<Bar> Bars { get; set; }
}

public class Bar
{
    public Guid Id { get; set; }

    public Guid FooId { get; set; }

    public virtual Foo Foo { get; set; }
}

Now I know that internally, Entity Framework understands that the relationship between Foo and Bar is defined by the foreign key Bar.FooId. What I'd like to do is to somehow extract that relationship at runtime using expressions. I'd like to implement a method that behaves as follows:

var context = new FooBarDbContext();
var bar = context.Set<Bar>().First();

// I want this method to return bar.FooId when passed the expression b => b.Foo
object result = MyService.GetForeignKeyValue(bar, b => b.Foo); 

Now in this simplistic example I know that I could just get bar.FooId and be done. The point is that I'm writing a class for which I believe the GetForeignKeyValue method specified above is the cleanest interface for a user.

Is it possible to query the DbContext configuration to determine which property is used as the foreign key for a navigation property? (Assuming there is one)

like image 787
John Bledsoe Avatar asked May 08 '13 14:05

John Bledsoe


People also ask

How do you use a foreign key in code first approach?

To create Foreign Key, you need to use ForeignKey attribute with specifying the name of the property as parameter. You also need to specify the name of the table which is going to participate in relationship.

What are navigation properties in Entity Framework?

A navigation property is an optional property on an entity type that allows for navigation from one end of an association to the other end. Unlike other properties, navigation properties do not carry data.

Do we use foreign keys in Entity Framework?

No foreign key propertyWhile it is recommended to have a foreign key property defined in the dependent entity class, it is not required.


1 Answers

I was actually able to determine the foreign key properties by using the GetDependentProperties method of the NavigationProperty class.

Here is the code that I used (more or less) to get what I needed:

object[] GetForeignKeyPropertyValues<TEntity, TRelatedEntity>(TEntity entity, Expression<Func<TEntity, TRelatedEntity>> navigationProperty)
{
    if (entity == null)
        return new object[] { };

    // Find the entity metadata in the object context.
    // (Assume you have access to the DbContext through the property CurrentDbContext.)
    var objectContext = (CurrentDbContext as IObjectContextAdapter).ObjectContext; 

    var metadataNamespace = ObjectContextAdapter.GetType().Namespace;

    var entityIdentity = metadataNamespace + "." + typeof(TEntity).Name; // HACK: This seems to work to retrieve the EntityType for an entity.
    var entityMetadata = objectContext.MetadataWorkspace.GetItem<EntityType>(entityIdentity, DataSpace.CSpace);

    // TODO: Verify that the entity metadata was found.

    // Get the navigation property metadata by parsing the name from the navigation property expression.
    var navigationPropertyName = GetPropertyName(navigationProperty);
    var navigationPropertyMetadata = entityMetadata.NavigationProperties.FirstOrDefault(np => np.Name == navigationPropertyName);

    // TODO: (JMB) Verify that the navigation property metadata was found.

    // Extract the foreign key columns from the navigation property.
    var foreignKeyPropertyMetadatas = navigationPropertyMetadata.GetDependentProperties();

    // Create property getters for each foreign key property.
    var foreignKeyPropertyGetters = foreignKeyPropertyMetadatas
        .Select(propertyMetadata => MakePropertyGetter<TEntity>(propertyMetadata.Name))
        .ToArray();

    // Execute the foreign key property getters to get the foreign key property values for the specified entity.
    var foreignKeyPropertyValues = foreignKeyPropertyGetters
        .Select(propertyGetter => propertyGetter(entity))
        .ToArray();

    return foreignKeyPropertyValues;
}

static string GetPropertyName<TEntity, TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty)
{
    var lambda = navigationProperty as LambdaExpression;
    var member = lambda.Body as MemberExpression;

    return member.Member.Name;
}

static Func<TEntity, object> MakePropertyGetter<TEntity>(Type entityType, string propertyName)
{
    var parameterExpression = Expression.Parameter(typeof(TEntity), "entity");
    var propertyExpression = Expression.PropertyOrField(parameterExpression, propertyName);

    var lambdaExpression = Expression.Lambda(propertyExpression, parameterExpression);
    var lambdaFunction = lambdaExpression.Compile();

    return (Func<TEntity, object>)lambdaFunction;
}
like image 160
John Bledsoe Avatar answered Nov 15 '22 10:11

John Bledsoe