I have a linq statement as such
dbContext.Items
.Where(
p =>
(p.Client.Contact != null && p.Client.Contact.Firstname.ToLower().Contains(searchText.ToLower()))
||
(p.Client.Contact != null && p.Client.Contact.Surname.ToLower().Contains(searchText.ToLower()))
||
(p.PolicyNumber != null && p.PolicyNumber.ToLower().Contains(searchText.ToLower()))
||
(
p.PolicyLivesAssureds
.Where(
pl =>
pl.Contact != null && pl.Contact.Firstname.ToLower().Contains(searchText.ToLower())
|| pl.Contact.Surname.ToLower().Contains(searchText.ToLower())
).Count() > 0
)
)
).OrderBy(p => p.IeUtem);
This is actually needed in an autocomplete. What I want to do is being able to know exactly which among my 5 conditions has been matched and display the particular item that has been matched. For example say that PolicyNumber has been matched i want to send only policynumber for that row and for others if name has been matched i want to send only the name for that row.
Is there a way to do this;
This is a bit more of a food for thought answer as it has flaws in it's approach, but I think it does solve your problem:
double[] items = { 1, 2, 3, 4, 5 };
IEnumerable<Tuple<double, int>> results = items.Select(x =>
{
int index = 0;
foreach (var condition in new Func<bool>[]
{
// TODO: Write conditions here.
() => x == 1,
() => x == 2
})
{
if (condition() == true)
return index;
else
index++;
}
return -1;
}).Zip(items, (matchedCondtion, item) => Tuple.Create(item, matchedCondtion))
.Where(x => x.Item2 != -1);
I've used a simple double array as an example of the collection to filter, but it's just an example, you can use anything.
The first select returns an integer for each element in the collection. If there is a condition match, it returns the index of the condition. If there is not match it returns -1.
It does this by enumerating over the Func collection and returning the index of the first true condition (emulating the short circuiting of the || operator). If no conditions match it simply returns -1 after evaluating all conditions.
These results are then zipped back up with the original collection (using a Tuple), mapping each element with the index of its matching condition (or -1).
So the example would return:
{ 1, 0 },
{ 2, 1 },
{ 3, -1 },
{ 4, -1 },
{ 5, -1 }
This result is then simply filtered using Where to remove any entries with -1, leaving you with a collection of elements that matched a condition and the index of the condition that matched (in the form of a Tuple).
So to customize this for your solution, you can remove the example conditions and place whatever number of conditions you want at:
// TODO: Write conditions here.
The question becomes how do you want to know which queries match. For example you could do something like this
class AutoCompleteItem {
String Text {get; set;}
Item Item {get; set;}
}
var firstNames = dbContext.Items.Select(p => new AutoCompleteItem { Name = p.Client.Contract.FirstName, Item = p})
var lastNames = dbContext.Items.Select(p => new AutoCompleteItem { Name = p.Client.Contract.SurName, Item = p})
var result = firstName.Union(lastNames).Where(p => p.Name.Contains(searchText)).OrderBy(a => a.Item.IeUtem);
Now AutcompleteItem is a class that contains the text you want (and possibly any other fields you need, like information which field it was that matched)
The Idea here is the MVVM patttern. You have your model (the items). Now you need to construct a viewModel (AutoCompleteItems) that actual aids you in displaying what you want.
Related
I would like to get a specific select with the best performance possible
I currently have a code like this:
var getData = from x in dbContex.MyTable
where x.Car == "audi"
select x;
MyTable has three columns:
Car
ProductionYear
Color
I would like to download a specific vehicle brand with the year of manufacture that has a specific color.
I am currently doing it with reference to the code above:
foreach (var item in getData)
{
if (item.color == "blue") item.ProductionYear = "1999";
if (item.color == "red") item.ProductionYear = "2003";
// etc (...)
}
I would like to assign a specific year for a given car and color if I find it.
A very prosaic example above.
Problem - I currently have 100 colors and only want to check 10 colors.
How can I do it faster so as not to search the entire list and not to use IF statements?
Create an array of the colors, then use Contains method in the predicate expression:
var colors = new [] { "red", "blue" };
var results = dbContext.MyTable
.Where( x => "audi" == x.Car
&& colors.Contains( x.color ) );
Then use a switch statement to assign your years:
foreach(var car in results)
{
car.ProductionYear = car.color switch
{
"blue" => 1999,
"red" => 2003,
_ => // unexpected result, throw exception
}
}
You could also define the years with the colors then work the year assignment into the LINQ query:
var colorYears = new []
{
new {
color = "red",
ProductionYear = 2003,
},
...
};
var colors = colorYears.Select( x => x.color ).ToArray();
var results = (...query from above...)
// enumerate
.ToArray()
// join datasource results with your colorYears array
// on the `color` property
.Join( colorYears,
c => c.color,
cy => cy.color,
( c, cy ) =>
{
// assign the configured production year for the color
c.ProductionYear = cy.ProductionYear;
// return the entity
return c;
} );
There is no scenario, where you don`t use if or switch clause. You want to change data, which is not LINQ case. Only thing I would do - go to switch, rather than If clause.
I don't want to create a new thread so I will write here - a question about LINQ of course and data comparison in the array
I have this code:
var dataTest = (dbContex.MyTable.Where(x => x.col1 == str1
&& x.col2 == str2 && x.col3 == str3).Select(x => x.ID ).FirstOrDefault());
I want to check if there is such a row for these three fields and get its ID - everything is fine when I have values, but when any field is NULL - it cannot compare it and returns information that there is no such record.
the goal is to check if there is a duplicate with these values in MyTable based on these three fields - except that each of these fields can be NULL
how best to compare NULL?
I've been trying to solve this by reading what was on StackOverflow and the information was quiet helpful but i can't seem to implement the things i found.
I have a data List and i want to check if an entry exists in the data set that match 2 variables i provide.
public void SaveWaveDataFor( List<WaveData> newData )
{
foreach(WaveData wave in newData)
{
//WaveData item = data.FirstOrDefault( o => o.id == wave.id );
var item = data.Select( o => new{ wave.id, wave.waveNumber } );
Debug.Log( item.id );
}
}
If you want to get all of the wave objects that match two criteria, you can use a Where() clause:
// items will be an IEnumerable<WaveData> containing the matching objects
// where id == matchId and waveNumber == matchNumber
var items = data.Where(o => o.id == matchId && o.waveNumber == matchNumber);
The Select() clause is typically used to transform the matching elements into objects of another type.
The commented out line is better for this actually.
FirstOrDefault will return either the first matching item or null if no items match.
On the other hand, you could use Any() if you just want to know if an item exists.
Any(x=>x.Id == matchId) will return true only if the list contains an item with a matching Id, false otherwise.
You would do it like this:
public void SaveWaveDataFor( List<WaveData> newData )
{
int waveIdToMatch = 1;
int waveNumberToMatch = 2;
foreach(WaveData wave in newData)
{
WaveData item = data.FirstOrDefault( o => o.id == waveIdToMatch && o.waveNumber == waveNumberToMatch );
//if a match exists, item will not be a WaveData object, otherwise it will be null
Debug.Log( item.id );
}
}
If you just want to check whether entry exists or not then you can use Any Operator from Linq.
bool recordsExists = data.Any(o => o.id == matchId && o.waveNumber == matchNumber);
I have a list stored in resultlist as follows:
var resultlist = results.ToList();
It looks something like this:
ID FirstName LastName
-- --------- --------
1 Bill Smith
2 John Wilson
3 Doug Berg
How do I remove ID 2 from the list?
List<T> has two methods you can use.
RemoveAt(int index) can be used if you know the index of the item. For example:
resultlist.RemoveAt(1);
Or you can use Remove(T item):
var itemToRemove = resultlist.Single(r => r.Id == 2);
resultList.Remove(itemToRemove);
When you are not sure the item really exists you can use SingleOrDefault. SingleOrDefault will return null if there is no item (Single will throw an exception when it can't find the item). Both will throw when there is a duplicate value (two items with the same id).
var itemToRemove = resultlist.SingleOrDefault(r => r.Id == 2);
if (itemToRemove != null)
resultList.Remove(itemToRemove);
Short answer:
Remove (from list results)
results.RemoveAll(r => r.ID == 2); will remove the item with ID 2 in results (in place).
Filter (without removing from original list results):
var filtered = result.Where(f => f.ID != 2); returns all items except the one with ID 2
Detailed answer:
I think .RemoveAll() is very flexible, because you can have a list of item IDs which you want to remove - please regard the following example.
If you have:
class myClass {
public int ID; public string FirstName; public string LastName;
}
and assigned some values to results as follows (used for all examples below):
var results = new List<myClass> {
new myClass { ID=1, FirstName="Bill", LastName="Smith" }, // results[0]
new myClass { ID=2, FirstName="John", LastName="Wilson" }, // results[1]
new myClass { ID=3, FirstName="Doug", LastName="Berg" }, // results[2]
new myClass { ID=4, FirstName="Bill", LastName="Wilson" } // results[3]
};
Then you can define a list of IDs to remove:
var removeList = new List<int>() { 2, 3 };
And simply use this to remove them:
results.RemoveAll(r => removeList.Any(a => a==r.ID));
It will remove the items 2 and 3 and keep the items 1 and 4 - as specified by the removeList. Note that this happens in place, so there is no additional assigment required.
Of course, you can also use it on single items like:
results.RemoveAll(r => r.ID==4);
where it will remove Bill with ID 4 in our example.
A last thing to mention is that lists have an indexer, that is, they can also be accessed like a dynamic array, i.e. results[3] will give you the 4th element in the results list (because the first element has the index 0, the 2nd has index 1 etc).
So if you want to remove all entries where the first name is the same as in the 4th element of the results list, you can simply do it this way:
results.RemoveAll(r => results[3].FirstName == r.FirstName);
Note that afterwards, only John and Doug will remain in the list, Bill is removed (the first and last element in the example). Important is that the list will shrink automatically, so it has only 2 elements left - and hence the largest allowed index after executing RemoveAll in this example is 1 (which is results.Count() - 1).
Some Trivia:You can use this knowledge and create a local function
void myRemove() { var last = results.Count() - 1;
results.RemoveAll(r => results[last].FirstName == r.FirstName); }
What do you think will happen, if you call this function twice?
Like
myRemove(); myRemove();
Answer (click to show):
The first call will remove Bill at the first and last position, the second call will remove Doug and only John Wilson remains in the list.
Note: Since C# Version 8, you can as well write results[^1] instead of var last = results.Count() - 1; and results[last]:
void myRemove() => results.RemoveAll(r => results[^1].FirstName == r.FirstName);
So you would not need the local variable last anymore (see indices and ranges). Furthermore, since it is a one-liner, you don't require the curly braces and can use => instead.
For a list of all the new features in C#, look here.
DotNetFiddle: Run the demo
resultList = results.Where(x=>x.Id != 2).ToList();
There's a little Linq helper I like that's easy to implement and can make queries with "where not" conditions a little easier to read:
public static IEnumerable<T> ExceptWhere<T>(this IEnumerable<T> source, Predicate<T> predicate)
{
return source.Where(x=>!predicate(x));
}
//usage in above situation
resultList = results.ExceptWhere(x=>x.Id == 2).ToList();
You don't specify what kind of list, but the generic List can use either the RemoveAt(index) method, or the Remove(obj) method:
// Remove(obj)
var item = resultList.Single(x => x.Id == 2);
resultList.Remove(item);
// RemoveAt(index)
resultList.RemoveAt(1);
More simplified:
resultList.Remove(resultList.Single(x => x.Id == 2));
there is no needing to create a new var object.
There is another approach. It uses List.FindIndex and List.RemoveAt.
While I would probably use the solution presented by KeithS (just the simple Where/ToList) this approach differs in that it mutates the original list object. This can be a good (or a bad) "feature" depending upon expectations.
In any case, the FindIndex (coupled with a guard) ensures the RemoveAt will be correct if there are gaps in the IDs or the ordering is wrong, etc, and using RemoveAt (vs Remove) avoids a second O(n) search through the list.
Here is a LINQPad snippet:
var list = new List<int> { 1, 3, 2 };
var index = list.FindIndex(i => i == 2); // like Where/Single
if (index >= 0) { // ensure item found
list.RemoveAt(index);
}
list.Dump(); // results -> 1, 3
Happy coding.
Try this code:
resultlist.Remove(resultlist.Find(x => x.ID == 2));
... or just resultlist.RemoveAt(1) if you know exactly the index.
{
class Program
{
public static List<Product> list;
static void Main(string[] args)
{
list = new List<Product>() { new Product() { ProductId=1, Name="Nike 12N0",Brand="Nike",Price=12000,Quantity=50},
new Product() { ProductId =2, Name = "Puma 560K", Brand = "Puma", Price = 120000, Quantity = 55 },
new Product() { ProductId=3, Name="WoodLand V2",Brand="WoodLand",Price=21020,Quantity=25},
new Product() { ProductId=4, Name="Adidas S52",Brand="Adidas",Price=20000,Quantity=35},
new Product() { ProductId=5, Name="Rebook SPEED2O",Brand="Rebook",Price=1200,Quantity=15}};
Console.WriteLine("Enter ProductID to remove");
int uno = Convert.ToInt32(Console.ReadLine());
var itemToRemove = list.Find(r => r.ProductId == uno);
if (itemToRemove != null)
list.Remove(itemToRemove);
Console.WriteLine($"{itemToRemove.ProductId}{itemToRemove.Name}{itemToRemove.Brand}{itemToRemove.Price}{ itemToRemove.Quantity}");
Console.WriteLine("------------sucessfully Removed---------------");
var query2 = from x in list select x;
foreach (var item in query2)
{
/*Console.WriteLine(item.ProductId+" "+item.Name+" "+item.Brand+" "+item.Price+" "+item.Quantity );*/
Console.WriteLine($"{item.ProductId}{item.Name}{item.Brand}{item.Price}{ item.Quantity}");
}
}
}
}
I have a DataTable. I can also use Linq.
In a DataTable have many columns, and rows. One of the column is called as feedCode. its type is string. in database it's length is 7 varchar, nullable.
feedCode may contain values as 9051245, 9051246, 9051247, 9031454, 9021447.
Method must return most matched (in this case starting with 905) value 905 (first 3 character of string)?
thanks.
Try to use this code:
var feedCodes = new string[] { "9051245", "9051246", "9051247", "9051245", "9031454", "9021447" };
var mostOccuring = feedCodes.Where(feedCode => feedCode != null)
.GroupBy(feedCode => feedCode.Length < 3 ? feedCode : feedCode.Substring(0, 3))
.OrderByDescending(group => group.Count())
.FirstOrDefault();
if(mostOccuring == null)
{
//some exception handling
}
else
{
//process mostoccuring.Key
}
this code also handle feedcodes with length less than 3 (even empty strings). If you don't want to use them just filter them out in where statement.
Maybe i didn't understand your question correctly but maybe this will be a starting point for your:
//The feedCodes (i put one in two times, to have one appearing most often)
var values = new string[] { "9051245", "9051246", "9051247", null, "", "9051245", "9031454", "9021447" };
//Just filter the list for filled up values
var query = values.Where(value => !String.IsNullOrEmpty(value))
//and group them by their starting text
.GroupBy(value => value.Substring(0, 3))
//order by the most occuring group first
.OrderByDescending(group => group.Count());
//Iterate over all groups or just take the first one with query.First() or query.FirstOrDefault()
foreach (var group in query)
{
Console.WriteLine(group.Key + " Count: " + group.Count());
}
I'm trying to select a subgroup of a list where items have contiguous dates, e.g.
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
1 41 Meeting with John 03/06/2010
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
5 41 Meeting with Kay 14/06/2010
6 41 Meeting Continues 15/06/2010
I'm using a pivot point each time, so take the example pivot item as 3, I'd like to get the following resulting contiguous events around the pivot:
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
My current implementation is a laborious "walk" into the past, then into the future, to build the list:
var activity = // item number 3: Meeting Continues (09/06/2010)
var orderedEvents = activities.OrderBy(a => a.ActivityDate).ToArray();
// Walk into the past until a gap is found
var preceedingEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID);
DateTime dayBefore;
var previousEvent = activity;
while (previousEvent != null)
{
dayBefore = previousEvent.ActivityDate.AddDays(-1).Date;
previousEvent = preceedingEvents.TakeWhile(a => a.ID != previousEvent.ID).LastOrDefault();
if (previousEvent != null)
{
if (previousEvent.ActivityDate.Date == dayBefore)
relatedActivities.Insert(0, previousEvent);
else
previousEvent = null;
}
}
// Walk into the future until a gap is found
var followingEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
DateTime dayAfter;
var nextEvent = activity;
while (nextEvent != null)
{
dayAfter = nextEvent.ActivityDate.AddDays(1).Date;
nextEvent = followingEvents.SkipWhile(a => a.ID != nextEvent.ID).Skip(1).FirstOrDefault();
if (nextEvent != null)
{
if (nextEvent.ActivityDate.Date == dayAfter)
relatedActivities.Add(nextEvent);
else
nextEvent = null;
}
}
The list relatedActivities should then contain the contiguous events, in order.
Is there a better way (maybe using LINQ) for this?
I had an idea of using .Aggregate() but couldn't think how to get the aggregate to break out when it finds a gap in the sequence.
Here's an implementation:
public static IEnumerable<IGrouping<int, T>> GroupByContiguous(
this IEnumerable<T> source,
Func<T, int> keySelector
)
{
int keyGroup = Int32.MinValue;
int currentGroupValue = Int32.MinValue;
return source
.Select(t => new {obj = t, key = keySelector(t))
.OrderBy(x => x.key)
.GroupBy(x => {
if (currentGroupValue + 1 < x.key)
{
keyGroup = x.key;
}
currentGroupValue = x.key;
return keyGroup;
}, x => x.obj);
}
You can either convert the dates to ints by means of subtraction, or imagine a DateTime version (easily).
In this case I think that a standard foreach loop is probably more readable than a LINQ query:
var relatedActivities = new List<TActivity>();
bool found = false;
foreach (var item in activities.OrderBy(a => a.ActivityDate))
{
int count = relatedActivities.Count;
if ((count > 0) && (relatedActivities[count - 1].ActivityDate.Date.AddDays(1) != item.ActivityDate.Date))
{
if (found)
break;
relatedActivities.Clear();
}
relatedActivities.Add(item);
if (item.ID == activity.ID)
found = true;
}
if (!found)
relatedActivities.Clear();
For what it's worth, here's a roughly equivalent -- and far less readable -- LINQ query:
var relatedActivities = activities
.OrderBy(x => x.ActivityDate)
.Aggregate
(
new { List = new List<TActivity>(), Found = false, ShortCircuit = false },
(a, x) =>
{
if (a.ShortCircuit)
return a;
int count = a.List.Count;
if ((count > 0) && (a.List[count - 1].ActivityDate.Date.AddDays(1) != x.ActivityDate.Date))
{
if (a.Found)
return new { a.List, a.Found, ShortCircuit = true };
a.List.Clear();
}
a.List.Add(x);
return new { a.List, Found = a.Found || (x.ID == activity.ID), a.ShortCircuit };
},
a => a.Found ? a.List : new List<TActivity>()
);
Somehow, I don't think LINQ was truly meant to be used for bidirectional-one-dimensional-depth-first-searches, but I constructed a working LINQ using Aggregate. For this example I'm going to use a List instead of an array. Also, I'm going to use Activity to refer to whatever class you are storing the data in. Replace it with whatever is appropriate for your code.
Before we even start, we need a small function to handle something. List.Add(T) returns null, but we want to be able to accumulate in a list and return the new list for this aggregate function. So all you need is a simple function like the following.
private List<T> ListWithAdd<T>(List<T> src, T obj)
{
src.Add(obj);
return src;
}
First, we get the sorted list of all activities, and then initialize the list of related activities. This initial list will contain the target activity only, to start.
List<Activity> orderedEvents = activities.OrderBy(a => a.ActivityDate).ToList();
List<Activity> relatedActivities = new List<Activity>();
relatedActivities.Add(activity);
We have to break this into two lists, the past and the future just like you currently do it.
We'll start with the past, the construction should look mostly familiar. Then we'll aggregate all of it into relatedActivities. This uses the ListWithAdd function we wrote earlier. You could condense it into one line and skip declaring previousEvents as its own variable, but I kept it separate for this example.
var previousEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID).Reverse();
relatedActivities = previousEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, prevItem) => items.OrderBy(a => a.ActivityDate).First().ActivityDate.Subtract(prevItem.ActivityDate).Days.Equals(1) ? ListWithAdd(items, prevItem) : items).ToList();
Next, we'll build the following events in a similar fashion, and likewise aggregate it.
var nextEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
relatedActivities = nextEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, nextItem) => nextItem.ActivityDate.Subtract(items.OrderBy(a => a.ActivityDate).Last().ActivityDate).Days.Equals(1) ? ListWithAdd(items, nextItem) : items).ToList();
You can properly sort the result afterwards, as now relatedActivities should contain all activities with no gaps. It won't immediately break when it hits the first gap, no, but I don't think you can literally break out of a LINQ. So it instead just ignores anything which it finds past a gap.
Note that this example code only operates on the actual difference in time. Your example output seems to imply that you need some other comparison factors, but this should be enough to get you started. Just add the necessary logic to the date subtraction comparison in both entries.