EF Code First - Add a DTO
Published Wednesday 21 March 2012
context.Entry(entity).CurrentValues.SetValues(dataTransferObject);
The DTO will generally have a primary key property, and you can use that to determine if it is a new record or a modification. Here's a method that does that:
public static T Merge<T>(this DbContext context, object dataTransferObject)
where T : class
{
if (context == null) throw new ArgumentNullException("context");
if (dataTransferObject == null) throw new ArgumentNullException("dataTransferObject");
var property = FindPrimaryKeyProperty<T>(context);
//find the id property of the dto
var idProperty = dataTransferObject.GetType().GetProperty(property.Name);
if (idProperty == null)
throw new InvalidOperationException("Cannot find an id on the dataTransferObject");
var id = idProperty.GetValue(dataTransferObject, null);
//has the id been set (existing item) or not (transient)?
var propertyType = property.PropertyType;
var transientValue = propertyType.IsValueType ?
Activator.CreateInstance(propertyType) : null;
var isTransient = Equals(id, transientValue);
T entity;
if (isTransient)
{
//it's transient, just create a dummy
entity = CreateEntity<T>(id, property);
//if DatabaseGeneratedOption(DatabaseGeneratedOption.None) and no id, this errors
context.Set<T>().Attach(entity);
}
else
{
//try to load from identity map or database
entity = context.Set<T>().Find(id);
if (entity == null)
{
//could not find entity, assume assigned primary key
entity = CreateEntity<T>(id, property);
context.Set<T>().Add(entity);
}
}
//copy the values from DTO onto the entry
context.Entry(entity).CurrentValues.SetValues(dataTransferObject);
return entity;
}
private static PropertyInfo FindPrimaryKeyProperty<T>(IObjectContextAdapter context)
where T : class
{
//find the primary key
var objectContext = context.ObjectContext;
//this will error if it's not a mapped entity
var objectSet = objectContext.CreateObjectSet<T>();
var elementType = objectSet.EntitySet.ElementType;
var pk = elementType.KeyMembers.First();
//look it up on the entity
var propertyInfo = typeof(T).GetProperty(pk.Name);
return propertyInfo;
}
private static T CreateEntity<T>(object id, PropertyInfo property)
where T : class
{
// consider IoC here
var entity = (T)Activator.CreateInstance(typeof(T));
//set the value of the primary key (may error if wrong type)
property.SetValue(entity, id, null);
return entity;
}
Previously: EF Code First - is entity transient? (20 Mar 2012)