2

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.

Hussein
  • 945
  • 1
  • 12
  • 30
  • Can you show us the new code where you pass the 2 variables to Find() ? – Colin Nov 08 '13 at 14:37
  • OK, I removed the reflection statement and pass the variables as parameters to the method containing the code, so the code calling this method pass it the name of the primary key parts. It's a work around but it is available for me because I call this method from WCF service layer. – Hussein Nov 08 '13 at 14:48
  • Just added that technique to my at the same time as you posted your comment! Is it working now? – Colin Nov 08 '13 at 14:52
  • I get the same exception as above and attachedEntity is null.for information I use Database First approach and EF creat the Entity Data Model from the database – Hussein Nov 08 '13 at 15:15
  • Can you post the code? – Colin Nov 08 '13 at 15:20
  • I just added the code as update2 – Hussein Nov 08 '13 at 15:30
  • Can you give us a link to the chrisb code? You have multiple calls doing the almost same thing. `var entry = _dbContext.Entry(entity);`, `T attachedEntity = set.Find(pkey);`, `var attachedEntry = _dbContext.Entry(attachedEntity);` but calling Entry also attaches http://stackoverflow.com/a/15045787/150342 – Colin Nov 08 '13 at 15:46
  • see chris ab answer to this [link](http://stackoverflow.com/questions/12585664/an-object-with-the-same-key-already-exists-in-the-objectstatemanager-the-object) – Hussein Nov 08 '13 at 15:55
  • I think you need to check what you're passing in as the primary key. But have a look at this too http://blog.oneunicorn.com/2012/05/03/the-key-to-addorupdate/ – Colin Nov 08 '13 at 16:47

2 Answers2

1

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

Improve MetaData API issue

Community
  • 1
  • 1
Colin
  • 22,328
  • 17
  • 103
  • 197
1

In the entity builder

modelBuilder.Entity<T>()
            .HasKey(o => new { key1, key2});

key1 and key2 are composite keys.

Sandhya
  • 11
  • 1