Reputation: 989
I use a Repository in the Data layer contains the following method suggested by chrisb for updating entities, the code access the primary key first before update:
var entry = _dbContext.Entry<T>(entity);
// Retreive the Id through reflection
var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);
if (entry.State == EntityState.Detached)
{
var set = _dbContext.Set<T>();
T attachedEntity = set.Find(pkey); // You need to have access to key
if (attachedEntity != null)
{
var attachedEntry = _dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
The question is how to use this method with composite primary key: i.e. when the primary key consists of two or more columns.
Update: my problem is with Find() method, for example when I pass two variables to it as a composite PK the attachedEntity is null and I get the exception: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key."
update2: here the complete method code after modification
public virtual void Update(T entity, params Object[] pkey)
{
var entry = _dbContext.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
var set = _dbContext.Set<T>();
T attachedEntity = set.Find(pkey); // You need to have access to key
if (attachedEntity != null)
{
var attachedEntry = _dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
Thanks.
Upvotes: 2
Views: 8587
Reputation: 11
In the entity builder
modelBuilder.Entity<T>()
.HasKey(o => new { key1, key2});
key1
and key2
are composite keys.
Upvotes: 1
Reputation: 22595
Instead of retrieving the key using reflection, you could pass the key in to your InsertOrUpdate
method:
public virtual T InsertOrUpdate(T e, params Object[] pkey)
{
//....
T attachedEntity = set.Find(pkey);
//...
}
There is nothing to prevent the error that will occur if you pass in the wrong values for the primary key.
Another way to get the key in generic methods is to create an abstract class that your entities inherit and constrain the repository:
public class RepositoryBase<T> : IRepository<T> where T : ModelBase
{
public virtual T InsertOrUpdate(T e)
{
//....
T attachedEntity = set.Find(e.ID);
//...
}
}
public abstract class ModelBase
{
public int ID { get; set; }
}
Reference: Repository pattern that allows for proxy creation
I prefer this method to reflection because it is enforced at compile time but you would have to adapt it to cope with multiple abstract classes. e.g.
public abstract class CompositeBase
{
public int Key1 {get; set:}
public int Key2 {get; set;}
}
public virtual T InsertOrUpdate(T e) where T: CompositeBase
{
//....
T attachedEntity = set.Find(e.Key1, e.Key2);
//...
}
Alternatively you could adapt the following code to retrieve the KeyMembers from metadata (see GetPrimaryKeyName
method)
private static Dictionary<Type, EntitySetBase> _mappingCache =
new Dictionary<Type, EntitySetBase>();
private EntitySetBase GetEntitySet( Type type )
{
if ( !_mappingCache.ContainsKey( type ) )
{
ObjectContext octx = ( (IObjectContextAdapter)this ).ObjectContext;
string typeName = ObjectContext.GetObjectType( type ).Name;
var es = octx.MetadataWorkspace
.GetItemCollection( DataSpace.SSpace )
.GetItems<EntityContainer>()
.SelectMany( c => c.BaseEntitySets
.Where( e => e.Name == typeName ) )
.FirstOrDefault();
if ( es == null )
throw new ArgumentException( "Entity type not found in GetTableName", typeName );
_mappingCache.Add( type, es );
}
return _mappingCache[type];
}
private string GetTableName( Type type )
{
EntitySetBase es = GetEntitySet( type );
return string.Format( "[{0}].[{1}]",
es.MetadataProperties["Schema"].Value,
es.MetadataProperties["Table"].Value );
}
private string GetPrimaryKeyName( Type type )
{
EntitySetBase es = GetEntitySet( type );
return es.ElementType.KeyMembers[0].Name;
}
Code lifted from Soft Delete pattern for Entity Framework Code First
Other references:
EF Code First Mapping Between Types & Tables
Upvotes: 1