IList<Item> items = new List<Item>();
items.Add(new Item
{
tag = "{" + Ann + "}",
value = "Anny"
});
items.Add(new Item
{
tag = "{" + John + "}",
value = "Johnny"
});
How can I use Linq to select tag with {John} and replace value with "Jane"?
LINQ is, as the name suggests, more of a query tools. So you can get specific item(s) that you want to modify from a collection using LINQ, but the modification itself is out-of-scope for LINQ.
Assuming that there is always maximum one match to your criteria, you can do as follows :
var john = items.FirstOrDefault(o => o.tag == "{John}");
if(john != null)
{
john.value = "Jane";
}
Otherwise, you can use LINQ Where(o => o.tag == "{John}") to get the target items for modification. But then you'll need to iterate through the result to actually update the value of each matched item.
items.Where(o => o.tag == "{"+John+"}").ToList().ForEach(item =>{
item.value = "Jane";
});
Here is working fiddle
Related
I have 2 set of coolection in memory and i want to return one set based on the 2. My object have the following suructure:
class Item
{
public string key {get,set;}
public int total1 {get;set;}
public int total2 {get ;set;}
}
I would like to "union" them so that when the key on item form set 1 is equal to the key of an item from the set 2 , my union should return an item as follow:
item_union.Key= item1.key==item2.key;
item_union.total1= item1.total1 + item2.total1;
item_union.total2= item1.total2 + item2.total2;
can someone show me how i should build my custom equality compararer to obtain this result?
many thanks in advance
It sounds like you might want a join, or you might just want to concatenate the collections, group by the key and then sum the properties:
// Property names changed to conform with normal naming conventions
var results = collection1.Concat(collection2)
.GroupBy(x => x.key)
.Select(g => new Item {
Key = g.Key,
Total1 = g.Sum(x => x.Total1),
Total2 = g.Sum(x => x.Total2)
});
I'm using LINQ's Union method to combine two or more collections. After that I'm trying to apply sorting to the combined collection by calling OrderBy on a field that is common to the collections. Here is how I am applying sorting:
combinedCollection.OrderBy(row => row["common_field"]);
combinedCollection is defined as:
Enumerable<DataRow> combinedCollection;
I need the sorting to be applied to the entire combined collection. For some reason, that is not happening. Instead I see there is sorting applied on some other field separately within each 'collection' block within the combined collection
And idea why??
First Edit
foreach (....)
{
if (combinedCollection != null)
{
combinedCollection = combinedCollection.Union(aCollection);
}
else
{
combinedCollection = aCollection;
}
}
Second Edit
_Cmd.CommandText = "SELECT Person.Contact.FirstName, Person.Contact.LastName, Person.Address.City, DATEDIFF(YY, HumanResources.Employee.BirthDate, GETDATE()) AS Age"
+ " FROM HumanResources.EmployeeAddress INNER JOIN"
+ " HumanResources.Employee ON HumanResources.EmployeeAddress.EmployeeID = HumanResources.Employee.EmployeeID INNER JOIN"
+ " Person.Address ON HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID INNER JOIN"
+ " Person.Contact ON HumanResources.Employee.ContactID = Person.Contact.ContactID AND HumanResources.Employee.ContactID = Person.Contact.ContactID AND "
+ " HumanResources.Employee.ContactID = Person.Contact.ContactID AND HumanResources.Employee.ContactID = Person.Contact.ContactID";
DataTable employeeTable = new DataTable();
_Adpt.Fill(employeeTable);
DataRow[] allRows = null;
allRows = employeeTable.Select("");
IEnumerable<DataRow> filteredEmployeeRows;
filteredEmployeeRows = from row in allRows select row;
// Declare a variable to hold the city-filtered rows and set it to null for now
IEnumerable<DataRow> cityFilteredEmployeeRows = null;
//Copy filtered rows into a data table
DataTable filteredEmployeeTable = filteredEmployeeRows.CopyToDataTable<DataRow>();
foreach (DataRowView city in CityListBox.SelectedItems)
{
// create an exact copy of the data table
DataTable filteredEmployeeCopyTable = filteredEmployeeTable.Copy();
// Enumerate it
IEnumerable<DataRow> filteredEmployeeRowsInSingleCity = filteredEmployeeCopyTable.AsEnumerable();
// Apply the city filter
filteredEmployeeRowsInSingleCity = _ApplyCityFilter(filteredEmployeeRowsInSingleCity, city["City"].ToString());
if (cityFilteredEmployeeRows != null)
{
// Combine the filtered rows for this city with the overall collection of rows
cityFilteredEmployeeRows = cityFilteredEmployeeRows.Union(filteredEmployeeRowsInSingleCity);
}
else
{
cityFilteredEmployeeRows = filteredEmployeeRowsInSingleCity;
}
}
//apply ordering
cityFilteredEmployeeRows.OrderBy(row => row["Age"]);
//cityFilteredEmployeeRows.OrderByDescending(row => row["Age"]);
EmployeeGridView.DataSource = cityFilteredEmployeeRows.CopyToDataTable<DataRow>();
.......
private IEnumerable<DataRow> _ApplyCityFilter(IEnumerable<DataRow> filteredEmployeeRows, string city)
{
IEnumerable<DataRow> temp = filteredEmployeeRows;
filteredEmployeeRows = from row in temp
where row["City"].ToString() == city
select row;
return filteredEmployeeRows;
}
I think you have a problem with the LINQ lazy evaluation, I would have to investigate to find out wich part causes the problem.
Using the foreach(var item...) in lazy functions has already bitten me (because when executed later they all reference the last iterated item), but in your case it doesn't look like this is the problem.
To check it is the really the issue you can just use a DataRow[] in place of the IEnumerable<DataRow> and call .ToArray() after every LINQ function.
Edit: I'm not sure I got your code right but can't you just use:
var cities = CityListBox.SelectedItems.Cast<DataRowView>()
.Select(city => city["City"].ToString())
.ToArray();
var rows = allRows
.Where(r => cities.Contains((string)r["City"]))
.OrderBy(r => (int?)r["Age"])
.ToArray(); // if you want to evaluate directly, not mandatory
I have a newbie LINQ question. I need to create two objects of same type from a list of strings. I need to append a text 'Direct' & "Indirect' to the string and use them as ID to create the two unique objects.
var vStrings = new List { "Milk", "Eggs", "Cheese" };
var vProducts = (from s in vStrings
select new Product { ID = s + "-Direct" })
.Union(
from s in vStrings
select new Product { ID = s + "-InDirect" });
You can see in the example above, I am using a Union to create two different objects, Is there a better way to rewrite this LINQ query?
Thanks for your suggestions
If you ever needed more suffixes, this might be a better way:
var strings = new List<string> { "Milk", "Eggs", "Cheese" };
var suffixes = new List<string> {"-Direct", "-InDirect"};
var products = strings
.SelectMany(_ => suffixes, (x, y) => new Product() {ID = x + y});
And it would only iterate over the original set of strings once.
This way isn't much shorter but I think it would be a little better such as there is only one Concat instead of many Union:
var vProducts2 = (from s in vStrings
select s + "-Direct").Concat(
from s in vStrings
select s + "-InDirect");
I have a long query that returns
Item {
DateTime entryDate
.....
}
I like to combine the result of this query with another table
Value {
DateTime date,
double value
}
such that if entryDate >= CUTOFF, then take the value on CUTOFF, else take the value on entryDate. In other words, I'd want to achieve:
SELECT Item.*, Value.value WHERE
MIN( Item.entryDate, CUTOFF ) == Value.date
Excuse my syntax, but that's the idea.
EDIT: After some trial and error, I came up with this linq-to-sql query:
from iValue in Values
join iItem in ... (long query)
let targetDate = iItem.EntryDate > CUTOFF ? iItem.EntryDate : CUTOFF
where iValue.Date == targetDate
select new
{
iItem,
targetDate,
iValue
}
Thanks for your help.
yourLongQuery.Where(y => y.Item.entryDate == Value.date || CUTOFF == Value.date)
.Select(x => new {
entrydate = (x.Item.entryDate < CUTOFF ? x.Item.entryDate : CUTOFF),
/*rest of x.Item properties here */ ,
x.Value.date,
x.Value.value
});
Filter the query, Combine the two items into one item and modify the first item
Given that you've returned your data from your Item query and that the Value table is relatively small then this is a nice way to go:
var lookup = values.ToLookup(v => v.date, v => v.value);
var query =
from i in items
let c = i.entryDate < CUTOFF ? i.entryDate : CUTOFF
let v = lookup[c].FirstOrDefault()
select new
{
Item = i,
Value = v,
};
The ToLookup extension is very useful and often overlooked.
such that if entryDate >= CUTOFF, then take the value on CUTOFF, else
take the value on entryDate. In other words, I'd want to achieve:
SELECT Item.*, Value.value WHERE MAX( Item.entryDate, CUTOFF ) ==
Value.date
This is contradictory - if entryDate >= CUTOFF then take the value on CUTOFF imples that you want MIN(Item.entryDate, CUTOFF), not MAX.
Having said that, you just want to select the value.Value that matches each item of your query. Each item should look up the relevant value which matches your MAX (or, I believe, MIN) statement.
query.Select(item =>
{
var matchingValue = Values.Single(v =>
v.date == Min(item.entryDate, CUTOFF));
return new { item, matchingValue.value };
});
This will return an IQueryable of anonymous { Item, double } objects.
If you require this to be a executed as a single SQL statement you'll need to do some refactoring. A good start is to swap matchingValue into a single statement.
query.Select(item => new { item, context.Values.Single(v =>
v.date == Min(item.entryDate, CUTOFF)).value });
I don't have a Visual Studio in front of me to confirm, but I am not sure that the Math.Min function is mapped in LINQ-to-SQL. Let's assume it's not.
query.Select(item => new { item, context.Values.Single(v =>
v.date == (item.entryDate < CUTOFF ? item.entryDate : CUTOFF)).value });
I believe that will resolve to a single query if you execute it with a .ToList() but can't confirm until I have some tools in front of me. Test it with SQL profiler to be sure.
I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)