Dynamically evaluating string conditions in C# - c#

I have a collection of strings. I need to find out from this collection strings which satisfies some condition e.g. that string contains A and B or C. These criteria are specified by the user so they are dynamic. In Linq it should be something like,
List<String> items = new List<string> { "sdsdsd", "sdsd", "abc"};
var query = from item in items
where item.Contains("a") && item.Contains("b") || item.Contains("c")
select item;
I want to make the where condition dynamic so that it can work for any input by the user. Is it possible to do this in C# without using any external library. Maybe using Linq or something else which is builtin into .Net framework.
Thanks,
Gary

Although you don't want to use external libraries, there is one which is just fantastic, and that is PredicateBuilder. Predicate builder allows you to build up a set of predicates to match items against, e.g.:
var predicate = PredicateBuilder.True<string>();
predicate = predicate
.And(p => p.Contains("a"))
.And(p => p.Contains("b"));
var matches = items.Where(predicate);

If you want to do it on your own, start here:
Dynamic Predicates:
http://msdn.microsoft.com/en-us/library/bb513731.aspx
Dynamic Expression Trees:
http://msdn.microsoft.com/en-us/library/bb882637.aspx
I think this is more than you wanted, and would strongy suggest to use some (lightweight) ready and tested library, that does the conversion from user-strings to runtime-queries for you.

(source: scottgu.com)
You need something like this? Use the Linq Dynamic Query Library (download includes examples).
Check out ScottGu's blog for more examples.

Related

Performing a wildcard search

I have a search where I use LINQ with EF. When ever the search criteria are null or empty I need to return everything. Currently I've used if conditions as a solution. and from that I moved to a solution like this.
data = data
.Where(p => !string.IsNullOrEmpty(searchriteria1)? p.field1.Contains(searchriteria1) : true)
.Where(p => !string.IsNullOrEmpty(searchriteria2)? p.field2.Contains(searchriteria2) : true);
Is there a better way to do this? maybe use an extension or any better approach?
You could check the search criteria field previously and build up the query this way:
IQueryable<Foo> data = context.Foo.AsQueryable();
if(!string.IsNullOrEmpty(searchriteria1))
{
data = data.Where(p => p.field1.Contains(searchriteria1));
}
if (!string.IsNullOrEmpty(searchriteria2))
{
data = data.Where(p => p.field2.Contains(searchriteria2));
}
There are two parts to the question. How to filter dynamically and how to filter efficiently.
Dynamic criteria
For the first question, there's no need for a catch-all query when using LINQ. Catch-all queries result in inefficient execution plans, so it's best to avoid them.
LINQ isn't SQL though. You can construct your query part by part. The final query will be translated to SQL only when you try to enumerate it. This means you can write :
if(!String.IsNullOrEmpty(searchCriteria1))
{
query=query=.Where(p=>p.Field1.Contains(searchCriteria1);
}
You can chain multiple Where call to get the equivalent of multiple AND criteria.
To generate more complex queries using eg OR you'd have to construct the proper Expression<Func<...,bool>> objects, or use a library like LINQKit to make this bearable.
Efficiency
Whether you can write an efficient query depends on the search criteria. The clause field LIKE '%potato%' can't use any indexes and will end up scanning the entire table.
On the other hand, field LIKE 'potato% can take advantage of an index that covers field because it will be converted to a range search like field >='potato' and field<='potatp.
If you want to implement autocomplete or spell checking though, you often want to find text that has the fewest differences from the criteria.
Full Text Search
You can efficiently search for words, word variations and even full phrases using Full-Text Search indexes and FTS functions like CONTAINS or FREETEXT.
FTS is similar to how Google or ... StackOverflow searches for words or sentences.
Quoting form the docs:
CONTAINS can search for:
A word or phrase.
The prefix of a word or phrase.
A word near another word.
A word inflectionally generated from another (for example, the word drive is the inflectional stem of drives, drove, driving, and driven).
A word that is a synonym of another word using a thesaurus (for example, the word "metal" can have synonyms such as "aluminum" and "steel").
FREETEXT on the other hand is closer to how Google/SO work by searching for an entire phrase, returning close matches, not just exact matches.
Both CONTAINS and FREETEXT are available in EF Core 5 and later, through the DbFunctions.Contains and DbFunctions.FreeText functions.
This means that if you want to search for a word or phrase, you could construct a proper FTS argument and use :
var searchCriteria1="' Mountain OR Road '";
if(!String.IsNullOrEmpty(searchCriteria1))
{
query=query=.Where(p=>DbFunctions.Contains(p.Field1.Contains(searchCriteria1));
}
That's a lot easier than using LINQKit.
Or search for ride, riding, ridden with :
var searchCriteria1="' FORMSOF (INFLECTIONAL, ride) '";
shorter syntax
data.Where(p => (string.IsNullOrEmpty(searchriteria1) || p.field1.Contains(searchriteria1))
&& (string.IsNullOrEmpty(searchriteria2) || p.field2.Contains(searchriteria2)));
public static List<Test> getAll(Expression<Func<Test, bool>> filter = null)
{
return filter == null ? context.Set<Test>().ToList() : context.Set<Test>().Where(filter).ToList();
}
If you want to filter
var l=getAll(p => p.field1.Contains(searchriteria1)&&p.field2.Contains(searchriteria2));
no filter
var l=getAll();

How to use Orderby Clause with IEnumerable

I have written following code:
IEnumerable<Models.bookings> search = new List<bookings>();
search = new available_slotsRepositories().GetAvailableSlot(param1,param2);
var data = from s in search.AsEnumerable().
OrderByDescending(c => c.BookingDate)
select s;
i have also tried this and it does not work:
search.OrderByDescending(c => c.BookingDate);
Third line gives me following error:
Expression cannot contain lambda expressions
Any one guide me how can i fix this issue?
Any help would be appreciated.
Thank you!
why r u using new List()??
follow the below pattern
IEnumerable<Step> steps = allsteps.Where(step => step.X <= Y);
steps = steps.OrderBy(step => step.X);
NOTE:
IEnumerable makes no guarantees about ordering, but the implementations that use IEnumerable may or may not guarantee ordering.
For instance, if you enumerate List, order is guaranteed, but if you enumerate HashSet no such guarantee is provided, yet both will be enumerated using the IEnumerable interface
Perhaps you are looking for the IOrderedEnumerable interface? It is returned by extensions methods like OrderBy() and allow for subsequent sorting with ThenBy().
Have you tried
var data = (from s in search
OrderByDescending(c => c.BookingDate)
select s).ToList();
That will make a List which is IEnumerable.
I'm not sure why you need "new" if as you say GetAvailableSlot returns an IEnumerable. What I think your code should look like assuming GetAvailableSlot returns IEnumerable is this:
var data = available_slotsRepositories().GetAvailableSlot(param1,param2).ToList().OrderByDescending(c => c.BookingDate);
All you're doing to your recordset is ordering the results there is no need to have multiple variables declared. If this still doesn't work then we need to see more of the code in order to see what the problem is...

Using StringCollection specified in Application Settings in a LINQ-to-Entities Query

In my application, I have Property Setting which is of type String.Collections.Specialized.StringCollection. It contains a list of customer codes such as MSFT, SOF, IBM etc. I'm trying to use this in a Linq-to-Entities query in the where clause:
var ShippedOrders = dbcontext.Orders
.Where(s=>(s.Status.Description.Equals("Shipped") && !Properties.Settings.Default.CustomersToExclude.Contains(s.CustomerCode)));
This fails as Contains is not recognized by Linq-to-Entities with a message similar to:
"LINQ-to-Entities does not recognize the method Contains...."
How do I revise the code above to avoid this error?
A shorter path is
myProperties.Settings.Default.CustomersToExclude.Cast<string>().Contains(blah);
That's a handy trick for any situation where a collection isn't inherently LINQ-aware.
Since your question is tagged as C# 4 use a List<string> instead (StringCollection is ancient) and your query should work. Also you should resolve your list reference outside your query:
List<string> customersToExclude = ..
var ShippedOrders = dbcontext.Orders
.Where(s=>(s.Status.Description.Equals("Shipped")
&& !customersToExclude.Contains(s.CustomerCode)));
Edit:
Just copy your customers to an array and use that:
var customerstoExclude = new string[Properties.Settings.Default.CustomersToExclude.Count];
myProperties.Settings.Default.CustomersToExclude.CopyTo(customerstoExclude, 0);
This is answered in a related question. EF4 apparently supports Contains directly, though, so that'd be my prefered solution... :)

c# Fluent SQL Helper - Syntax improvement

I'm writing a little library to help building SQL requests (only doing SELECTs for the moment) but I'm not satisfied with an aspect of the syntax, here's an exemple to explain:
var db = FluentDb.WithConnectionString("SqlCeTest");
var query = db.From("Customers")
.Where(FS.Col("Age") > 18 & FS.Col("Name").StartsWith("L"))
.OrderBy("BirthDate")
.Select("Name", "Age", "BirthDate");
var customers = query.ToList((r) => new
{
Name = r.Get<string>("Name"),
Age = r.Get<int>("Age"),
BirthDate = r.Get<DateTime?>("BirtDate")
});
The part I'd like to improve is the FS.Col("ColumnName"), it's supposed to stand for FluentSql.Column (return a new FluentColumn(columnName)), but I find it a bit long in that context, what I'd really like is to be able to use just Col("ColumnName")...
Do anybody see a trick I could use to achieve that, or another syntax idea?
My ideas:
Extension method on string: Where("Name".Col() == "Jon")
Lambda expression with factory object using indexer: .Where(c => c["Name"] == "Jon")
Anyone see something better/shorter/nicer?
Edit:
my second idea looks good but there's a downside if i use it in another context:
I sometime need to use FluentColumns in Select (or OrderBy, or GroupBy) statements like that:
query.Select(FS.Col("Name").As("Customer"), FS.Col("OrderId").Count().As("OrdersCount"));
I would have to repeat the 'c => ' for each column...
A twist on your second option (which is pretty good) would be to use a dynamic expandoobject in the lambda instead of a string indexer.
http://blogs.msdn.com/b/csharpfaq/archive/2009/10/01/dynamic-in-c-4-0-introducing-the-expandoobject.aspx
Just for information, I decided to go with an indexer syntax on the FluentDb instance:
db["Customer", "AddressId"] mean column AddressId of table Customer,
an alternative syntax is available: db["Customer"]["AddressId"]
So in the end, it's gonna be (I still need to find a trick to make the Column declaration without table nice):
.Where(db["Customer", "Name"] == "Jon")

Using [0] or First() with LINQ OrderBy

If I have a structure like this
Albums
- Album
- Discs
- Tracks
and I want to order a collection of albums by the title of the first track on the first disc.
Is there something similar to the following I could do (keeping in mind I need to use the OrderBy extension method that accepts a string)?
albums.OrderBy("Discs[0].Tracks[0].Title")
I need to be able to sort using a string expression thus the need to use the OrderBy method i.e. albums.OrderBy("Track[0].Title"). The reason for this is our custom framework uses a sort expression (e.g. "Title") passed back from a GridView which is looked up in a dictionary (e.g. "Track[0].Title") to get the correct order by clause. That is, the field and direction of sorting is dynamically determined at runtime.
or
albums.OrderBy("Discs.First().Tracks.First().Title")
Untested, but how about:
var query = from album in albums
let disc = album.Discs.First()
let track = disc.Tracks.First()
orderby track.Title
select album;
LINQ has two ways to query "from . in .." and Lambda expressions. They way you were almost writing it looked Lambda-ish. Here would be the Lambda expression:
albums.OrderBy(a=>a.Discs.First().Tracks.First().Title)
I used variable 'a' to indicate album but you can use any variable, this is identical to the first expression:
albums.OrderBy(album=>album.Discs.First().Tracks.First().Title)
or you can use the from obj in obj form as mention in the other answers.
How about this, in order to satisfy your need for an initial query that does not perform the sorting? This uses anonymous types to store the album information, plus the name of the first track so you can sort on it later.
var query = from album in albums
let disc = album.Discs.First()
let track = disc.Tracks.First()
select new { Album = album, FirstTrack = track.Title };
var sortedQuery = from album in query
order by album.FirstTrack
select album.Album;
Sorry people,
It looks like the OrderBy method that I am asking about and trying to use is specific to the ORM (genom-e) that we are using and is not reflected on the .net Queryable or IEnumerable classes (unlike the majority of genom-e's LINQ functionality). There is no OrderBy overload that accepts a string in .net, this is specific to genom-e.
Those of you using .net encountering a similar problem should probably give either of the above two answers a try.

Categories