简体   繁体   English

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

[英]Entity Framework Add if not exist without update

I like the fact that AddOrUpdate let's you specify a filter to check to avoid adding duplicates.我喜欢 AddOrUpdate 让您指定要检查的过滤器以避免添加重复项的事实。 But I would like similar functionality without the update.但我想要没有更新的类似功能。

Right now I do something like this:现在我做这样的事情:

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

I know I can do something like this:我知道我可以做这样的事情:

db.Profile.AddOrUpdate(p => new {p.LastName, p.FirstName, p.Middle}, newProfile);
db.SaveChanges();

But I would rather skip modifying the data in this case.但在这种情况下,我宁愿跳过修改数据。

The first example does what I want but with more code.第一个例子做了我想要的,但有更多的代码。 Is there a simpler/cleaner way to do what I want in the first example?在第一个示例中是否有更简单/更清洁的方法来做我想做的事情?

Update:更新:

I like Ognyan Dimitrov's suggestion.我喜欢 Ognyan Dimitrov 的建议。 I'm trying to implement it.我正在努力实现它。 My models inherit from BaseEntity.我的模型继承自 BaseEntity。 Can I put a generic version of that there?我可以在那里放一个通用版本吗?

My model is defined:我的模型定义为:

public class Address :BaseEntity
{

My 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;
    }
}

I'm getting errors for Any(...) and Add(...).我收到 Any(...) 和 Add(...) 的错误。 The error for Add(...) is 'An object reference is required for the non-static field, method, or property 'System.Data.Entity.DbSet.Add(object)' ' Add(...) 的错误是“非静态字段、方法或属性 'System.Data.Entity.DbSet.Add(object)' 需要对象引用”

Should I be using this.Add(object) ?我应该使用 this.Add(object) 吗?

Update 2:更新 2:

I've created this code:我已经创建了这个代码:

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

}

Now I'm trying to call it like this, but it's not correct.现在我试图这样称呼它,但这是不正确的。 Forgive my lack of understanding.原谅我的不理解。

_db.ProfileIdentifier.AddIfNotExists(newIdentifier,
            pi => new {pi.ProfileId, pi.ProfileIdentifierTypeId, pi.ProfileIdentifierValue});

Update - Solution:更新 - 解决方案:

I can call the DbSetextensions like this:我可以像这样调用 DbSetextensions:

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

Thanks a lot for working with me Ognyan!!!非常感谢您与我一起工作 Ognyan!!!

Have you tried to check if the entity exists and if not - add it?您是否尝试检查实体是否存在,如果不存在 - 添加它? Like this :像这样 :

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

You can use this method directly and remember to call DbContext.SaveChanges() after the call.可以直接使用这个方法,调用后记得调用DbContext.SaveChanges()。

All the other answers are incorrect.所有其他答案都不正确。

"Read before write" can violate data integrity without being put inside a transaction control. “先读后写”可能会违反数据完整性,而无需置于事务控制中。

In SQL Server, you can use merge statement.在 SQL Server 中,您可以使用合并语句。 However merge statement is not available in EF.但是合并语句在 EF 中不可用。

The solution is OK, when you have to add just one item, but it's very expensive in terms of performance in case you have to add multiple items.解决方案是好的,当您只需要添加一个项目时,但如果您必须添加多个项目,则在性能方面会非常昂贵。 I think there is a better solution:我认为有一个更好的解决方案:

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

So later it can be used like this:所以以后可以这样使用:

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

I used something like, read these two posts to make my code.我使用了类似的东西,阅读这两篇文章来制作我的代码。 I hope to help those in need of a similar signature to AddOrUpdate.我希望能帮助那些需要类似 AddOrUpdate 签名的人。

Entity Framework Add if not exist without update 实体框架如果不存在则添加而不更新

Making AddOrUpdate change only some properties 使 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;
        }
    }
}

Quoted from MSDN EF article .引自MSDN EF 文章

Insert or update pattern插入或更新模式

A common pattern for some applications is to either Add an entity as new (resulting in a database insert) or Attach an entity as existing and mark it as modified (resulting in a database update) depending on the value of the primary key.某些应用程序的常见模式是将实体添加为新实体(导致数据库插入)或将实体附加为现有实体并将其标记为已修改(导致数据库更新),具体取决于主键的值。 For example, when using database generated integer primary keys it is common to treat an entity with a zero key as new and an entity with a non-zero key as existing.例如,当使用数据库生成的整数主键时,通常将具有零键的实体视为新实体,将具有非零键的实体视为现有实体。 This pattern can be achieved by setting the entity state based on a check of the primary key value.这种模式可以通过基于对主键值的检查来设置实体状态来实现。

Note that when you change the state to Modified all the properties of the entity will be marked as modified and all the property values will be sent to the database when SaveChanges is called.请注意,当您将状态更改为已修改时,实体的所有属性都将被标记为已修改,并且在调用 SaveChanges 时,所有属性值都将发送到数据库。

context.Entry(profile).State = profile.Id == 0 ? EntityState.Added : EntityState.Modified; 
context.SaveChanges(); 

What worked for me is this:对我有用的是:

public static void AddIfNotExists<T>(this DbSet<T> dbSet, Func<T, object> predicate, params T [] entities) where T : class, new()
{
    foreach (var entity in entities)
    {
        var newValues = predicate.Invoke(entity);
        Expression<Func<T, bool>> compare = arg => predicate(arg).Equals(newValues);
        var compiled = compare.Compile();
        var existing = dbSet.FirstOrDefault(compiled);
        if (existing == null)
        {
            dbSet.Add(entity);
        }
    }
}

唯一想到的是使用IEqualityComparer<T> ,但这并没有真正停止工作,只是将其抽象出来并创建更清晰的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM