I\'m working on an EF 5 Code-First solution and I am trying to update an existing entity with a modified one using the Repository pattern:
public void Up
Ah, the glories of LINQ (and using
):
public List<PropertyInfo> GetNavigationProperties(T entity)
{
var t = entity.GetType();
var elementType = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<T>().EntitySet.ElementType;
return elementType.NavigationProperties.Select(np => entityType.GetProperty(np.Name)).ToList();
}
This can also be implemented via a static
method, with the following signature:
public static List<PropertyInfo> GetNavigationProperties<T>(DbContext context)
{
var t = typeof(T);
...
I wrote the following using EF6, but I believe it is all compatible with EF5. The general idea behind the code is to use the excellent classes in a System.Data.Metadata.Edm to get the navigation properties and use reflection on those property names to get the true properties of the object for updating.
I wanted to make my example as generic yet complete as possible. In the asker's case, he'd just obviously replace "context" with "_uow.Context".
public class MyClass<T> where T : class //T really needs to always be an entity,
//but I don't know a general parent type
//for that. You could leverage partial classes
//to define your own type.
{
public MyEntities context { get; set; }
public void UpdateValues(T originalEntity, T modifiedEntity)
{
//Set non-nav props
context.Entry(originalEntity).CurrentValues.SetValues(modifiedEntity);
//Set nav props
var navProps = GetNavigationProperties(originalEntity);
foreach (var navProp in navProps)
{
//Set originalEntity prop value to modifiedEntity value
navProp.SetValue(originalEntity, navProp.GetValue(modifiedEntity));
}
}
public List<System.Reflection.PropertyInfo> GetNavigationProperties(T entity)
{
List<System.Reflection.PropertyInfo> properties = new List<System.Reflection.PropertyInfo>();
//Get the entity type
Type entityType = entity.GetType();
//Get the System.Data.Entity.Core.Metadata.Edm.EntityType
//associated with the entity.
var entitySetElementType = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext.CreateObjectSet<T>().EntitySet.ElementType;
//Iterate each
//System.Data.Entity.Core.Metadata.Edm.NavigationProperty
//in EntityType.NavigationProperties, get the actual property
//using the entityType name, and add it to the return set.
foreach (var navigationProperty in entitySetElementType.NavigationProperties)
{
properties.Add(entityType.GetProperty(navigationProperty.Name));
}
return properties;
}
}
public static T Clone<T>(this T entity) where T : class
{
var type = entity.GetType();
var clone = Activator.CreateInstance(type);
var navigationProperties = type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty | BindingFlags.DeclaredOnly);
foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty))
{
//if (property.Module.ScopeName == "EntityProxyModule") continue;
if (navigationProperties.Contains(property)) continue;
if (property.CanWrite)
{
property.SetValue(clone, property.GetValue(entity, null), null);
}
}
return (T)clone;
}
Only the navigation properties are defined directly on the dynamic proxy so using BindingFlags.DeclaredOnly will filter them out.
The alternative (commented out) method differentiates the navigation properties using property.Module.ScopeName, for navigation properties this value will be "EntityProxyModule", whereas for the other properties this value will be the dll name of the project you defined the EF code first domain class in.
I use this Clone method to clone EF code first domain objects ready for Json serialization (avoiding circular references), but the author's query as to how to get a list of navigation properties is contained within.
Based on Zev's answer:
public List<PropertyInfo> GetNavigationProperties<T>(DbContext context) where T : class
{
var entityType = typeof(T);
var elementType = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<T>().EntitySet.ElementType;
return elementType.NavigationProperties.Select(property => entityType.GetProperty(property.Name)).ToList();
}