问题
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)
回答1:
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;
}
回答2:
The default way EF knows that FooID is the foreign key property for the Foo object property is based on a naming convention - any reason you can't rely on the same assumption? If so, just get the string that your expression parameter represents, append "Id", and you're good.
If you aren't following the naming convention and you aren't using attributes on your model, I think you're out of luck. There's no documented method of getting the DbModelBuilder reference from the DbContext (at least not that I can find).
来源:https://stackoverflow.com/questions/16442801/entity-framework-code-first-how-can-i-determine-the-foreign-key-property-used-f