如果存在实体框架的其他插入逻辑,则更新行


179

是否有人对使用实体框架实现“更新行(如果存在,否则插入)”逻辑的最有效方式提出建议?


2
这是应该在存储过程中在数据库引擎级别执行的操作。否则,您必须将检测/更新/插入包装在事务中。
Stephen Chung

1
@Stephen:实际上,这就是我最终要做的。谢谢。
乔纳森·伍德

乔纳森,您的问题对我非常有用。为什么要切换到存储过程?
anar khalilov 2014年

2
@Anar:这很容易,我希望效率更高。
乔纳森·伍德

您是否需要为每个表编写一个存储过程?
tofutim

Answers:


174

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

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上下文没有问题吗?
乔纳森·伍德

检查以下内容:stackoverflow.com/questions/3653009/…对象上下文应尽可能短地存在,但是对于Winforms或WPF,这可能意味着上下文与演示者一样长。链接的问题包含有关在Winforms中使用nhibernate会话的msdn文章的链接。相同的方法可以用于上下文。
Ladislav Mrnka,2011年

1
但是,如果我需要使用对象列表执行此操作怎么办...在我的数据库中,有一个具有相同ID的行列表,如果要存在则替换该行,如果不存在则要插入..我该怎么做?谢谢!
Phoenix_uy

1
这个答案看起来很棒,但是我在更新时就遇到了这个问题:ObjectStateManager中已经存在具有相同键的对象。ObjectStateManager无法使用相同的键跟踪多个对象。
约翰·祖布鲁姆

1
看起来我在获取现有对象以便在执行更新之前检索其键时遇到了一个问题;分离该查找对象首先有助于修复它。
约翰·祖布鲁姆

33

从Entity Framework 4.3开始,AddOrUpdate在namespace处有一个方法System.Data.Entity.Migrations

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

文档

调用SaveChanges时,通过键添加或更新实体。等效于数据库术语中的“更新”操作。使用“迁移”为数据播种时,此方法很有用。


为了回答@ Smashing1978评论,我将粘贴@Colin提供的链接中的相关部分。

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

首先,它将在您的数据库中执行查询以查找一条记录,其中您提供的任何键(第一个参数)与AddOrUpdate中提供的映射列值(或多个值)相匹配。因此,对于匹配而言,这有点松散,但对于播种设计时间数据而言,则完全可以。

更重要的是,如果找到匹配项,则更新将全部更新,并将所有未包含在您的AddOrUpdate中的内容清除。

就是说,我遇到这样的情况,我正在从外部服务中提取数据并通过主键插入或更新现有值(并且我的本地消费者数据是只读的)- AddOrUpdate在生产中已经使用了6个月以上,因此远没有问题。


7
System.Data.Entity.Migrations命名空间包含与基于代码的迁移及其配置有关的类。有什么原因为什么我们不应该在我们的存储库中将其用于非迁移实体AddOrUpdates?
Matt Lengenfelder 2015年

10
请注意AddOrUpdate方法:thedatafarm.com/data-access/…–
Colin

1
本文描述为什么AddOrUpdate不应使用michaelgmccarthy.com/2016/08/24/...
NolmëINFORMATIQUE

11

调用时会发生魔力,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(); 
    } 
}

有关EntityState的更多信息

如果您无法检查Id = 0以确定它是否是新实体,请检查Ladislav Mrnka答案


8

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

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 当然可以是一个类字段,也可以将方法设为静态和扩展,但这是基础。


4

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

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中的扩展方法存在,因此,如果您是我,我将避免为自己的方法重复使用相同的方法名。
AFract

2

在我看来,值得一提的是,通过新发布的用于实体框架代码的EntityGraphOperations,您可以省去编写一些重复的代码来定义图中所有实体的状态的麻烦。我是该产品的作者。我已经在github代码项目包括分步演示和示例项目准备下载)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();
       });

众所周知,唯一的键属性在定义Phone实体的状态时可能会发挥作用。出于此类特殊目的,我们有ExtendedEntityTypeConfiguration<>类,该类继承自EntityTypeConfiguration<>。如果要使用这样的特殊配置,则必须从而ExtendedEntityTypeConfiguration<>不是从继承映射类EntityTypeConfiguration<>。例如:

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

就这样。


2

插入其他同时更新

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

我使用了这种方法,但是检查(在第一次或默认之后)是否(查询== null)
Patrick

2

使用“任何”检查现有行。

    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();

        }

    }

1

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

-1

已更正

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

2
请使用Edit而不是发布其他答案
Suraj Rao
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.