17

I have the following Entity

public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

and have a list List<Person> badGuys

what I want to do is select from all persons except those in badGuys List

My Code

context.Persons
    .where(p => !badGuys.Contain(p))
    .ToList()

but I get an error

Only primitive types or enumeration types are supported in this context.

How to fix this?

Mohamed Badr
  • 2,562
  • 2
  • 26
  • 43

4 Answers4

41

You could make an array containing the ids of the bad guys and filter out those ids (which are of a primitive type, so it should work):

var badGuyIds = badGuys.Select(x => x.PersonId).ToArray();

context.Persons
    .Where(p => !badGuyIds.Contain(p.PersonId))
    .ToList();
Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
  • 3
    You could do it a little more efficient by removing the `.ToArray();` like so: `var badGuyIds = badGuys.Select(x => x.PersonId);` thus preventing a needless query on `badGuyId`, as the second query on `Persons` will do that anyway... – ShayLivyatan Aug 01 '17 at 08:29
  • if I have badGuyIds list, and I want to right join and select badguys where not in Persons table, how to do that, Sir? – toha Jan 05 '23 at 13:32
5

You can use the clause ALL with the distinct(!=)

var badBoys= from P in context.Persons
         where badGuys.All(a => a.PersonId!= P.PersonId)
         select P;
3

You can implement your own method to make necessary expression tree, like here:

    public static IQueryable<TEntity> WhereNotIn<TEntity, TValue>(
        this IQueryable<TEntity> queryable,
        Expression<Func<TEntity, TValue>> valueSelector,
        IEnumerable<TValue> values)
        where TEntity : class
    {
        if (queryable == null)
            throw new ArgumentNullException("queryable");

        if (valueSelector == null)
            throw new ArgumentNullException("valueSelector");

        if (values == null)
            throw new ArgumentNullException("values");

        if (!values.Any())
            return queryable.Where(e => true);

        var parameterExpression = valueSelector.Parameters.Single();

        var equals = from value in values
                     select Expression.NotEqual(valueSelector.Body, Expression.Constant(value, typeof (TValue)));

        var body = equals.Aggregate(Expression.And);

        return queryable.Where(Expression.Lambda<Func<TEntity, bool>>(body, parameterExpression));
    }
}

And now you can call this extension method

var badGuys = new int[] { 100, 200, 300 };
context.Persons.WhereNotIn(p => p.PersionId, badGuys);

This method makes the same thing as:

context.Persons.Where(p => p.PersonId != badGuys[0]
                        && p.PersonId != badGuys[1]
                        . . .
                        && p.PersonId != badGuys[N]);

for each element of badGuys enumerable object.

An additional advantage of the method is its universality, cause you can call it for any property of any entity, f.e. context.Persons.WhereNotIn(p => p.LastName, new[] { "Smith", "Brown", "Jones" }).

Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
  • That's a pretty solution but it's not a good one in terms of performance. I'd rather go for the ANY statement to increase performance. Plus, it's natively supported by EF so why not go for that approach? – hbulens Nov 12 '15 at 09:56
  • @hbulens I'm not sure EF can process `Any` if it can't process `Contains` in the proposed context. But this is possible. As for performance, I think both queries will have similar execution plans at the DB level. – Mark Shevchenko Nov 12 '15 at 10:04
  • @MarkShevchenko this is a pretty, but hence I will have to user `p.PersonId` anyway for filtering, what would be the difference between your solution and @W0lf solution? – Mohamed Badr Nov 12 '15 at 10:17
  • @MohamedBadr As I can see, there's no more differences. – Mark Shevchenko Nov 12 '15 at 11:21
  • well the SQL generated using your approach is like `WHERE (2 <> [Extent1].[PersonId]) AND (1 <> [Extent1].[PersonId])` while the code generated from @W0lf answer is like `WHERE NOT ([Extent1].[PersonId] IN (2, 1))` which query would be more efficient? – Mohamed Badr Nov 12 '15 at 11:24
  • @MohamedBadr As I wrote above, I think the execution plans will be similar. The solution of w0lf is more clear, if you need to filter by single property. – Mark Shevchenko Nov 12 '15 at 11:27
0

LINQ to ENTITIES: NOT CONTAINS METHOD:

The method should be .Contains() not .Contain. This requires a primitive type enumeration, so just enumerate your key field of your badGuys collection and call .Contains() on it in your Where clause when you query your context.

//get the list of bad guys
List<Person> badGuys = ...
//get simple primitive type enumeration
var badGuysIDs = badGuys.Select(t => t.PersonId).ToList();

using(Context c = new Context())
{
   var badPersons = c.Persons.Where(t => !badGuysIDs.Contains(t.PersonId)).ToList();
}

LINQ TO OBJECTS EXCEPT() METHOD:

Another option, if you already have your List<Person> of badguys is to use the .Except() method on your collection. This will have to be done after you enumerate your collection to objects.

//get the list of bad guys
List<Person> badGuys = ...

using(Context c = new Context())
{
   //enumerate Persons, then exclude badPersons
   var goodPersons = c.Persons.ToList().Except(badPersons);
}

LINQ to ENTITIES EXCEPT() METHOD:

The above method will not be ideal if you have a very large number of rows in your Persons table, because it will enumerate the entire collection. If you have a large number of rows, you will want to perform the exclusion on the database side before enumerating. To do this, you simply have to tell the database how to compare two objects for equality as in This excellent SO answer.

public class PersonComparer: IEqualityComparer<Persons> 
{
     public bool Equals(Person x, Person y) {return x.Id == y.Id;}
     public int GetHashCode(Person person) {return person.PersonId.GetHashCode();}
}


using(Context c = new Context())
{
    //get the list of bad guys
    List<Person> badGuys = ...

   var goodPersons = c.Persons.Except(badPersons, PersonComparer()).ToList();
}
Community
  • 1
  • 1
Brino
  • 2,442
  • 1
  • 21
  • 36