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

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

【问题讨论】:

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

标签: c# entity-framework


【解决方案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,您可以避免编写一些重复代码来定义图中所有实体的状态。我是这个产品的作者。我已将其发布在githubcode-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) 会给出运行时错误。请在发布前测试代码!
            猜你喜欢
            • 2023-01-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-23
            • 2013-04-19
            • 2023-03-17
            相关资源
            最近更新 更多