如果存在则更新行否则使用实体框架插入逻辑[关闭]

Posted

技术标签:

【中文标题】如果存在则更新行否则使用实体框架插入逻辑[关闭]【英文标题】:Update Row if it Exists Else Insert Logic with Entity Framework [closed] 【发布时间】:2011-07-30 07:02:16 【问题描述】:

使用实体框架实现更新行(如果存在),否则插入新行逻辑的最有效方法是什么?或者有什么模式?

【问题讨论】:

这应该在数据库引擎级别,在存储过程中完成。否则,您必须将检测/更新/插入包装在事务中。 @Stephen:事实上,这就是我最终要做的。谢谢。 乔纳森,你的问题对我很有用。为什么切换到存储过程? @Anar:这更容易,我希望效率更高。 在不了解更多上下文的情况下无法回答此问题。比如,“高效”在哪方面?冲突最少?表现?可维护的代码?此外,取决于功能甚至非功能需求,哪种 upsert 方法最合适。因此,这个问题被关闭为“基于意见”,还不如“需要更多关注”。 【参考方案1】:

如果您正在使用附加对象(从上下文的同一实例加载的对象),您可以简单地使用:

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)

    context.MyEntities.AddObject(myEntity);


// Attached object tracks modifications automatically

context.SaveChanges();

如果您可以使用有关对象键的任何知识,则可以使用以下内容:

if (myEntity.Id != 0)

    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);

else

    context.MyEntities.AddObject(myEntity);


context.SaveChanges();

如果您无法通过其 Id 确定对象的存在,则必须执行查找查询:

var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))

    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);

else

    context.MyEntities.AddObject(myEntity);


context.SaveChanges();

【讨论】:

谢谢。看起来像我需要的。我能问你一个困扰我很久的问题吗?通常,我将我的上下文放在一个简短的using 块中。将上下文留在内存中一段时间​​可以吗?例如,在 Windows 窗体的生命周期中?我通常会尝试清理数据库对象以确保数据库上的负载最小。等待销毁我的 EF 上下文没有问题吗? 但是如果我需要使用对象列表来执行此操作...在我的数据库中有一个具有相同 id 的行列表,如果它们存在我想替换或插入..我是怎么做到的?谢谢! 这个答案看起来很棒,但我在更新时遇到了这个问题:ObjectStateManager 中已经存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象。 看起来我在获取现有对象以便在更新之前检索其密钥时遇到了一些问题;首先分离该查找对象有助于修复它。 如果其他外部进程在您检索到dbcontext(处于连接状态)之后可以修改/添加相同的记录,它不会失败..【参考方案2】:

从 Entity Framework 4.3 开始,在命名空间 System.Data.Entity.Migrations 中有一个 AddOrUpdate 方法:

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set,
    params TEntity[] entities
)
where TEntity : class

doc:

在调用 SaveChanges 时按键添加或更新实体。相等的 从数据库术语到“upsert”操作。这种方法可以 在使用 Migrations 播种数据时很有用。


为了回答comment by @Smashing1978,我将从@Colin提供的链接中粘贴相关部分

AddOrUpdate 的工作是确保您不会创建重复项 当您在开发过程中播种数据时。

首先,它将在您的数据库中执行查询以查找记录 您作为键(第一个参数)提供的任何内容与 AddOrUpdate 中提供的映射列值(或值)。所以这 匹配时有点松散,但播种非常好 设计时数据。

更重要的是,如果找到匹配项,则更新将更新所有 并清除任何不在您的 AddOrUpdate 中的内容。

也就是说,我有一种情况,我从外部服务中提取数据并通过主键插入或更新现有值(我的消费者本地数据是只读的) - 在生产中使用 AddOrUpdate 以获得更多信息现在超过 6 个月,到目前为止没有任何问题。

【讨论】:

System.Data.Entity.Migrations 命名空间包含与基于代码的迁移及其配置相关的类。我们有什么理由不应该在我们的存储库中为非迁移实体 AddOrUpdates 使用它? 注意 AddOrUpdate 方法:thedatafarm.com/data-access/… 这篇文章描述了为什么不应使用 AddOrUpdate michaelgmccarthy.com/2016/08/24/…【参考方案3】:

调用SaveChanges() 时会发生魔法,这取决于当前的EntityState。如果实体有EntityState.Added,它将被添加到数据库中,如果它有EntityState.Modified,它将在数据库中更新。所以你可以实现一个InsertOrUpdate()方法如下:

public void InsertOrUpdate(Blog blog) 
 
    using (var context = new BloggingContext()) 
     
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
     

More about EntityState

如果您无法检查Id = 0 以确定它是否为新实体,请检查answer of Ladislav Mrnka。

【讨论】:

【参考方案4】:

如果您知道您使用的是相同的上下文并且没有分离任何实体,您可以制作这样的通用版本:

public void InsertOrUpdate<T>(T entity, DbContext db) where T : class

    if (db.Entry(entity).State == EntityState.Detached)
        db.Set<T>().Add(entity);

    // If an immediate save is needed, can be slow though
    // if iterating through many entities:
    db.SaveChanges(); 

db 当然可以是类字段,或者方法可以是静态的和扩展,但这是基础。

【讨论】:

【参考方案5】:

Ladislav 的回答很接近,但我必须进行一些修改才能使其在 EF6(数据库优先)中工作。我用我的 on AddOrUpdate 方法扩展了我的数据上下文,到目前为止,这似乎与分离的对象很好地配合:

using System.Data.Entity;

[....]

public partial class MyDBEntities 

  public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) 
      if (ID != 0) 
          set.Attach(obj);
          ctx.Entry(obj).State = EntityState.Modified;
      
      else 
          set.Add(obj);
      
  
[....]

【讨论】:

AddOrUpdate 也作为扩展方法存在于 System.Data.Entity.Migrations 中,所以如果我是你,我会避免为自己的方法重用相同的方法名称。【参考方案6】:

使用 Any 检查现有行。

public static void insertOrUpdateCustomer(Customer customer)

    using (var db = getDb())
    

        db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
        db.SaveChanges();

    


【讨论】:

【参考方案7】:

在我看来,值得一提的是,使用新发布的EntityGraphOperations for Entity Framework Code First,您可以避免编写一些重复代码来定义图中所有实体的状态。我是这个产品的作者。我已将其发布在github、code-project(包括分步演示和可供下载的示例项目)和nuget。

它将自动设置实体的状态AddedModified。如果实体不再存在,您将手动选择必须删除的实体。

示例:

假设我有一个Person 对象。 Person 可以有很多电话、一个文档并且可以有一个配偶。

public class Person

     public int Id  get; set; 
     public string FirstName  get; set; 
     public string LastName  get; set; 
     public string MiddleName  get; set; 
     public int Age  get; set; 
     public int DocumentId get; set;

     public virtual ICollection<Phone> Phones  get; set; 
     public virtual Document Document  get; set; 
     public virtual PersonSpouse PersonSpouse  get; set; 

我想确定图中包含的所有实体的状态。

context.InsertOrUpdateGraph(person)
       .After(entity =>
       
            // Delete missing phones.
            entity.HasCollection(p => p.Phones)
               .DeleteMissingEntities();

            // Delete if spouse is not exist anymore.
            entity.HasNavigationalProperty(m => m.PersonSpouse)
                  .DeleteIfNull();
       );

您也知道,唯一的关键属性可以在定义电话实体的状态时发挥作用。对于这种特殊用途,我们有ExtendedEntityTypeConfiguration&lt;&gt; 类,它继承自EntityTypeConfiguration&lt;&gt;。如果我们想使用这样的特殊配置,那么我们必须从ExtendedEntityTypeConfiguration&lt;&gt; 继承我们的映射类,而不是EntityTypeConfiguration&lt;&gt;。例如:

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
    
        public PhoneMap()
        
             // Primary Key
             this.HasKey(m => m.Id);
              …
             // Unique keys
             this.HasUniqueKey(m => new  m.Prefix, m.Digits );
        
    

就是这样。

【讨论】:

【参考方案8】:

插入否则更新两者

public void InsertUpdateData()

//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();

var query = from data in context.Employee
            orderby data.name
            select data;

foreach (Employee details in query)

    if (details.id == 1)
    
        //Assign the new values to name whose id is 1
        details.name = "Sanjay";
        details. Surname="Desai";
        details.address=" Desiwadi";
    
    else if(query==null)
    
        details.name="Sharad";
        details.surname=" Chougale ";
        details.address=" Gargoti";
    


//Save the changes back to database.
context.SaveChanges();

【讨论】:

我使用了这种方法,但检查了(在第一个或默认之后) if (query == null)【参考方案9】:

@LadislavMrnka 答案的替代方案。这适用于实体框架 6.2.0。

如果您有特定的DbSet 和需要更新或创建的项目:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)

    _dbContext.Names.Add(name);

else

    _dbContext.Entry(current).CurrentValues.SetValues(name);

_dbContext.SaveChanges();

但是,这也可以用于具有单个主键或复合主键的通用 DbSet

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class

    foreach (var value in values)
    
        try
        
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        
        catch
        
            throw;
        
    
    _dbContext.SaveChanges();


public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class

    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    
        someDbSet.Add(value);
    
    else
    
        Entry(current).CurrentValues.SetValues(value);
    

【讨论】:

【参考方案10】:

试试这个算法

public void InsertOrUpdate(Item item) 
 
    using (var context = new ItemContext()) 
     
       var existedItem = context.Items.Where(x => x.Id==item.Id).FirstOrDefault();

       if(existedItem != null)
       
          context.Entry(existedItem).CurrentValues.SetValues(item);
          
              //or only if you want to update some special properties
                existedItem.Prop1=item.Prop1;
                existedItem.Prop2=item.Prop2
                context.Entry(existedItem).State =EntityState.Modified;
              //-----------

        
       else 
        
        context.Items.Add(item);
       

    context.SaveChanges();
   

【讨论】:

更好的方法是让您的else 块说existedItem = new Item(); context.Items.Add(existedItem);,然后将更新existedItem 属性的代码移到else 块下方。【参考方案11】:

更正

public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity) 
        where T : class
        where T2 : class
        
            foreach(var e in updateEntity)
            
                context.Set<T2>().InsertOrUpdate(e);
            
        


        public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity) 
        where T : class
        where T2 : class
        
            if (context.Entry(updateEntity).State == EntityState.Detached)
            
                if (context.Set<T2>().Any(t => t == updateEntity))
                
                   context.Set<T2>().Update(updateEntity); 
                
                else
                
                    context.Set<T2>().Add(updateEntity);
                

            
            context.SaveChanges();
        

【讨论】:

请使用edit而不是发布另一个答案 很抱歉,这段代码一团糟。 T 没有使用,第二个InsertOrUpdate 必须是DbSet 上的扩展方法,context.Set&lt;T2&gt;().Any(t =&gt; t == updateEntity) 会给出运行时错误。请在发布前测试代码!

以上是关于如果存在则更新行否则使用实体框架插入逻辑[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

如果已存在则更新行,否则使用 mysql 在表中插入新记录

SQL Server CE:如果存在更新,否则插入

如果存在则更新,否则插入[重复]

需要使用实体框架将大量记录插入数据库

如果 id 存在则更新,否则插入 (ODBC)

如果不存在则插入,否则在 Netezza 中更新