Building up a LINQ query based on bools - c#

(The title for this question isn't the best, but I'm unsure how else to word it!)
I'm working on a search form which contains a checklist of values. Basically, a checked item means 'include this type in the search'. Something like this:
Search for item: __________
Search in:
[ ] Fresh Foods
[ ] Frozen Foods
[ ] Beverages
[ ] Deli Counter
I have an object to represent this search:
class FoodSearchCriteria{
public string SearchString {get;set;}
public bool SearchFreshFoods {get;set;}
public bool SearchFrozenFoods {get;set;}
public bool SearchBeverages {get;set;}
public bool SearchDeliCounter {get;set;}
}
The only way I can think of doing this atm is like this:
public IList<FoodItem> FindFoodItems(FoodSearchCriteria criteria)
// in reality, this is a fuzzy search not an exact match
var matches = _DB.FoodItems.Where(x => x.FoodTitle == SearchString);
var inCategories = new List<FoodItem>();
if (criteria.SearchFreshFoods)
inCategories.Add(matches.Where(x => x.Type == 'Fresh Foods'));
if (criteria.SearchFrozenFoods)
inCategories.Add(matches.Where(x => x.Type == 'Frozen Foods'));
//etc etc
return inCategories;
}
This feels like a code smell to me, what would be a better way to approach it?

Take a look at PredicateBuilder
PredicateBuilder predicate = PredicateBuilder.False<FoodItem>();
if (criteria.SearchFreshFoods)
{
predicate = predicate.Or(x => x.Type == 'Fresh Foods');
}
if (criteria.SearchFrozenFoods)
{
predicate = predicate.Or(x => x.Type == 'Frozen Foods'));
}
...
_DB.FoodItems.Where(predicate);

Have you tried:
List<string> types = new List<string>();
if (criteria.SearchFreshFoods) { types.Add("Fresh Foods"); }
if (criteria.SearchFrozenFoods) { types.Add("Frozen Foods"); }
if (criteria.SearchBeverages) { types.Add("Beverages"); }
if (criteria.SearchDeliCounter) { types.Add("Deli Counter"); }
return _DB.FoodItems.Where(x => x.FoodTitle == SearchString &&
types.Contains(x.Type));
That means just one SQL query, which is handy.
You could certainly refactor the FoodSearchCriteria type to make it easier to build the list though...

I have no time to review but this could be an untested solution.
class SearchItem
{
string Name {get; set;}
bool IsSelected {get; set;}
}
class FoodSearchCriteria
{
String searchText {get; set;}
IList<SearchItem> SearchItems{ get; }
}
public IList<FoodItem> FindFoodItems(FoodSearchCriteria criteria)
// in reality, this is a fuzzy search not an exact match
var matches = _DB.FoodItems.Where(x => x.FoodTitle == criteria.SearchText &&
criteria.SearchItems.Where(si => si.IsSelected).Contains(i => i.Name == x.Type));
return mathces;
}

Related

Net Core: Resolve "Add a way to break out of this property accessor's recursion"

I am trying to create a simple class. ColumnSort member is a list of items in comma delimited text "Car,Book,Food".
ColumnSortList creates a List
Car
Book
Food
C# and SonarQube is mentioning items like Error
Get: Add a way to break out of this property accessor's recursion.
Set: Use the 'value' parameter in this property set accessor declaration
How would I resolve these to make warnings/ errors (in SonarQube) go away? Open to making code more efficient also.
Note: columnSortList is purely supposed to be a read only computed field from ColumnSort string.
public class PageModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string ColumnSort { get; set; }
public IEnumerable<string> columnSortList
{
get
{
return columnSortList;
}
set
{
if (ColumnSort == null)
{
columnSortList = null;
}
else
{
columnSortList = ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
If columnSortList is intended to be purely read-only, computed from ColumnSort, then you should not have a set method at all. All the logic should go inside get like this:
public IEnumerable<string> columnSortList
{
get
{
if (ColumnSort == null)
{
return Enumerable.Empty<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
Your getter is returning itself, which you can't do, and your setter is setting itself, which you also can't do. This here seems to be what you want:
public IEnumerable<string> columnSortList
{
get
{
if (ColumSort == null)
{
return new List<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}

Filtering nested lists with nullable property

Say I have the following class structures
public class EmailActivity {
public IEnumerable<MemberActivity> Activity { get; set; }
public string EmailAddress { get; set; }
}
public class MemberActivity {
public EmailAction? Action { get; set; }
public string Type { get; set; }
}
public enum EmailAction {
None = 0,
Open = 1,
Click = 2,
Bounce = 3
}
I wish to filter a list of EmailActivity objects based on the presence of a MemberActivity with a non-null EmailAction matching a provided list of EmailAction matches. I want to return just the EmailAddress property as a List<string>.
This is as far as I've got
List<EmailAction> activityTypes; // [ EmailAction.Open, EmailAction.Bounce ]
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Where(
activity => activityTypes.Contains(activity.Action)
)
)
.Select(member => member.EmailAddress)
.ToList();
However I get an error message "CS1503 Argument 1: cannot convert from 'EmailAction?' to 'EmailAction'"
If then modify activityTypes to allow null values List<EmailAction?> I get the following "CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type".
The issue is the nested .Where it's returning a list, but the parent .Where requires a bool result. How would I tackle this problem?
I realise I could do with with nested loops however I'm trying to brush up my C# skills!
Using List.Contains is not ideal in terms of performance, HashSet is a better option, also if you want to select the email address as soon as it contains one of the searched actions, you can use Any:
var activityTypes = new HashSet<EmailAction>() { EmailAction.Open, EmailAction.Bounce };
List<string> activityEmailAddresses =
emailActivity.Where(
member => member.Activity.Any(
activity => activity.Action.HasValue &&
activityTypes.Contains(activity.Action.Value)
)
)
.Select(activity => activity.EmailAddress)
.ToList();
You want to use All or Any depends if you want each or at least one match...
HashSet<EmailAction> activityTypes = new HashSet<EmailAction> { EmailAction.None };
var emailActivity = new List<EmailActivity>
{
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.None } }, EmailAddress = "a" },
new EmailActivity { Activity = new List<MemberActivity>{ new MemberActivity { Action = EmailAction.Click } }, EmailAddress = "b" }
};
// Example with Any but All can be used as well
var activityEmailAddresses = emailActivity
.Where(x => x.Activity.Any(_ => _.Action.HasValue && activityTypes.Contains(_.Action.Value)))
.Select(x => x.EmailAddress)
.ToArray();
// Result is [ "a" ]

Check which elements are on one list comparing to another list LINQ

I have two lists, one of all languages and another subset of languages that the site has, the idea is to return all the languages but change the property of a boolean if the element of the subset corresponds to the list of all languages.
DTO of language:
public class DTOLanguage
{
public bool HaveLanguage { get; set; }
public int IdLanguage { get; set; }
//Other properties...
}
Method that returns all languages:
public List<DTOLanguage> GetLanguages()
{
var result = repository.RepSite.GetLanguages().Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage,
CodName = x.CodName,
Name = x.Name
}).ToList();
return result;
}
Method that returns the subset of languages:
public List<DTOLanguage> GetLanguagesById(int idSite)
{
var result = repository.RepSite.GetLanguagesById(idSite).Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage
}).ToList();
return result;
}
The GetLanguagesById is called in the DataAccess layer, so what Im thinking is that this method should receive another parameter (what GetLanguages returns) and make some fancy LINQ there.
I know that I can filter (example):
SubsetOfLanguages.Where(lg => lg.IdLanguage == AllLanguagesItem.IdLanguage)
{
AllLanguagesItem.HaveLanguage = True;
}
But Im not really sure as how it should be.
Thanks in advance.
You could use Contains extension method this way:
var languages=GetLanguages();
var subsetids=repository.RepSite.GetLanguagesById(idSite).Select(x =>x.IdLanguage);//Select just the id value
foreach(var l in languages.Where(l=>subsetids.Contains(l.IdLanguage)))
{
l.HaveLanguage = true;
}
You could do this:
var allLanguages = GetLanguages();
var subset = SubsetOfLanguages
.Where(lg => allLanguages.Any(a => lg.IdLanguage == a.IdLanguage))
.ToArray();
foreach(var item in subset)
{
item.HaveLanguage = True;
}

Querying a RavenDB index against an external List<T>

I have the following RavenDB Index:
public class RidesByPostcode : AbstractIndexCreationTask<Ride, RidesByPostcode.IndexEntry>
{
public class IndexEntry
{
public string PostcodeFrom { get; set; }
public string PostcodeTo { get; set; }
}
public RidesByPostcode()
{
Map = rides => from doc in rides
select new
{
doc.DistanceResult.PostcodeFrom,
doc.DistanceResult.PostcodeTo
};
StoreAllFields(FieldStorage.Yes);
}
}
I also have a list of strings representing postcodes, and I want to get all the Rides for which the PostcodeFrom is in the list of postcodes:
var postcodes = new List<string> { "postcode 1", "postcode 2" };
var rides = _database.Query<RidesByPostcode.IndexEntry, RidesByPostcode>()
.Where(x => postcodes.Contains(x.PostcodeFrom))
.OfType<Ride>()
.ToList();
But of course RavenDb says it cannot understand the .Contains expression.
How can I achieve such a query in RavenDb without having to call .ToList() before the where clause?
Ok, I found the answer: RavenDb's .In() extension method (see the "Where + In" section of the docs).
Apparently I was thinking from the outside in, instead of from the inside out :)
This is the final query:
var rides = _database.Query<RidesByPostcode.IndexEntry, RidesByPostcode>()
.Where(x => !x.IsAccepted && x.PostcodeFrom.In(postcodes))
.OfType<Ride>()
.ToList();

How do I search nested criteria using MongoDB c# driver (version 2)?

I have a collection of documents that can contain criteria grouped into categories. The structure could look like this:
{
"Name": "MyDoc",
"Criteria" : [
{
"Category" : "Areas",
"Values" : ["Front", "Left"]
},
{
"Category" : "Severity",
"Values" : ["High"]
}
]
}
The class I'm using to create the embedded documents for the criteria looks like this:
public class CriteriaEntity
{
public string Category { get; set; }
public IEnumerable<string> Values { get; set; }
}
The user can choose criteria from each category to search (which comes into the function as IEnumerable<CriteriaEntity>) and the document must contain all the selected criteria in order to be returned. This was my first attempt:
var filterBuilder = Builders<T>.Filter;
var filters = new List<FilterDefinition<T>>();
filters.Add(filterBuilder.Exists(entity =>
userCriterias.All(userCriteria =>
entity.Criteria.Any(entityCriteria =>
entityCriteria.Category == userCriteria.Category
&& userCriteria.Values.All(userValue =>
entityCriteria.Values.Any(entityValue =>
entityValue == userValue))))));
However I get the error: "Unable to determine the serialization information for entity...". How can I get this to work?
MongoDB.Driver 2.0 doesn't support Linq.All. Anyway you task can be resolve next way:
var filterDefinitions = new List<FilterDefinition<DocumentEntity>>();
foreach (var criteria in searchCriterias)
{
filterDefinitions
.AddRange(criteria.Values
.Select(value => new ExpressionFilterDefinition<DocumentEntity>(doc => doc.Criterias
.Any(x => x.Category == criteria.Category && x.Values.Contains(value)))));
}
var filter = Builders<DocumentEntity>.Filter.And(filterDefinitions);
return await GetCollection<DocumentEntity>().Find(filter).ToListAsync();

Categories