Querying a list based on a string input - c#

I'm trying to accomplish what I feel like should be a straightforward task, but have found it much more complicated than I expected.
Essentially, given:
public class MyObject
{
public int A;
public float B;
public string C;
}
List<MyObject> objectList;
I would like to be able to read in strings something like:
"A < 1"
"B > 0.5"
"C = \"text\""
and for each of those get a List of items in objectList satisfying the requirement.
I've been working with LINQ queries like:
objectList.Where(obj => obj.A < 1)
so far, but am unable to figure out how to create queries like that with the field name.
Is there something straightforward that I am missing? Or is my whole approach here flawed?

I thinks you can use Expression tree, which you may be create own lambda expression and pass the it Where function.
For example please check these link:
Dynamically generate LINQ queries
Or you can use System.Linq.Dynamic namespace.:
https://docs.telerik.com/data-access/developers-guide/linq-support/data-access-feature-ref-linq-support-dynamic-linq

You can apply AND operator (&&):
objectList.Where(obj => obj.A < 1 && obj.B > 0.5 && obj.C == "text").ToList();
More to read Filtering in LINQ
EDIT:
If you want to query based on string you can use System.Linq.Dynamic
objectList.Where("string predicate ..");

Related

Translate/Separate IQueryable expressions?

consider the following scenario:
public class DBEntry {
public string Id;
}
public class ComputedEntry {
public string Id;
public int ComputedIndex;
}
IQueryable<DBEntry> databaseQueryable; // Somewhere hidden behind the API
IQueryable<ComputedEntry> entryQueryable; // Usable with the API
Let's assume each DBEntry has a unique Id and not much else. A ComputedEntry has a 1:n relationship with DBEntry, meaning that a DBEntrycan be expanded into more than a single ComputedEntryduring execution of the query.
Now, I am trying to query entryQueryable to get a range of computed indices, e.g:
entryQueryable.Where(dto => dto.ComputedIndex < 10 && dto.Id == "some-id");
What I'm looking for is a way of separating the given query expression to only push down the relevant parts of the query to the databaseQueryable. In the example above something like this should happen (probably in the implementation of IQueryableProvider.Execute when using the entryQueryable):
var results = databaseQueryable.Where(e => e.Id == "some-id").ToList();
int i = 0;
return results.Select(e => new ComputedEntry(e.Id, i++));
So basically I'd like the query to be separated and the relevant/compatible parts should be pushed down to the databaseQueryable.
The obvious question would be: How should I approach this? I tried to figure out a way of separating the expression with an ExpressionVisitor, but haven't been very successful here and it seems like this is a rather complex task.
Any ideas? Maybe there is an already existing method of optimizing/translating the query I am not aware of? I have looked through the documentation but couldn't find anything useful here.
Many thanks for your suggestions!

Encapsulation and use of LINQ logic in functions using dot notation

I tried my best to explain in the title, however I am trying to achieve giving linq statements an 'alias' and still use them in dot notation. Allow me to explain further.
below we have a list that has a linq statement applied:
private List<string> _matches;
var output = _matches.Where(x => x.EntityScore == 100).ToList();
I agree that this is simple to read. However I wish to simplify it further especially when the statements start to get bigger. This is an example of linq getting longer than I care for:
private List<string> _matches;
var matchAddressList = _matches.Where(x => x.EntityDetails.Addresses.Any(x => x.Street.Equals(inputObject.Address)
&& x.StateProvinceDistrict.Equals(inputObject.State)
&& x.City.Equals(inputObject.City))).ToList();
What I am trying to do is alias certain groups of LINQ and then call that linq as a dot operator
for example:
var finalOutput = _matches.perfectMatches().addressMatches(inputObject).someOtherMatchCondition(inputObject)
I think the above line is clear and easily readable. Future devs dont necessarily have to look into the logic. They can read the business domain name and understand what it does.
I want to avoid the following line, as I believe the previous code is more clean:
var finalOutput = someOtherMatchCondition(addressMatches(perfectMatches(_matches)));
the previous line is how I feel you would go about it using functions at a basic level. However I am struggling to find a way to create an alias or encapsulate the linq logic into a business domain name and then use that as a dot operator.
I have tried expression body definitions:
public List<string> perfectMatches => _matches.Where(x => x.EntityScore == 100).ToList();
is this going to require extensions of another class? or the writing of generics? or am I perhaps unaware of a standard way of doing this?
Update: maybe this is helpfull too:
How to add custom methods for LINQ queries (C#)
It has to be an extension method to make use of the dot notation.
Do you mean something like that. It is rather pseudo code than working. You may have to play around with the types or try out some kind of generic approach:
public class ProductionCode
{
public void MyMain()
{
var myList = new List<EntityThingType>() { .... };
var newList = myList.PerfectMatches().AddressMatches(myInputObject).ToList();
}
}
public static class test
{
public static IEnumerable<EntityThingType> PerfectMatches(this IEnumerable<EntityThingType> myList)
{
return myList.Where(x => x.EntityScore == 100);
}
public static IEnumerable<EntityThingType> AddressMatches(this IEnumerable<EntityThingType> myList, MyObjectType inputObject)
{
return myList.Where(x => x.EntityDetails.Addresses.Any(x => x.Street.Equals(inputObject.Address)
&& x.StateProvinceDistrict.Equals(inputObject.State)
&& x.City.Equals(inputObject.City)));
}
}
I think what you are looking for is Extension Methods. You can have the perfectMatches() method be an extension method that takes an IEnumerable<string> and return the same. Then you can chain those together.

How to use a dynamically-created string as a WHERE condition in Linq

I've to create method which returns IQueryable, and tor depends on exact type.
Is it possible to prepare any criteria which could be used after WHERE statement to get that?
for example, if T == License i use "c.fkCustomer == Organization.Customer"
if T== People , I use "c.fkPeople== Organization.People" etc.
XPQuery<T> cQuery = new XPQuery<T>(cSession);
IQueryable CurrQr = from c in cQuery
where "c.fkCustomer == Organization.Customer"
select c;
Can someone suggest something, how to achieve this goal?
I think you would be better off to use a lambda as an argument here rather than dynamic linq eg.
public IQueriable<T> MyQuery<T>(Expression<Func<T, bool>> predicate)
{
return new XPQuery<T>(cSession).Where(predicate)/*and any other bits you want at the moment this is a straight up where clause so kinda pointless*/;
}
then you can call it with:
MyQuery(c=> c.fkCustomer == Organization.Customer)
or
MyQuery(c=> c.fkPeople == Organization.People)
Yes, this can be done. One way to do this is to use the Dynamic Query Library which you can find here along with detailed information on how to use it:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Linq - Is there are way to build up a linq statement from several snippets of linq

I have several methods that use similar linq statements but different enough for them to be in their own methods. So say, for the sake of arguemnt, I had the following linq snippet which is repeated across all methods (the real snippets would be much longer than this):
where su.ObjId == serviceUserId
where cl.StaffMemberId == staffMemberId
If I was working in SQL I could just contatenate the repeated SQL as follows:
private string GetRepeatedSql()
{
return "where su.ObjId = serviceUserId and cl.StaffMemberId = staffMemberId";
}
private void DoSomething()
{
string mySql = "Select * from ...... lots of sql .." + GetRepeatedSql() + ".. some more sql";
}
(Usual health warnings around contatenating SQL string together noted).
Is there something equivalent in Linq? I'm sick of having to make changes in several places - this seems to contravene the DRY principle.
Thanks!
Correct me if I'm wrong but I always thought LINQ statements weren't executed until you actually used them. (Coming from LINQ to NHibernate)
If that is actually the case you could simply just add whatever you need to the existing statement. For example:
var temp=from x in Sometable select x;
Then adding a where clause:
temp = from x in temp where x.ID==1234 select x;
Then order by
temp=from x in temp order by x.ID select x;
I won't lie I have never done it this way but I assume it should work. If someone knows this won't work please explain why. Thanks.
Found this on msdn: http://msdn.microsoft.com/en-us/library/bb397906.aspx
In LINQ the execution of the query is
distinct from the query itself; in
other words you have not retrieved any
data just by creating a query
variable.
So by creating the variable you have not retrieved any data. Although maybe the way I'm doing it above would return data because I am calling from x in temp to change the query.
I do it like this
IQueryable<Publication> pubs = GetPubs();
pubs = ApplySort(pubs, SortBy);
pubs = GetPage(pubs, PageSize, Page);
private IQueryable<Publication> GetPage(IQueryable<Publication> pubs, int PageSize, int Page)
{
return pubs.Skip(PageSize * (Page - 1)).Take(PageSize);
}
private IQueryable<Publication> ApplySort(IQueryable<Publication> pubs, string SortBy)
{
switch (SortBy)
{
case "Latest": return pubs.OrderByDescending(p => p.Posted);
break;
default: return pubs.OrderByDescending(p => p.Posted);
break;
}
}
You can use PredicateBuilder to do this:
The Albahari one here is one I've used recently although there are others around:
http://www.albahari.com/nutshell/predicatebuilder.aspx

"Or" Together Expression<Func<EntityFramework Entity, bool>> in .NET 4, Silverlight RIA domain service

I have a small custom object defined as:
public class TimeSeriesDefinition
{
public int classID;
public DateTime startTime;
public DateTime endTime;
}
I'm passing a List classIDs, a List startTimes, and a List endTimes into an RIA Domain Service function. As a matter of organization, I was grouping these values into a List of TimeSeriesDefinitions and then trying to use a foreach loop to create an expression that would select with AND operators between the values in a class and OR operators between each class or implement a ".Any" query as suggested by the first answer I received below. The problem is that I can't use the TimeSeriesDefinition class in a DomainService function because it is not a primitive type or one of my entity types (maybe I should just make an entity with this type?), so I need another method of achieving the desired query results. My original idea for using expressions that I never got anywhere with is here:
Expression<Func<EventLog, bool>> bounds;
Boolean assignedBounds = false;
foreach (TimeSeriesDefinition ts in reporters)
{
if (assignedBounds.Equals(false))
{
bounds = c => c.reporterID == ts.classID && c.reportDateTime >= ts.startTime && c.reportDateTime <= ts.endTime;
assignedBounds = true;
}
else
{
Expression<Func<EventLog, bool>> newBounds = c => c.reporterID == ts.classID && c.reportDateTime >= ts.startTime && c.reportDateTime <= ts.endTime;
bounds = Expression.Or(Expression.Invoke(bounds), Expression.Invoke(newBounds);
// bounds = Expression<Func<EventLog, bool>>.Or(bounds, newBounds);
}
}
return this.ObjectContext.EventLog.Where(bounds);
My goal is for the resultset to have all records of a ts.classID between ts.startDate and ts.EndDate. From what I've found online, it seems that making sure the parameters are correctly assigned is tricky as well, but right now I'm still getting a
"Cannot implicitly convert type 'System.Linq.Expressions.BinaryExpression' to 'System.Linq.Expressions.Expression>'"
error at the line
bounds = Expression.Or(Expression.Invoke(bounds), Expression.Invoke(newBounds);
Can anybody point me in the right direction? I suppose I could possibly build this whole thing into a query string somehow, but I'd rather not go there.
Thanks in advance for your insight!
If you Funcletize (localize) your references to TimeSeriesDefinition on the client side, you should be able to include them in your query (see Evaluator.PartialEval method in here http://msdn.microsoft.com/en-us/library/bb546158.aspx). You should be able to simply call it on your Expression object and have the references to TimeSeriesDefinitions lifted away to primitive constants:
Evaluator.PartialEval(lambdaExpression);
As for your compilation problem:
bounds = Expression.Or(Expression.Invoke(bounds), Expression.Invoke(newBounds);
The left hand side of that assignment is a generic LambdaExpression. The right hande side is a BinaryExpression. To do the assignment, you need to Lambda the Or and also provide a ParameterExpression for your InvocationExpressions:
var parameterExpression = Expression.Parameter(typeof(EventLog));
bounds = Expression.Lambda<Func<EventLog, bool>>(
Expression.Or(
Expression.Invoke(bounds, parameterExpression),
Expression.Invoke(newBounds, parameterExpression),
parameterExpression);
However...you will probably run into the wonderful fact that RIA doesn't support InvocationExpressions... (I haven't verified this but I know EF doesn't). You've got to Expand your InvocationExpressions to inline them (sort of like with the Funcletlizer mentioned above).
LINQKit ( http://www.albahari.com/nutshell/linqkit.aspx ) provides one out of the box. It also provides helper methods for combining criteria as you mention above. If you don't want the whole dependency on LINQKit, you can grab the source for the same thing here: http://www.java2s.com/Open-Source/CSharp/Content-Management-Systems-CMS/Kooboo/Microsoft/Data/Extensions/DataExtensions.cs.htm
Then just change your Where to:
return this.ObjectContext.EventLog.Where(InvocationExpander.Expand(bounds));
Instead of List<TimeSeriesDefinition> can you use List<Tuple<int, DateTime, DateTime>>. Your query would then be this...
return ObjectContext.EventLog.Where(c =>
reporters.Any(r =>
c.reporterID == r.Item1 &&
c.reportDateTime >= r.Item2 &&
c.reportDateTime <= r.Item3));

Categories

Resources