I have a list containing models where each model has a number.
Now I'd like to search another list containg other models where each model could potentially have the number from the first list.
This is how my models look:
public class Task
{
int AddressId; //foreign key
}
public class Address
{
int Id; //primary key
}
Now I have a list of addresses and a list of tasks.
I'd like to filter my list of tasks to only those tasks where the AddressId is inside my addresses list.
How would my linq look like?
result = tasks.Where(t => t.AddressId == ???)
Take a look at Join
from m in model1s
join n in model2s on m.Id equals n.Fk
select n
To get them into arrays, if you don't know:
var model1s = new[] { model1A, model1B, model1C };
var model2s = new[] { model2A, model2B, model2C };
Use GroupJoin to avoid duplicates:
result = from l2 in list2
join l1 in list1 on l2.FK equals l1.Id into g
select l2;
List<Task> taskList = new List<Task>{new Task(2), new Task(3), new Task(5)};
List<Address> addrList = new List<Address>{new Address(1), new Address(3), new Address(2)};
var result = taskList.Where(x => addrList.Select(y => y.Id).Contains(x.AddressId));
Just one more alternative I often like to use:
I u could also use a HashSet<T> instead of List<T> then you could use the IntersectWith Method or Intersect Method which makes a more smooth code:-)
so you would have
var result = items1.Intersect(items2)
Notice the AddressId must be public (however it is not recommended to have public fields so you may change it to a property) so you can do this:
var yourList = addressList.Select(i => i.Id).Distinct();
taskList.Where(i => yourList.Contains(i.AddressId));
Related
I have a list which I get from a database. The structure looks like (which I'm representing with JSON as it's easier for me to visualise)
{id:1
value:"a"
},
{id:1
value:"b"
},
{id:1
value:"c"
},
{id:2
value:"t"
}
As you can see, I have 2 unique ID's, ID 1 and 2. I want to group by the ID. The end result I'd like is
{id:1,
values:["a","b","c"],
},
{id:2,
values["g"]
}
Is this possible with Linq? At the moment, I have a massive complex foreach, which first sorts the list (by ID) and then detects if it's already been added etc but this monstrous loop made me realise I'm doing wrong and honestly, it's too embarrassing to share.
You can group by the item Id and have the resulting type be a Dictionary<int, List<string>>
var result = myList.GroupBy(item => item.Id)
.ToDictionary(item => item.Key,
item => item.Select(i => i.Value).ToList());
You can either use GroupBy method on IEnumerable to create IGrouping object that contains a key and grouped objects or you can use ToLookupto create exactly what you want in result:
yourList.ToLookup(m => m.id, m => m.value);
This creates a hashed collection of keys with their values.
For more information please see below post:
https://www.c-sharpcorner.com/UploadFile/d3e4b1/practical-usage-of-using-tolookup-method-in-linq-C-Sharp/
Just a little more detail to emphasize the difference between the ToLookup approach and the GroupBy approach:
// class definition
public class Item
{
public long Id { get; set; }
public string Value { get; set; }
}
// create your list
var items = new List<Item>
{
new Item{Id = 0, Value = "value0a"},
new Item{Id = 0, Value = "value0b"},
new Item{Id = 1, Value = "value1"}
};
// this approach results in a List<string> (a collection of the values)
var lookup = items.ToLookup(i => i.Id, i => i.Value);
var groupOfValues = lookup[0].ToList();
// this approach results in a List<Item> (a collection of the objects)
var itemsGroupedById = items.GroupBy(i => i.Id).ToList();
var groupOfItems = itemsGroupedById[0].ToList();
So, if you want to work with values only after grouping, then you could take the first approach; if you want to work with objects after grouping, you could take the second approach. And, these are just a couple example implementations, there are plenty of ways to accomplish your goal.
First convert to a Lookup then select into a list, like so:
var groups = list
.ToLookup
(
item => item.ID,
item => item.Value
)
.Select
(
item => new
{
ID = item.Key,
Values = item.ToList()
}
)
.ToList();
The resulting JSON looks like this:
[{"ID":1,"Values":["a","b","c"]},{"ID":2,"Values":["t"]}]
Link to working example on DotNetFiddle.
Codewise, what it the cleanest way to do this using linq? Below, I have a crude example where I want to find a matching class instance based on name.
class item
{
string name {get;set;}
int identifier {get;set;}
}
void DoSomething()
{
List<item> List1 = GetSampleItems();
List<item> List2 = GetOtherSampleItems();
for(int a=0;a<List1.count;a++)
{
for(int b=0;b<List2.count;b++)
{
if(List1[a].identifier == List2[b].identifier)
{
List1[a].name = List2[b].name;
}
}
}
}
Linq is for querying, not updating, so you'll still need to loop through the results to make the changes, but you can join to match up the two lists like so:
var query = from l1 in List1
join l2 in List2
on l1.identifier equals l2.identifier
select new {l1, l2};
Now loop through the query to update the l1 items:
foreach(var item in query)
item.l1.name = item.l2.name;
As a side note, there's nothing wrong with the way you're doing it (other than you could break out of the inner loop if a match is found). If you understand how it works and the performance is acceptable, there's no compelling reason to change it.
This should work:
var query = from l1 in List1
join l2 in List2 on l1.identifier equals l2.identifier
select new
{
l1values = l1,
l2Name = l2.name
};
foreach(var item in query)
item.l1Values.name = item.l2Name;
A better way is using a Dictionary<TK,TV>:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
List1.ForEach(x => {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
});
Or as #Rawling says, use a foreach loop instead:
Dictionary<int,item> l2dic = List2.ToDictionary(x => x.identifier);
item itm;
foreach(item x in List1) {
if(l2dic.TryGetValue(x.identifier,out itm)) {
x.name = itm.name;
}
}
Ideone demo (with slight modifications to your item class).
This runs on average in linear time whereas your approach runs in quadratic time.
The assumption is however that the identifiers are unique: no two elements in the same list can have the same identifier.
A concluding note is that variables in general start with a lowercase character so list1 and list2 whereas classes and properties start with a capital one (this Item, Identifier and Name).
I have an objectA with an ID property and a Status property.
I have a ListA which is a collection of objectA.
I also have an objectB with an ID property and a Status property.
I have a ListB which is a collection of objectB.
I need to match listA collection with listB collection based on ID and if there is a match update the Status from ListB to ListA.
What is the best way to do this without foreach loop?
Thanks for the help.
You can use LINQ for this:
var objectsForUpdate = (from a in listA
join b in listB
on a.Id equals b.Id
select new { a, b });
foreach (var obj in objectsForUpdate)
{
obj.a.Status = obj.b.Status;
}
Note this cannot be achieved without using the foreach statement.
I had a similar scenario. I had cartProducts and productsPurchasePrices.
I used linq to join the two collections:
//join currentCart...
var p = currentCart.Rows.Join(
productsPurchasePrices, //... with productsPurchasePrices
row => row.ProductCode, //first collection key
purchasePrice => purchasePrice.Key, //second collection key
(row, purchasePrice) => new
{
productCode = row.ProductCode,
productMargin = purchasePrice.ProductMargin
});
References:
http://msdn.microsoft.com/en-us/library/bb397941.aspx
http://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9
I have an Entity Class called Session and it containts two attributes: LecturerOne and LectureTwo. I want to create a union of all the distinct names in LecturerOne and LecturerTwo:
I got just LecturerOne working.
public List<string> ListLecturer()
{
var lecturerNames = (from s in db.Sessions
select s.LecturerOne).Distinct();
List<string> lecturerList = lecturerNames.ToList();
return lecturerList;
}
One option:
var list = db.Sessions.SelectMany(s => new string[] { s.LecturerOne,
s.LecturerTwo })
.Distinct()
.ToList();
I don't know offhand how EF will treat that, but it's worth a try...
Alternatively, similar to Jamiec's answer but IMO simpler:
var list = db.Sessions.Select(s => s.LecturerOne)
.Union(db.Sessions.Select(s => s.LecturerTwo))
.ToList();
(Union already returns distinct results, so there's no need to do it explicitly.)
var lecturerOnes = (from s in db.Sessions
select s.LecturerOne);
var lecturerTwos = (from s in db.Sessions
select s.LecturerTwo);
List<string> lecturerList = lecturerOnes.Union(lectureTwos).ToList();
I have 2 lists. 1 is a collection of products. And the other is a collection of products in a shop.
I need to be able to return all shopProducts if the names match any Names in the products.
I have this but it doesn't seem to work. Any ideas?
var products = shopProducts.Where(p => p.Name.Any(listOfProducts.
Select(l => l.Name).ToList())).ToList();
I need to say give me all the shopproducts where name exists in the other list.
var products = shopProducts.Where(p => listOfProducts.Any(l => p.Name == l.Name))
.ToList();
For LINQ-to-Objects, if listOfProducts contains many items then you might get better performance if you create a HashSet<T> containing all the required names and then use that in your query. HashSet<T> has O(1) lookup performance compared to O(n) for an arbitrary IEnumerable<T>.
var names = new HashSet<string>(listOfProducts.Select(p => p.Name));
var products = shopProducts.Where(p => names.Contains(p.Name))
.ToList();
For LINQ-to-SQL, I would expect (hope?) that the provider could optimise the generated SQL automatically without needing any manual tweaking of the query.
You could use a join, for example:
var q = from sp in shopProducts
join p in listOfProducts on sp.Name equals p.Name
select sp;
A fuller guide on join is here.
You could create an IEqualityComparer<T> that says products with equal names are equal.
class ProductNameEqulity : IEqualityComparer<Product>
{
public bool Equals(Product p1, Product p2)
{
return p1.Name == p2.Name
}
public int GetHashCode(Product product)
{
return product.Name.GetHashCode();
}
}
Then you can use this in the Intersect extension method.
var products = shopProducts.Intersect(listOfProducts, new ProductNameEquality());
Try this please
var products = shopProducts.Where(m=> listOfProducts.Select(l=>l.Name).ToList().Contains(m=>m.Name));
var products = shopProducts
.Where(shopProduct =>
listOfProducts.Any(p => shopProduct.Name == p.Name))
.ToList();