实体框架:使用复合主键更新实体

时间:2013-11-08 12:33:56

标签: entity-framework

我在数据层使用Repository包含chrisb建议的以下方法来更新实体,代码在更新之前首先访问主键:

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
    }
}

问题是如何将此方法与复合主键一起使用:即主键由两列或更多列组成时。

更新:我的问题在于Find()方法,例如当我将两个变量作为复合PK传递给它时,attachmentEntity为null,我得到了异常:  “ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法使用相同的键跟踪多个对象。”

update2:这里是修改后的完整方法代码

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
            }
        }

    }

感谢。

2 个答案:

答案 0 :(得分:1)

您可以将密钥传递到InsertOrUpdate方法,而不是使用反射检索密钥:

   public virtual T InsertOrUpdate(T e, params Object[] pkey)
   {
      //....
      T attachedEntity = set.Find(pkey);
      //...
   }

如果您为主键传入了错误的值,则无法阻止将发生的错误。

获取泛型方法中密钥的另一种方法是创建一个抽象类,实体继承并约束存储库:

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; }
}

参考: Repository pattern that allows for proxy creation

我更倾向于使用这种方法进行反射,因为它在编译时强制实施,但您必须对其进行调整以应对多个抽象类。 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);
   //...
}

或者,您可以调整以下代码以从元数据中检索KeyMembers(请参阅GetPrimaryKeyName方法)

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;
}

代码从Soft Delete pattern for Entity Framework Code First

解除

其他参考资料:

EF Code First Mapping Between Types & Tables

Improve MetaData API issue

答案 1 :(得分:0)

在实体构建器中

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

key1key2 是复合键。