实体框架添加如果没有更新则不存在

M K*_* II 32 c# entity-framework

我喜欢AddOrUpdate让你指定一个过滤器来检查以避免添加重复项.但我想要没有更新的类似功能.

现在我做这样的事情:

var checkProfile = from p in db.Profile
    where p => p.LastName == newProfile.lastName
         && p => p.FirstName == newProfile.firstName
         && p => p.Middle== newProfile.middle
    select p;
if (checkProfile.FirstOrDefault() == null)
{
    db.Profile.Add(newProfile);
    db.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以这样做:

db.Profile.AddOrUpdate(p => new {p.LastName, p.FirstName, p.Middle}, newProfile);
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

但我宁愿在这种情况下跳过修改数据.

第一个例子做了我想要的,但有更多的代码.在第一个例子中是否有更简单/更清洁的方式来做我想要的事情?

更新:

我喜欢Ognyan Dimitrov的建议.我正在努力实现它.我的模型继承自BaseEntity.我可以在那里放一个通用版本吗?

我的模型定义如下:

public class Address :BaseEntity
{
Run Code Online (Sandbox Code Playgroud)

我的BaseEntity:

public class BaseEntity 
{
    public virtual T AddIfNotExists<T>(T entity, Expression<Func<T, bool>> predicate = null)
    {
        var exists = predicate != null ? DbSet.Any(predicate) : DbSet.Any();
        return !exists ? DbSet.Add(entity) : null;
    }
}
Run Code Online (Sandbox Code Playgroud)

我收到Any(...)和Add(...)的错误.Add(...)的错误是'非静态字段,方法或属性'System.Data.Entity.DbSet.Add(object)''需要对象引用

我应该使用这个.添加(对象)?

更新2:

我创建了这段代码:

public static class DbSetExtensions
{
    public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
    {
        var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
        return !exists ? dbSet.Add(entity) : null;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在我试着像这样称呼它,但这不正确.原谅我缺乏理解.

_db.ProfileIdentifier.AddIfNotExists(newIdentifier,
            pi => new {pi.ProfileId, pi.ProfileIdentifierTypeId, pi.ProfileIdentifierValue});
Run Code Online (Sandbox Code Playgroud)

更新 - 解决方案:

我可以像这样调用DbSetextensions:

_db.ProfileIdentifier.AddIfNotExists(newIdentifier,
            pi => pi.ProfileId == profileId &&  
            pi.ProfileIdentifierTypeId == (int)type &&  
            pi.ProfileIdentifierValue == value);
Run Code Online (Sandbox Code Playgroud)

非常感谢与我合作Ognyan!

Ogn*_*rov 36

您是否尝试检查实体是否存在,如果不存在 - 添加它?像这样 :

UPDATE

using System.Linq.Expressions;
    public class ContextWithExtensionExample
    {
        public void DoSomeContextWork(DbContext context)
        {
            var uni = new Unicorn();
            context.Set<Unicorn>().AddIfNotExists(uni , x => x.Name == "James");
        }
    }

    public static class DbSetExtensions
    {
        public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
        {
            var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
            return !exists ? dbSet.Add(entity) : null;
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以直接使用此方法,并记住在调用后调用DbContext.SaveChanges().

  • @Shiva当你调用`dbSet.Any`时,它会急切地评估.因此,在`dbSet.Any`和`dbSet.Add`调用之间仍然有一个打开的窗口.这不是这种情况吗? (4认同)
  • @ginkner 您只需将返回类型从`T` 更改为`EntityEntry&lt;T&gt;` 并添加`using Microsoft.EntityFrameworkCore.ChangeTracking;`。 (3认同)
  • 扩展方法是打包此解决方案的好方法。 (2认同)
  • 您可以在从调用代码引用的任何程序集中定义此方法."扩展方法被定义为静态方法,但是通过使用实例方法语法来调用." -MSDN https://msdn.microsoft.com/en-us/library/bb383977.aspx (2认同)
  • 这不会在事务中运行. (2认同)

Sal*_*ros 8

解决方案是好的,当您只需要添加一个项目时,但如果您必须添加多个项目,则在性能方面会非常昂贵。我认为有一个更好的解决方案:

public static class DbSetExtensions
{
    public static EntityEntry<TEnt> AddIfNotExists<TEnt, TKey>(this DbSet<TEnt> dbSet, TEnt entity, Func<TEnt, TKey> predicate) where TEnt : class
    {
        var exists = dbSet.Any(c => predicate(entity).Equals(predicate(c)));
        return exists
            ? null
            : dbSet.Add(entity);
    }

    public static void AddRangeIfNotExists<TEnt, TKey>(this DbSet<TEnt> dbSet, IEnumerable<TEnt> entities, Func<TEnt, TKey> predicate) where TEnt : class
    {
        var entitiesExist = from ent in dbSet
            where entities.Any(add => predicate(ent).Equals(predicate(add)))
            select ent;

        dbSet.AddRange(entities.Except(entitiesExist));
    }
}
Run Code Online (Sandbox Code Playgroud)

所以以后可以这样使用:

using (var context = new MyDbContext())
{
    var user1 = new User { Name = "Peter", Age = 32 };
    context.Users.AddIfNotExists(user1, u => u.Name);

    var user2 = new User { Name = "Joe", Age = 25 };
    context.Users.AddIfNotExists(user2, u => u.Age);

    // Adds user1 if there is no user with name "Peter"
    // Adds user2 if there is no user with age 25
    context.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

  • 升级到 .net core 3.1 后,这似乎停止工作了...... (3认同)

Gus*_*ler 7

我用了类似的东西,阅读这两篇帖子来制作我的代码.我希望帮助那些需要与AddOrUpdate类似签名的人.

实体框架添加如果没有更新则不存在

使AddOrUpdate仅更改某些属性

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace System.Data.Entity.Migrations
{
    //
    // Summary:
    //     Metodos de extensão para System.Data.Entity.IDbSet
    public static class DbSetMigrationsGustavoExtensions
    {
        /// <summary>
        /// Adiciona uma entidade se ela não existe ainda
        /// Assinatura semelhante ao AddOrUpdate
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="set">Set onde serão adicionadas as entidades</param>
        /// <param name="identifierExpression">Campos usados na comparação</param>
        /// <param name="entities">Entidades para adicionar</param>
        public static void AddIfNotExists<TEntity>(this IDbSet<TEntity> set, Expression<Func<TEntity, object>> identifierExpression, params TEntity[] entities) where TEntity : class
        {

            var identifyingProperties = GetProperties<TEntity>(identifierExpression).ToList();
            var parameter = Expression.Parameter(typeof(TEntity));
            foreach (var entity in entities)
            {
                var matches = identifyingProperties.Select(pi => Expression.Equal(Expression.Property(parameter, pi.Name), Expression.Constant(pi.GetValue(entity, null))));
                var matchExpression = matches.Aggregate<BinaryExpression, Expression>(null, (agg, v) => (agg == null) ? v : Expression.AndAlso(agg, v));

                var predicate = Expression.Lambda<Func<TEntity, bool>>(matchExpression, new[] { parameter });
                if (!set.Any(predicate))
                {
                    set.Add(entity);
                }
            }
        }

        private static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> exp) where T : class
        {
            Debug.Assert(exp != null);
            Debug.Assert(exp.Body != null);
            Debug.Assert(exp.Parameters.Count == 1);

            var type = typeof(T);
            var properties = new List<PropertyInfo>();

            if (exp.Body.NodeType == ExpressionType.MemberAccess)
            {
                var memExp = exp.Body as MemberExpression;
                if (memExp != null && memExp.Member != null)
                    properties.Add(type.GetProperty(memExp.Member.Name));
            }
            else if (exp.Body.NodeType == ExpressionType.Convert)
            {
                var unaryExp = exp.Body as UnaryExpression;
                if (unaryExp != null)
                {
                    var propExp = unaryExp.Operand as MemberExpression;
                    if (propExp != null && propExp.Member != null)
                        properties.Add(type.GetProperty(propExp.Member.Name));
                }
            }
            else if (exp.Body.NodeType == ExpressionType.New)
            {
                var newExp = exp.Body as NewExpression;
                if (newExp != null)
                    properties.AddRange(newExp.Members.Select(x => type.GetProperty(x.Name)));
            }

            return properties.OfType<PropertyInfo>();
        }

        /// <summary>
        /// Faz um set.Any(predicate)
        /// Se não existe nada no set então adiciona
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="set">Set onde será adicionada a entidade</param>
        /// <param name="predicate">Condição (exemplo: dbUser => dbUser.Nome == "Gustavo")</param>
        /// <param name="entity">Entidade para adicionar</param>
        /// <returns></returns>
        public static T AddIfNotExists<T>(this IDbSet<T> set, Expression<Func<T, bool>> predicate, T entity) where T : class, new()
        {
            return !set.Any(predicate) ? set.Add(entity) : null;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


zs2*_*020 6

所有其他答案都不正确。

“先写入后先读取”可能会违反数据完整性,而不会放入事务控件中。

在SQL Server中,可以使用merge语句。但是merge语句在EF中不可用。

  • 更好的答案是提供一种解决方法或可以克服该限制/可能性的方法 (17认同)
  • 然而这个问题是针对 c# 实体框架的,所以你的答案没有帮助。在没有提供替代解决方案的情况下告诉某人不要做已经解决问题的事情是没有意义的。 (3认同)
  • 真的。他们必须为此使用交易。 (3认同)
  • 您能对此进行扩展或提供参考吗? (2认同)
  • 我编辑了上面的评论,描述了可能出现的问题。 (2认同)
  • 这根本不是解决办法!只是咆哮! (2认同)