Search code examples
c#entity-frameworklambdaextension-methods

EF extension AddIfNotExists local lambda


I made an generic extension method to add new entities only if a specified key doesn’t exists. This works for the data in the database, but I also want to check the Local values. Maybe it has been added and didn’t saved to the database yet. I use the following code:

public static void AddIfNotExists<TEntity>(this IDbSet<TEntity> set, Expression<Func<TEntity, object>> identifierexp, params TEntity[] entities) where TEntity : class
    {
        if (set == null)
            throw new Exception("DbSet not assinged");
        if (entities == null)
            throw new Exception("Entities not assinged");

        var dbSet = set as DbSet<TEntity>;

        if (dbSet != null)
        {
            MemberExpression body = identifierexp.Body as MemberExpression;

            if (body == null)
            {
                UnaryExpression ubody = (UnaryExpression)identifierexp.Body;
                body = ubody.Operand as MemberExpression;
            }


            ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "X");
            Expression property = Expression.Property(parameter, body.Member.Name);

            foreach (var entity in entities)
            {
                Expression target = Expression.Constant(entity.GetType().GetProperty(body.Member.Name).GetValue(entity).ToString());
                Expression containsMethod = Expression.Call(property, "Contains", null, target);
                Expression<Func<TEntity, bool>> lamba = Expression.Lambda<Func<TEntity, bool>>(containsMethod, parameter);

                var Exists = dbSet.SingleOrDefault(lamba);
                

                if (Exists == null)
                {
                    dbSet.Add(entity);
                }

            }

        }

    }

When I change it with the following code he can’t use Lambda on the local property. Because the is not a right overload. But as far as I can see the overload is the same. What is the right way?

                var Exists = dbSet.Local.SingleOrDefault(lamba);
                if (Exists != null)
                    return;

                Exists = dbSet.SingleOrDefault(lamba);
                if (Exists != null)
                    return;

                dbSet.Add(entity);

And the error i'm getting:

Error   1   'System.Collections.ObjectModel.ObservableCollection<TEntity>' does not contain a definition for 'SingleOrDefault' and the best extension method overload 'System.Linq.Enumerable.SingleOrDefault<TSource>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,bool>)' has some invalid arguments  

Solution

  • That is because dbSet.Local doesn't implement IQueryable<T>. The LINQ extensions for IEnumerable<T> only accept delegates, not expressions representing those.

    There are two ways to fix that:

    1. Compile your lambda expression to a delegate by using the Compile() method, and use that for the local query. The resulting code should look like this:

      var Exists = dbSet.Local.SingleOrDefault(lamba.Compile());
      
    2. Wrap the local collection in an IQueryable<T> implementation by invoking AsQueryable() on it. The resulting code should look like this:

      var Exists = dbSet.Local.AsQueryable().SingleOrDefault(lamba);