I'm having difficulty with IEnumerable and LINQ. Perhaps I don't understand it fully.
I have a data source of fake files ("PlumFile"), and some filters. Each filter has a Fits(PlumFile)
that determines if the argument fits that filter. Each filter also has an enum indicating if it's "and", "or", or "not".
Here is how I'm trying to combine all those filters into a query:
public ObservableCollection<PlumFile> FoundFiles
{
get
{
ObservableCollection<PlumFile> searchResults = new ObservableCollection<PlumFile>();
// get the data source
IEnumerable<PlumFile> query = PlumData.GetFiles();
foreach (FilterConstraint filter in filters)
{
// debugging
IList<PlumFile> oldQuery = query.ToList();
switch (filter.QueryCombiningRule)
{
case FilterConstraint.QueryRule.And:
query = query.Where(file => filter.Fits(file));
break;
case FilterConstraint.QueryRule.Or:
query = query.Concat(PlumData.GetFiles().Where(file => filter.Fits(file)));
break;
// is this really how I want to do 'not'?
case FilterConstraint.QueryRule.Not:
query = query.Where(file => !filter.Fits(file));
break;
}
// debugging
IList<PlumFile> currQuery = query.ToList();
}
query = query.Distinct();
foreach (PlumFile file in query)
{
searchResults.Add(file);
}
return searchResults;
}
}
I'm not sure what I'm doing wrong. For some queries, it works fine. For others, it fails.
If I have a single "and" filter, it works fine. Then I add a "not" filter that shouldn't filter out anything already selected, but everything is removed. Why is this?
(I'm doing this for a Silverlight 4 app, but I don't think it matters.)
Update: One example of a filter constraint:
public class NameFilterConstraint : FilterConstraint
{
public string Name { get; set; }
public override bool Fits(PlumFile plumFile)
{
return plumFile.Name.Contains(Name);
}
public override string Description
{
get
{
return ToString();
}
}
public override string ToString()
{
return String.Format("Name contains '{0}'", Name);
}
}
Update 2: Here is a non-LINQ version that doesn't have the bugs I mentioned earlier:
public ObservableCollection<PlumFile> FoundFiles
{
get
{
ObservableCollection<PlumFile> searchResults = new ObservableCollection<PlumFile>(PlumData.GetFiles().ToList());
foreach (FilterConstraint filter开发者_JS百科 in filters)
{
switch (filter.QueryCombiningRule)
{
case FilterConstraint.QueryRule.And:
foreach (PlumFile file in searchResults.ToList())
{
if (! filter.Fits(file))
{
searchResults.Remove(file);
}
}
break;
case FilterConstraint.QueryRule.Or:
foreach (PlumFile file in PlumData.GetFiles())
{
if (filter.Fits(file))
{
searchResults.Add(file);
}
}
break;
case FilterConstraint.QueryRule.Not:
foreach (PlumFile file in searchResults.ToList())
{
if (filter.Fits(file))
{
searchResults.Remove(file);
}
}
break;
}
}
return new ObservableCollection<PlumFile>(searchResults.Distinct());
}
}
So, I guess my problem is solved, although I'm still curious as to what I was doing wrong with the LINQ. Perhaps my intentions (made clear in the last example) were not translated into LINQ properly?
Just a guess here, but I wonder if this is a capture/closure issue. Try this:
public ObservableCollection<PlumFile> FoundFiles
{
get
{
ObservableCollection<PlumFile> searchResults = new ObservableCollection<PlumFile>();
// get the data source
IEnumerable<PlumFile> query = PlumData.GetFiles();
foreach (FilterConstraint filter in filters)
{
var localFilter = filter;
// debugging
IList<PlumFile> oldQuery = query.ToList();
switch (filter.QueryCombiningRule)
{
case FilterConstraint.QueryRule.And:
query = query.Where(file => filter.Fits(file));
break;
case FilterConstraint.QueryRule.Or:
query = query.Concat(PlumData.GetFiles().Where(file => localFilter.Fits(file)));
break;
// is this really how I want to do 'not'?
case FilterConstraint.QueryRule.Not:
query = query.Where(file => !localFilter.Fits(file));
break;
}
// debugging
IList<PlumFile> currQuery = query.ToList();
}
query = query.Distinct();
foreach (PlumFile file in query)
{
searchResults.Add(file);
}
return searchResults;
}
}
On the face of it, I would suggest applying the filters as eager evaluation (not lazy).
Use .ToList() after applying all the filters inside loop + make "query" List. If that does not work, then you need to post "Fits" method also.
精彩评论