EF Core中AddorUpdate方法的替代方法是什么?

24
我想在使用EF Core的通用仓储中实现类似以下的ADDorUpdate()方法,有人可以帮助我吗?
  public virtual void AddOrUpdate(T entity)
    {
        #region Argument Validation

        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }
        #endregion
         DbSet.AddOrUpdate(e => e.Id, entity);  
        this.DbContext.SaveChanges();
    }
4个回答

56

只需简单使用

context.Update(entity);

它根据实体主键的值(0表示添加,> 0表示更新)确切执行AddOrUpdate

public virtual void AddOrUpdate(T entity)
{
    if (entity == null)
        throw new ArgumentNullException("entity");

     this.DbContext.Update(entity);  
     this.DbContext.SaveChanges();
}

这对任何人都有效吗?我正在使用EF 3.1,当尝试更新不存在的条目时,我会收到此异常“数据可能已在加载实体后被修改或删除。” - kewur
2
如果不跟踪实体并且遇到重复键冲突怎么办?一种有效的原始SQL/MySQL查询方法是当查询违反约束条件时使用“ON DUPLICATE KEY UPDATE”,然后进行更新。否则,您需要执行重复查询以查询行是否存在,然后在代码中决定添加还是更新它,这样效率较低,并且在其他方式直接与数据库引擎交互的情况下无法很好地扩展。在数据库引擎中,您将提供数据,它将使用一个查询处理它并减少性能损失。而且在您的代码中,您可能还不知道它是否为重复数据... - fr332lanc3
42
注意:这仅适用于自动生成的密钥,否则您将收到ConcurrencyException异常。 - Poul Bak
1
它抛出错误:数据库操作预计会影响1行,但实际上影响了0行;数据可能已经在实体加载后被修改或删除。请参阅http://go.microsoft.com/fwlink/?LinkId=527962以了解如何理解和处理乐观并发异常。 - Ali Adravi
我刚刚检查了一下:如果 (ctx.Orders.Any(o => o.OrderId == order.OrderId)),则 ctx.Orders.Update(order); 否则 ctx.Orders.Add(order); - Rob Sedgwick
显示剩余5条评论

3

如果你使用复合键,你可以使用我编写的下一个方法:

public static void AddOrUpdateRange<TEntity>(this DbSet<TEntity> set, IEnumerable<TEntity> entities)
            where TEntity : class
        {
            foreach (var entity in entities)
            {
                _ = !set.Any(e => e == entity) ? set.Add(entity) : set.Update(entity);
            }
        }

1
这将为每个实体发送请求到数据库。此外,所有对数据库的调用都应该是“异步”的(在您的情况下使用“AnyAsync”)。 - maxc137
@МаксимКошевой Add() 或 Update() 只会更改本地数据。只有在调用 SaveChange() 时才会与数据库交互。 - Koen van der Linden
5
@Koen van der Linden,我在谈论set.Any。它会为每个实体发送一次数据库请求。 - maxc137

0
你可以检查实体是否存在。 像这样:
book exists= context.Set<T>().Any(x=>x.Id==entity.Id);

使用方法如下。

if(exists)
context.Set<T>().Add(entity);
else
context.Set<T>().Update(entity);

编辑: 由于这是一种通用方法,您应该为该方法创建一个约束条件才能使其正常工作。例如,一个抽象类。

public abstract class BaseEntity
{
public int Id {get; set; }
}

你的方法应该有这个限制。

public virtual void AddOrUpdate(T entity)
 where T: BaseEntity{}

你的实体应该继承自BaseEntity


嗨,Bolkey,我遇到了一个错误:“运算符==不能应用于类型为'Tkey'和'T'key'的操作数”。 - Naurto san
感谢您的回复,@Bolkay。请查看IGenricRepo和GNericRepo的链接:https://www.c-sharpcorner.com/forums/the-name-savechanges-does-not-exist-in-the-current-context - Naurto san

0
EF Core(包括6版)不支持AddOrUpdate功能。实现它的一种方法是在数据库中创建upsert(插入或更新)函数,并使用EF方法ExecuteSqlRawAsync调用它。
以下是使用PostgreSQL的示例表和upsert函数。
CREATE TABLE employee(
 id int PRIMARY KEY,
 "name" TEXT,
 "position" TEXT, 
 unique_identification_number int8 unique
);

CREATE OR REPLACE FUNCTION upsert_employee(_id int, _name TEXT,_position 
TEXT,_unique_identification_number int8)
   RETURNS void
   LANGUAGE plpgsql
   AS 
$$
BEGIN
   INSERT INTO employee(id,name,"position",unique_identification_number)
   VALUES(_id,_name,_position,_unique_identification_number)
   ON CONFLICT(unique_identification_number)
   DO UPDATE SET 
       name = EXCLUDED.name,
       "position" = EXCLUDED."position"
   WHERE employee.id = _id;
END;
$$;

在后端使用EF的ExecuteSqlRawAsync调用pg函数:
var sql = @"SELECT upsert_employee(@p0,@p1,@p2,@p3)";
await _context.Database.ExecuteSqlRawAsync(sql, 1,'test','hr', 12);

这种方法在实体被分离、不使用自增主键并且只想创建一个数据库事务时非常有用(例如,不获取实体来检查其是否存在,然后决定是创建还是更新)。
缺点是它不是通用的。

这是一个PostgreSQL的答复,不是对问题的回答,应该是一条评论。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接