57

I like the fact that AddOrUpdate let's you specify a filter to check to avoid adding duplicates. 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. I'm trying to implement it. My models inherit from 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(...). The error for Add(...) is 'An object reference is required for the non-static field, method, or property 'System.Data.Entity.DbSet.Add(object)' '

Should I be using this.Add(object) ?

Update 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:

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

Thanks a lot for working with me Ognyan!!!

M Kenyon II
  • 4,136
  • 4
  • 46
  • 94
  • I don't think there is - you can write a stored procedure that will do a `MERGE`, but I don't think there's anything built in. – Patryk Ćwiek Jul 01 '15 at 13:26
  • only way i can think of is what you are doing right now – user1666620 Jul 01 '15 at 13:29
  • Take care with the `AddOrUpdate` method: http://thedatafarm.com/data-access/take-care-with-ef-4-3-addorupdate-method/ – Colin Jul 01 '15 at 15:11
  • To prevent duplicates I normally override the `ValidateEntity` method on the `DbContext ` http://stackoverflow.com/a/16647237/150342 – Colin Jul 01 '15 at 15:16

8 Answers8

52

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.

Steven Benitez
  • 10,936
  • 3
  • 39
  • 50
Ognyan Dimitrov
  • 6,026
  • 1
  • 48
  • 70
  • 1
    An extension method is a nice way to package up this solution. – pseudocoder Jul 01 '15 at 13:40
  • That sounds good. Where would I put that code? In my DBContext file? Or my model file? – M Kenyon II Jul 01 '15 at 13:41
  • 2
    You can define this method in any assembly you are referencing from the calling code. "Extension methods are defined as static methods but are called by using instance method syntax." -MSDN https://msdn.microsoft.com/en-us/library/bb383977.aspx – pseudocoder Jul 01 '15 at 13:48
  • I will prefer putting it in Repository. This method is excerpt from my implementation of IRepo. – Ognyan Dimitrov Jul 01 '15 at 13:56
  • Okay, I'm still working through Entity Framework so please help me understand. My model is defined like this: public class Address :BaseEntity. Can I add the above method to BaseEntity? – M Kenyon II Jul 01 '15 at 16:02
  • @MKenyonII take a look at the code update. Do you find it useful now? – Ognyan Dimitrov Jul 01 '15 at 18:21
  • Much closer. What if I have multiple items to check on? – M Kenyon II Jul 01 '15 at 18:24
  • It will be easier and less chatty ( less expensive requests ) if you make Select of the Names ( Names only for example) then intersect the collection you are going to insert by matching the Names and then insert only the missing. Another approach is to aggregate the predicate but I am not sure about the limitations of the [SQL IN](https://msdn.microsoft.com/en-us/library/ms177682.aspx?f=255&MSPPError=-2147217396) command string that will be generated - it can become very long. Usually one uses the INDEXed columns of the database rather then AddRangeIfNotExists through EntityFramework. – Ognyan Dimitrov Jul 01 '15 at 19:02
  • @OgnyanDimitrov This is great! It might not work for EF 7 though. I'm using the prerelease and there's an issue converting the return. Might be fixed when it finally comes out. – Benjamin Jun 23 '16 at 21:08
  • I wonder why microsoft not introduse somthing like this in there EF – Prageeth godage Jul 22 '16 at 02:34
  • 4
    @ginkner You just have to change the return type from `T` to `EntityEntry` and add `using Microsoft.EntityFrameworkCore.ChangeTracking;`. – Richard Marskell - Drackir Sep 27 '16 at 15:21
  • 4
    This will not be running in a transaction. – tugberk Jan 10 '17 at 12:51
  • @tugberk Not sure I understand. Are you saying this won't work if you have a transaction? How so?? – Shiva Apr 02 '17 at 02:32
  • 8
    @Shiva when you call `dbSet.Any`, it will evaluate eagerly. So, you still have an open window between `dbSet.Any` and `dbSet.Add` call. Isn't this the case here? – tugberk Apr 02 '17 at 15:02
  • @tugberk Wasn't aware of that. Thanks for the explanation. – Shiva Apr 02 '17 at 15:52
  • @tugberk I think it creates window between dbSet.Any and DbContext.SaveChanges. So, in case of multiple threads you still can have duplicates. – mdementev Sep 05 '18 at 13:47
  • This code does not work with object which has Foreign keys. – Major Sep 14 '18 at 10:08
20

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. However merge statement is not available in EF.

zs2020
  • 53,766
  • 29
  • 154
  • 219
10

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();
}
Salaros
  • 1,444
  • 1
  • 14
  • 34
  • AddIfNotExists works fine but AddRangeIfNotExists gives me the full dataset for entitiesExist. Calling it like `context.Accounts.AddRangeIfNotExists(accounts, t => t.ID);` where accounts = List – rory Apr 18 '19 at 12:12
  • 3
    This stopped working after upgrading to .net core 3.1 it seems... – sevenam Feb 19 '20 at 10:19
  • Yes, it doesn't work on .net core 3.1 as @johnb said https://i.imgur.com/qFWnBdW.png – ms_devel Apr 11 '20 at 08:01
7

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.

Entity Framework Add if not exist without update

Making AddOrUpdate change only some properties

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;
        }
    }
}
Community
  • 1
  • 1
Gustavo Rossi Muller
  • 1,062
  • 14
  • 18
  • Why do you need identifyingProperties? Expression> predicate From [Ognyan Dimitrov‘s answer](https://stackoverflow.com/questions/31162576/entity-framework-add-if-not-exist-without-update/31162909#31162909) should be sufficient. – Michael Freidgeim Nov 29 '17 at 19:54
  • I just published my code, because it has the same signature as AddOrUpdate public static void AddOrUpdate(this IDbSet set, Expression> identifierExpression, params TEntity[] entities) where TEntity : class; – Gustavo Rossi Muller Nov 30 '17 at 15:40
4

Quoted from MSDN EF article.

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.

context.Entry(profile).State = profile.Id == 0 ? EntityState.Added : EntityState.Modified; 
context.SaveChanges(); 
Brad C
  • 2,868
  • 22
  • 33
  • 2
    Actually, in this case, I do not want Modified. I do not want to SaveChanges if the record already exists. – M Kenyon II Jul 01 '15 at 16:04
  • @br4d thank you for your answer - i do not quite understand it: where is the blog instance obtained from? – BenKoshy Dec 14 '16 at 07:35
  • @BKSpurgeon Probably obtained as method parameter. Which could be safely mapped from a view model class to an entity class. As such: `public Category AddOrUpdate(CategoryVM categoryVM) { var category = categoryVM.Adapt(); context.Entry(category).State = category.Id == 0 ? EntityState.Added : EntityState.Modified; }` – Burak Dobur Mar 07 '17 at 18:20
  • The correct way would be as following : public static class DbSetExtensions { public static T AddIfNotExists(this DbSet dbSet, T entity, Expression> predicate = null) where T : class, new() { var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any(); return !exists ? dbSet.Add(entity).Entity : null; } } – Usman Sep 23 '20 at 06:21
2

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

The only thing that comes to mind is to use IEqualityComparer<T>, but this doesn't really stop the work, merely abstracts it away and creates cleaner code.

AllFallD0wn
  • 551
  • 7
  • 23
0

Because this answer stopped working in NET 7, and you only want to check by Id if the entity exists you can remove the Function part from the code and still works perfect

public static class DbContextExtensions
{
    public static EntityEntry<TEnt> AddIfNotExists<TEnt>(this DbSet<TEnt> dbSet, TEnt entity) where TEnt : class
    {
        var exists = dbSet.Any(c => entity == c);
        return exists
            ? dbSet.Attach(entity)
            : dbSet.Add(entity);
    }
}

As you can see in the SQL generated by EF, only look for the entity by the Id Any example SQL

Santiago
  • 2,190
  • 10
  • 30
  • 59