I'm trying to find a way to build a where clause and pass it to repository Get() method. It is supposed to filter items which name starts with a specific letter. I was able to construct part of this Where Clause Body, but can't find a way how to handle the scenario where the item name does not start with letter. For example: _ItemName or 97_SomeName.
So, here is my method:
protected override Expression<Func<DataSetSettings, bool>> GetWhereClause()
{
//The user has selected FilterLetter, for example: "A"
// return all items which name starts with "A"
if (!string.IsNullOrWhiteSpace(FilterLetter) && !FilterLetter.Equals("All"))
return (x => x.Name.StartsWith(FilterLetter) && x.Type == Type);
if (FilterLetter.Equals("Other"))
{
//Here i need to extract all items which name does not start with letter
}
//return All items of the current type
return x => x.Type == Type;
}
I would appreciate any help! Thanks!
Now that I understand what you need, I looked around and was not able to find a graceful solution to this. Seems complex string pattern matching is a weak point in EF.
The only way I can see to do this is to either compare against every letter, ie:
!x.Name.StartsWith("A") && !x.Name.StartsWith("B") && //on and on to Z
Or to make sure the entire list is loaded into memory and then use regular expressions to filter:
protected override Expression<Func<DataSetSettings, bool>> GetWhereClause()
{
var noletter = new Regex("^[^a-z].*", RegexOptions.IgnoreCase);
return (
x => x.Type == Type && (
string.IsNullOrWhiteSpace(FilterLetter) ||
FilterLetter == "All" ||
(FilterType == "Other" && noletter.IsMatch(x.Name)) ||
x.Name.StartsWith(FilterType)
)
);
}
If you do end up going with option of loading everything into memory, you can at least filter based on the x.Type first. That seems to be the common denominator in the filtering. At least that way you don't have to load the entire table into memory.
You can do equals false on your StartsWith
Like this:
return (x => x.Name.StartsWith(FilterLetter) == false && x.Type == Type);
My advice would be make 1 db call of all types into a list, then use linq to query that list. Your example would be making two db calls.
List<Type> allTypes = new List<Type>();
List<Type> typesWithA = new List<Type>();
List<Type> typesWOA = new List<Type>();
// make one db call
allTypes = entities.Types.ToList();
typesWithA = allTypes.Where(x => x.Name.StartsWith(FilterLetter)).ToList();
typesWOA = allTypes.Where(x => !x.Name.StartsWith(FilterLetter)).ToList();
Related
Previously, I had great help on my previous question, thank you vyrp
,
How do I create and populate a dynamic object using a dynamically built lambda expression
I'm now looking to search the dynamic object, and as before, I don't know the objects properties, and therefore what I'm searching until runtime.
Here's the code that builds the dynamic object:
// Get list of optional fields
var optFieldList = await _tbList_FieldRepository.GetAsync(lf => lf.ListID == listId && lf.DisplayInList == true);
// order list of optional fields
optFieldList.OrderBy(lf => lf.DisplayOrder);
// Get base Data excluding Inactive if applicable
IEnumerable<tbList_Data> primaryData = await _tbList_DataRepository.GetAsync(ld => ld.ListID == listId && (ld.IsActive == includeInactive ? ld.IsActive : true));
// Build IEnumerable<dynamic> from base results plus any optional fields to be displayed in table
var results = primaryData.Select(pd => {
dynamic result = new System.Dynamic.ExpandoObject();
result.Id = pd.ID;
result.PrimaryData = pd.PrimaryData;
result.DisplayOrder = pd.DisplayOrder;
result.IsActive = pd.IsActive;
foreach (var optField in optFieldList)
{
switch (optField.FieldType.ToLower()) {
case "text":
((IDictionary<string, object>)result).Add(optField.FieldName, pd.tbList_DataText.Where(ld => ld.DataRowID == pd.ID && ld.ListColumnID == optField.ID).Select(ld => ld.DataField).DefaultIfEmpty("").First());
break;
}
}
return result;
});
For the purpose of testing, I have 2 dynamic fields, "PhoneNumber" and "FuelType"
I can search the known field(s) i.e. PrimaryData, no problem, as below.
results = results.Where(r => r.PrimaryData.Contains(searchString));
And the following will work if I know the field PhoneNumber at design time
results = results.Where(r => r.PhoneNumber.Contains(searchString));
but what I want to do, is something like:
results = results.Where(r => r.PrimaryData.Contains(searchString)
|| foreach(var optField in optFieldList)
{
r.optField.FieldName.Contains(searchString)
})
ending up with
results = results.Where(r =>
r.PrimaryData.Contains(searchString)
|| r.PhoneNumber.Contains(searchString) ||
r.FuelType.Contains(searchString));
but obviously that code doesn't work. I've tried a bunch of different attempts, none successful, so I'm looking for suggestions. Thanks
Since you know that the dynamic element of your query is actually ExpandoObject, hence IDictionary<string, object>>, you can safely cast it to dictionary interface and use it to access the property values by name, while Enumerable.Any method can be used to simulate dynamic || condition:
results = results.Where(r => r.PrimaryData.Contains(searchString)
|| optFieldList.Any(f =>
{
object value;
return ((IDictionary<string, object>)r).TryGetValue(f.FieldName, out value)
&& value is string && ((string)value).Contains(searchString);
}));
I can retrieve an object from a generic list of that object like so:
return _itemTotalsAcrossMonthsList.Find(s => s.ItemDescription == desc);
But how can I retrieve an object from a generic list when I need to search on multiple object member values (in my case two). I start off with this:
ItemsForMonthYear ifmy;
. . .
ifmy = _itemsForMonthYearList.Find(s => s.ItemDescription == itemDesc);
...but I need to also search based on the value in monthYr. I hoped it would be something obvious like this:
ifmy = _itemsForMonthYearList.Find(s => s.ItemDescription == itemDesc, t => t.monthYr == monYr);
Do I need to do something like:
ifmy = from _itemsForMonthYearList.
Where (ItemDescription == itemDesc) && (monthYr == monYr).
Select(*);
? The latter doesn't work either, but which direction is the right one, or something else?
The predicate in here simply resolves to a bool to determine whether something matches or not:
Find(s => s.ItemDescription == itemDesc)
So anything which resolves to a bool would have the same effect:
Find(s => s.ItemDescription == itemDesc && s.monthYr == monYr)
I have a gridview which has drop down boxes in each header for filtering. Each filter is loaded with the distinct values from its column when loaded. At run time, I add "ALL" to allow the user to select all from that field. I am trying to build the linq statement dynamically to ignore the field if the drop down box is set to "ALL". Is this possible? I want to see if I can do this in one single statement. The example below only shows 2 dropdown boxes, but my actually case has up to 5.
If I choose to use if then statements, I end up with spaghetti code.
DropDownList drpOwners = this.grdOtherQuotes.HeaderRow.FindControl("drpOwners") as DropDownList;
DropDownList drpCompanyName = this.grdOtherQuotes.HeaderRow.FindControl("drpCompanyName") as DropDownList;
var filteredList = (from x in allQuotes
where (drpOwners.SelectedValue != ALL) ? x.SalesRepFullName == drpOwners.SelectedValue:true
&& drpCompanyName.SelectedValue != ALL ? x.CompanyName == drpCompanyName.SelectedValue: true
select x);
Personally, I'd find having this broken up to be simpler:
IEnumerable<Quote> filteredList = allQuotes;
// If using EF or LINQ to SQL, use: IQueryable<Quote> filteredList = allQuotes;
if (drpOwners.SelectedValue != ALL)
filteredList = filteredList.Where(x => x.SalesRepFullName == drpOwners.SelectedValue);
if (drpCompanyName.SelectedValue != ALL)
filteredList = filteredList.Where(x => x.CompanyName == drpCompanyName.SelectedValue);
// More conditions as needed
This really isn't any longer, and it's far simpler to follow.
If you really wanted to be able to write this as a "one-liner", you could make an extension method to build the query. For example, if using Entity Framework:
static IQueryable<T> AddCondition(this IQueryable<T> queryable, Func<bool> predicate, Expression<Func<T,bool>> filter)
{
if (predicate())
return queryable.Where(filter);
else
return queryable;
}
This would then let you write this as:
var filteredList = allQuotes
.AddCondition(() => drpOwners.SelectedValue != ALL, x => x.SalesRepFullName == drpOwners.SelectedValue)
.AddCondition(() => drpCompanyName.SelectedValue != ALL, x.CompanyName == drpCompanyName.SelectedValue);
You could, of course, take this even further, and make a version that hard-wires the predicate to check a combo box against "ALL", making the predicate shorter (just the combo box).
You could create a helper method that handles the All logic. Something like:
private bool CompareSelectedValue(string value, string dropDownValue)
{
if(dropDownValue == "ALL")
return true;
else
return value == dropDownValue;
}
Then your query could be:
var filteredList = (from x in allQuotes
where (CompareSelectedValue(x.SalesRepFullName, drpOwners.SelectedValue)
&& CompareSelectedValue(x.CompanyName, drpCompanyName.SelectedValue)
select x);
Create extension method(s) which encapsulate the where logic so it looks cleaner:
var filteredList = allQuotes.WhereDropOwnersAreContained()
.WhereCompanyIsContained()
...
;
List<DTOeduevent> newList = new List<DTOeduevent>();
foreach (DTOeduevent e in eduList.FindAll(s =>
s.EventClassID.Equals(cla)
&& s.LocationID.Equals(loc)
&& s.EducatorID.Equals(edu)))
newList.Add(e);
cla, loc, edu can be (null or empty) or supplied with values--
basically how can I simply return the original list (eduList) if cla, loc, edu are all null
or search by loc, search by loc, edu, search by edu, cla -- etc........
my sample code only makes a new list if all 3 vars have values--
is there an elegant way to do this, without brute force if statements?
List<DTOeduevent> newList = eduList.FindAll(s =>
(cla == null || s.EventClassID.Equals(cla))
&& (loc == null || s.LocationID.Equals(loc))
&& (edu == null || s.EducatorID.Equals(edu)));
Assuming the values are Nullable value types or classes. If they're strings, you could replace cla == null with String.IsNullOrEmpty(cla).
IEnumerable<DTOeduevent> newList = eduList;
if (cla != null)
{
newList = newList.Where(s => s.EventClassID == cla);
}
if (loc != null)
{
newList = newList.Where(s => s.LocationID == loc);
}
if (edu != null)
{
newList = newList.Where(s => s.EducatorID == edu);
}
newList = newList.ToList();
Due to deferred execution, the Where statements should all execute at once, when you call ToList; it will only do one loop through the original list.
I would personally lean towards something that encapsulated the logic of what you seem to be doing here: checking that a found id is equal to some search id. The only wrinkle is how to get that check for null or empty in there first.
One way to do that is by using a static extension method:
public static class DtoFilterExtensions
{
public static bool IsIdEqual(this string searchId, string foundId) {
Debug.Assert(!string.IsNullOrEmpty(foundId));
return !string.IsNullOrEmpty(searchId) && foundId.Equals(searchId);
}
}
I would also lean towards using LINQ and IEnumerable<> as Domenic does, even though you could make it work with List.FindAll just as easily. Here would be a sample usage:
public void Filter(string cla, string loc, string edu) {
var startList = new List<DTOeduevent>();
var filteredList = startList
.Where(x => x.classId.IsIdEqual(cla) && x.locationId.IsIdEqual(loc) && x.educatorId.IsIdEqual(edu));
Show(filteredList.ToList());
}
In your own code of course you have got that start list either in a member variable or a parameter, and this assumes you have got some method like Show() where you want to do something with the filtered results. You trigger the deferred execution then, as Domenic explained with the ToList call (which is of course another extension method provided as part of LINQ).
HTH,
Berryl
Does anyone know if there is a way to test for list membership utilizing a list. For example I have a class named Membership which has a property Rebates which is of type List<Enums.RebateType>. I want to test using a lambda expression to see if that list contains any rebates that are of a specific type. My orginal lambda expression is as follows
return Membership.Rebates.Exists(rebate =>
rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
Instead of having to do the following:
return Membership.Rebates.Exists(rebate =>
(rebate.RebateType == Enums.RebateType.A &&
rebate.RebateStatus == Enums.RebateStatus.Approved) ||
(rebate.RebateType == Enums.RebateType.B &&
rebate.RebateStatus == Enums.RebateStatus.Approved));
I was wondering if something similar to the following mocked up SQL syntax could be done via one Lambda expression.
SELECT COUNT(*)
FROM Membership.Rebates
WHERE RebateType IN (ValidRebateTypes) AND Approved = true
ValidRebateTypes is curently a List<Enums.RebateType> that I am testing for i.e. ValidRebateTypes = (Enums.RebateType.A, Enums.RebateType.B).
The work around I currently have is as follows:
bool exists = false;
foreach (Enums.RebateType rebateType in ValidRebateTypes())
{
exists = Membership.Rebates.Exists(
rebate =>
rebate.RebateType == rebateType &&
rebate.RebateStatus == Enums.RebateStatus.Approved);
if (exists) { break; }
}
return exists;
Sounds like you want:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
You can then use .Count() for the count:
Membership.Rebates.Where(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved)
.Count();
Or .Any() to determine the existence of any that satisfy that condition
Membership.Rebates.Any(r => ValidRebateTypes.Contains(r.RebateType)
&& r.RebateStatus == Enums.RebateStatus.Approved);
In addition to Marc's suggestion, I would recomment making ValidRebateTypes a HashSet<Enums.RebateType>. Not only is this likely to be more efficient (although possibly not for a small set), it also reveals your intent (ie. that there only be one of each value of RebateType in it).