AnalogWeapon
AnalogWeapon

Reputation: 548

Linq: Filter list of type A based on an object of type A?

I have a List of objects of type Foo, and another instance of an object of type Foo. I would like to use linq to filter the list based on the non-null properties of the instance.

class Foo {
  public int ID;
  public string Description;
  public long Location;
}

Foo fooFilter = new Foo() {
  ID = null,
  Description = null,
  Location = 1
}

List<Foo> fooList = new List<Foo>();

fooList.Add(new Foo(){ID = 1, Description = "one", Location = 1});
fooList.Add(new Foo(){ID = 2, Description = "two", Location = 0});
fooList.Add(new Foo(){ID = 3, Description = "three", Location = 1});

List<Foo> filteredFooList = fooList.Where(???);

I want to somehow use fooFilter to query fooList and fill filteredFooList with:

[
  {ID = 1, Description = "one", Location = 1},
  {ID = 3, Description = "three", Location = 1}
]

EDIT:

I was trying to be brief to make the question more clear, but I might have left of important information. In my actual program, List<Foo> is a large result from a database (over 40k entries). I'm trying to make a controller method (MVC) which can take any combination of parameters that would match the field names of an entity framework object. So <Foo> is a EF record type. So I'm trying to avoid having to explicitly list all the (15 or so) fields that can be filtered on in the controller:

public class Home : Controller
{
  public ActionResult FilteredFooList(int ID, string Description, long Location, etc, etc, etc)
    {

    }
}

And do something more like:

public class Home : Controller
{
  public ActionResult FilteredFooList(Foo filterObj)
    {

    }
}

Maybe this isn't possible or a good idea?

Upvotes: 0

Views: 949

Answers (5)

Dogu Arslan
Dogu Arslan

Reputation: 3384

I recently solved a very similar problem by dynamically building up the lambda query based on input objects using mainly Expression class in .net. I can write up a solution if you are interested in it for your case, just being cautious to over do this since there is already an answer accepted but pointing out potential alternatives.

Upvotes: 0

Arthur Rey
Arthur Rey

Reputation: 3056

If you only want to filter out non instanciated properties, you don't need a filter.

class Foo
{
    public int ID;
    public string Description;
    public long Location;

    public bool IsInstanciated()
    {
        return this.ID != default(int) && this.Description != default(string) && this.Location != default(long);
    }
}

List<Foo> filteredFooList = fooList.Where(f => f.IsInstanciated());

Edit: If you really need to use that instanciated class as a filter, I'd suggest you to use IEquatable<T>

class Foo : IEquatable<Foo>
{
    public int ID;
    public string Description;
    public long Location;

    public bool Equals(Foo other)
    {
        // Whatever your logic is
        return string.IsNullOrEmpty(this.Description) == string.IsNullOrEmpty(other.Description) && 
               this.ID > 0 == other.ID > 0 && 
               this.Location > 0 == other.Location > 0;
    }
}

public class Home : Controller
{
    public ActionResult FilteredFooList(Foo filterObj)
    {
        List<Foo> filteredFooList = fooList.Where(f => f.Equals(filterObj));
    }
}

Upvotes: 2

Jeff Mercado
Jeff Mercado

Reputation: 134611

Don't paint yourself into a corner by adding unnecessary properties to your data objects, they should stay data objects. You're effectively trying to build a dynamic query where you want to conditionally filter by a list of conditions. There are patterns to do this.

Start with a base query, then determine if you want to filter by one of the properties. Do the same with the rest of the other properties. By the time you reach the end, you could then just gather the results.

var filter = new Foo
{
    ID = null,
    Description = null,
    Location = 1,
};

var data = new List<Foo>
{
    new Foo { ID = 1, Description = "one", Location = 1 },
    new Foo { ID = 2, Description = "two", Location = 0 },
    new Foo { ID = 3, Description = "three", Location = 1 },
};

var query = data.AsEnumerable();
if (filter.ID != null)
    query = query.Where(x => x.ID == filter.ID);
if (filter.Description != null)
    query = query.Where(x => x.Description == filter.Description);
if (filter.Location != null)
    query = query.Where(x => x.Location == filter.Location);

var result = query.ToList();

This assumes that ID and Location are actually nullable just like your example implies.

public class Foo
{
    public int? ID { get; set; }
    public string Description { get; set; }
    public long? Location { get; set; }
}

Upvotes: 1

Rufus L
Rufus L

Reputation: 37070

By looking at your expected output, it appears that you want to filter fooList so that you get all items that have the same Location as your fooFilter object. If that's what you're asking, you can do this:

List<Foo> filteredFooList = fooList.Where(item => item.Location == fooFilter.Location);

Upvotes: 0

NetMage
NetMage

Reputation: 26936

Assuming you can change your value types to be nullable:

class Foo {
    public int? ID;
    public string Description;
    public long? Location;
}

Then you can use some extensions:

public static class Ext {
    public static bool EqualOrNull<T>(this T? value, T? filter) where T : struct, IComparable {
        return (filter == null) || (value.Value.CompareTo(filter.Value) == 0);
    }
    public static bool EqualOrNull<T>(this T value, T filter) where T : class, IComparable {
        return (filter == null) || (value.CompareTo(filter) == 0);
    }
}

To do this:

var filteredFooList = fooList.Where(f => f.ID.EqualOrNull(fooFilter.ID) && f.Description.EqualOrNull(fooFilter.Description) && f.Location.EqualOrNull(fooFilter.Location));

If you wanted something truly generic (e.g. not dependent on knowing the field names) you would need to step into the world of Reflection.

Upvotes: 0

Related Questions