简体   繁体   中英

Using a list property to filter a dbset with LINQ

Say I have an event that has a list of staff tasks:

public class Event()
{
  public Guid? StaffId { get; set; }
}

public class StaffTask()
{
  public Guid StaffId { get; set; }
  public Guid TaskId { get; set; }
}

How would I do something like this where I get all the events for a list of staff members?

var staffTasks = new List<StaffTasks>() 
{ 
  new StaffTask () { StaffId = "guid1", TaskId = "guid2" },
  new StaffTask () { StaffId = "guid3", TaskId = "guid4" }
};

queryable = _db.Events.AsQueryable()
  .Where(event => 
      staffTasks.Any(st => st.StaffId == event.StaffId)
  );

I currently get this error when running the above:

The LINQ expression 'DbSet<Event>()
    .Where(e => __staffTasks
        .Any(or => (Nullable<Guid>)or.StaffId == e.StaffId))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

The goal would be to have this return only the second and third event here

var events = new List<Event>() {
  new Event() { StaffId = null },
  new Event() { StaffId = "guid1" },
  new Event() { StaffId = "guid2" },
  new Event() { StaffId = "guid20" },
  new Event() { StaffId = null }
}

Try adding additional condition in where clause to check StaffId in event class is null or not,

Usage:

queryable = _db.Events.AsQueryable()
         .Where(event =>  event.StaffId.HasValue && 
        staffTasks.Any(st => st.StaffId.HasValue  && st.StaffId == event.StaffId));

or with null coalescing operator??

queryable = _db.Events.AsQueryable()
        .Where(event => 
        staffTasks.Any(st => (st.StaffId ?? Guid.Empty) == event?.StaffId));

Try Online

this seemed to get the job done, though i'm still not sure why @Prasad's answer didn't work

var staffTasks = new List<StaffTasks>() 
{ 
  new StaffTask () { StaffId = "guid1", TaskId = "guid2" },
  new StaffTask () { StaffId = "guid3", TaskId = "guid4" }
};

var staff = staffTasks.Select(st => st.StaffId).ToList();

queryable = _db.Events.AsQueryable()
  .Where(event => staffTasks.Contains(event.StaffId ?? Guid.Empty));

i'm still not sure why @Prasad's answer didn't work

EF wants to take the C# you provide and make an SQL out of it. It knows how to do some things, but not everything

When you have a pattern of "collection in c#, search in column for value within the collection" EF will want to make an SQL like WHERE id IN(value1,value2..) but critically it won't go digging and running complex projections to get that list of values

Any will work, but (as far as I know) only on collections that are just the type of the value being searched. This means projecting your StaffTasks to a simple Guid? collection would also work as an Any:

var staff = staffTasks.Select(st => (Guid?)st.StaffId).ToArray();

_db.Events
  .Where(event => staff.Any(st => event.StaffId == st ));

EF can translate this to an IN like it can Contains, but the reason it's probably just better to remember "don't use Any, use Contains" is because Contains is much better at causing a compile error if you do something EF won't tolerate.

This wouldn't compile (note I've used staffTasks.Contains):

_db.Events
  .Where(event => staffTasks.Contains(event.StaffId));

So Contains automatically guides you towards using a list of primitives whereas Any makes you think in LINQ "I'll just pull the prop I want in the lambda" mode and write:

_db.Events
  .Where(event => staffTasks.Any(st => event.StaffId == st.SomeProp));

This would compile in c# but won't translate to EF because EF would have to run the projection to get the values it wants to put in the IN. It also tries to get away with doing a nullable<T> == T here (StaffTask and Event have different types for StaffId), which is legal C# in a locally evaluated Any, but another thing that EF doesn't translate

--

So ends up, your answer became translated as COALESCE(event.StaffId, '00000000-0000-0000-0000-000000000000') IN ('guid1', 'guid3') (I guess that 3's a typo because your sample data is guid2) which is fine

If you'd type-matched the list so it was full of Guid? you could have dropped the ??Guid.Empty and it would have translated as event.StaffId IN ('guid1', 'guid3') which is also fine (it would discard the null StaffId on events) and actually possibly faster as the COALESCE could preclude the use of an index

And if you'd used a list of Guid? With Any it also would have worked..

..but generally if you use Contains you will have these things work out first time more often because the way Contains demands you supply things is more often in line with how EF needs to receive things

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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