Dynamically build a condition on a DataTable using a Dictionary - c#

Currently I can query my DataTable (dtContacts) like this:
If I am looking for a person whose FirstName is "John" and LastName is "Smith". My result will be
DataTable dt = dtContacts.AsEnumerable().Where(c => c.Field<string>("FirstName") == "John" && c.Field<string>("LastName") == "Smith").CopyToDataTable();
But such static querying does not help me because my conditions may be slightly more complicated than this. So I would like my LINQ to be a bit more dynamic so it can accommodate the following scenario.
I am going to save my condition in a Dictionary<string,string> like this:
Dictionary<string, string> dicConditions = new Dictionary<string, string>();
dicConditions.Add("FirstName", "John");
dicConditions.Add("LastName", "Smith");
dicConditions.Add("IsCustomer", "Yes");
Note that we can add as many conditions as we want to this dictionary.
And let's assume that the main DataTable dtContacts always contains the column names that are mentioned in the dictionary keys so we do not need to check for that every time.
In the new version we would like our LINQ to act according to dicConditions and return all the values whose FirstName is "John", LastName is "Smith" and IsCustomer is "Yes".
How should this new dynamic LINQ query look like?

You could do it like this:
DataTable dt = dtContacts
.AsEnumerable()
.Where(c =>
dicConditions.All(kv => c.Field<string>(kv.Key) == kv.Value)
).CopyToDataTable();
The idea is to apply LINQ twice - once to the dtContacts, and once to dicConditions inside the Where clause.

You can build the predicate you showed above from a dictionary using an approach like this:
using StringKvp = System.Collections.Generic.KeyValuePair<string, string>;
using DataRowPredicate = System.Func<System.Data.DataRow, bool>;
...
var pred = dicConditions.Aggregate<StringKvp, DataRowPredicate>(r => true,
(net, curr) => r => net(r) && r.Field<string>(curr.Key) == curr.Value);
Then it is just a matter of filtering your collection with this predicate:
var dt = dtContacts.AsEnumerable().Where(pred).CopyToDataTable();

something like this...
var result= dtContacts.Rows.OfType<DataRow>().Where(r=>dicConditions.All(d=>r[d.key]==d.Value));
You may need to properly convert the values of the dictionary to be compatible with the datatable...
Use Regex to support wildcards...etc...

Related

Multiple like in lambda expression

I have a string[] variable which i want to use in the lambda expression to filter data using like clause. I tried using contains, but it works like the 'in' clause, which is not what I need.
Please see the code below which behaves like in clause:
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(c => !strArray.Contains(c.columnName));
Could someone provide me the lambda expression which could filter records using like instead of in using an array.
The only methods LINQ provide for this purpose is .Where(), .StartsWith() or .EndsWith().. Also, there is pretty similar question here How to do SQL Like % in Linq?
Unlike VB.NET, C# has no builtin like-operator. However, you can use the operator from VB.NET easily. What you have to do is to reference the assembly Microsoft.VisualBasic.dll and include a using Microsoft.VisualBasic.CompilerServices; at the top of your file. Then you can do
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(c => !strArray.Any(s => LikeOperator.LikeString(c.columnName, s, CompareMethod.Text)));
I'm not entirely sure what you are trying to do, but I think this is what you are trying to accomplish. If not, please provide some more information and I will update my answer.
To check if a string is equal to a part of another string (which like does), you can use the .Contains method:
bool contains = "Some fancy sentence".Contains("fancy");
This will evaluate to true. Given your example, this would result in the following:
var inventories = ..............AsQuerable();
string[] strArray = <<somevalues>>;
inventories = inventories.Where(inv => !strArray.Any(s => inv.columnName.Contains(s)));
This checks all inventories and removes all inventories where the inventory column name (partially) occurs in any of the strArray values.
It is not pure Lambda expression, but Maybe it can help:
List<SomeObject> inventories = new List<SomeObject>();
// add objects to list...
string[] strArray = <<somevalues>>;
// result will be stored here
List<SomeObject> filtered = new List<SomeObject>();
foreach (var itm in strArray)
{
// LIKE '%SOMEVALUE%'
var match = inventories.Where(x => x.columnName.Contains(itm)).ToList();
// LIKE '%SOMEVALUE'
// var match = inventories.Where(x => x.columnName.StartsWith(itm)).ToList();
// LIKE 'SOMEVALUE%'
// var match = inventories.Where(x => x.columnName.EndsWith(itm)).ToList();
foreach (var m in match)
filtered.Add(m);
}

LINQ ALL Method only works when there is one condition to check

I have a Dictionary<string, string> that contains some conditions that need to be checked against a DataTable.
E.g. If dic contains one entry like (key: "Email", value: "john.smith#gmail.com"), the following has to search for all the rows in the DataTable who have a value of Email equal to "john.smith#gmail.com".
var foundRows = dtContacts.AsEnumerable()
.Where(c => dicConditions.All(kv => c.Field<string>(kv.Key) == kv.Value.ToString()));
This is working when we have only one condition in the dictionary. But I expected it to work for more conditions, too.
For instance, I will now want to check in the DataTable for everyone whose value for FirstName is "John" and value for LastName is "Smith" (case-insensitive). Even though I can see that there is a row in my DataTable with FirstName = "John" and LastName = "Smith", the above LINQ is not returning any value.
What am I doing wrong?
That should work also for multiple conditions(entries in the dictionary).
So the only thing that comes to my mind is that it's currently case sensitive but you explicitly asked for a case-insensitive approach:
var foundRows = dtContacts.AsEnumerable()
.Where(row => dicConditions
.All(kv => String.Equals(row.Field<string>(kv.Key), kv.Value, StringComparison.InvariantCultureIgnoreCase)));

how to get a DataRow object from a datatable using Select function on an array in C#

I'm trying to use the select function filtered by a list as oposed to a value
if dt_old is a datatable, CFKEY is a column of dt_old, this statement uses Select for a specific Value and it work fine.
DataRow[] dt_oldDuplicateRow = dt_old.Select("CFKEY = '1'");
I can't find a way to use select to filter on an array or list based on an other datatable, I would like to do something like this.
DataColumn dc = dt_new.Columns["CFKEY"];
DataRow[] dt_oldDuplicateRow = dt_old.Select("CFKEY in " + dc );
where dt_new is the same format as st_old. any idea?
I am not sure if the list of keys are your choice or a list obtained from dt_new. Anyway,
List<string> listOfDuplicateKeys = createTheKeyList();
DataRow[] dt_oldDuplicateRow = dt_old.Rows.Cast<DataRow>()
.Where(dr => listOfDuplicateKeys.Contains(dr["CFKEY"].ToString()))
.ToArray();
You must refrence to System.Data.DataSetExtensions and use AsEnumerable() for datatable
var results = dt_old.AsEnumerable().Select()
check this question LINQ query on a DataTable
Thanks serdar, it's exactly what I needed.
My list was obtained from dt_new and I slightly changed your code.
var list = dt_new.Rows.OfType<DataRow>()
.Select(dr => dr.Field<string>("CFKEY"))
.ToList();
DataRow[] dt_oldDuplicateRow = dt_old.Rows.OfType<DataRow>()
.Where(dr => list.Contains(dr["CFKEY"].ToString()))
.ToArray();

Dynamic where clause with loop linq c#

I have dictionary with regular expressions and a data table.
var data = ds.Tables["mytable"].AsEnumerable();
var regexlist = new Dictionary<string, Regex>
{
{"PI1", new Regex(#"(-.[^I](-P[^IS]))|(-[^P].(-P[^IS]))")},
{"SM1", new Regex("(-.[^I](-P[^IS]))|(-[^P].(-P[^IS]))")}
};
Now I want to select all rows of the data table that does not match the regular expressions in the list and also the key of the dictionary (the error code).
So far I have this:
var query = data.Select(dr => dr.Field<string>("F1"));
query = regexlist.Aggregate(query, (current, regex1) => current.Where(u => regex1.Value.IsMatch(u) ));
but I think that only the first regex is added as where clause.
And I don't know how to output the "error code"
I hope I explained my problem clear.
jonas
Whole things looks OK except that Where expression that doesn't reflect your objective, you need to invert test clause to get data that does not match regex'es
current.Where(u => regex1.Value.IsMatch(u) == false)

How to pull filtered Data from a Datatable and create a string

I have a dataset ds with two fields, AllowInput int and TypeName string.
I wanna get all TypeName as a comma separated string where AllowInput == 1
This is what I have done so far.
string keys = string.Join(",", ds.Tables[0].Rows.Cast<DataRow>().
Where(x => x["AllowInput"].ToString() == "1").
ToArray().
Cast<DataRow>().
Select(x => x["TypeName"].ToString()).
ToArray());
This works.
But does the code needs to be this verbose?
You can probably drop the following 2 lines:
ToArray().
Cast<DataRow>().
You could also consider using the DataRow extensions defined in Linq to DataSet
Something like:
string keys = string.Join(",", from row in table.AsEnumerable()
where (row.Field<int>("AllowInput") == 1)
select row.Field<string>("TypeName"));

Categories