Linq query between two list objects - c#

I have two objects:
ObjectA
{
string code;
string country;
}
ObjectB
{
string code;
string otherstuff;
}
And I have List<objectA> and List<ObjectB> and I need to find all objects in List<ObjectB> which contains objectA.Code.
But cannot manage to implement it on LINQ query.

It sounds like you are trying to find all instances of ObjectB which have a code value present in any of the List<ObjectA> values. If so try the following
List<ObjectA> listA = ...;
List<ObjectB> listB = ...;
var all = listB.Where(b => listA.Any(a => a.code == b.code));

It sounds like you want to join the list of ObjectA with the list of ObjectB on the code property. This is one way:
List<ObjectA> listOfA = ...;
List<ObjectB> listOfB = ...;
var all = from a in listOfA
join b in listOfB on a.code equals b.code
select new {a,b};
The result is a list of anonymous objects, containing 2 properties: a of type ObjectA, b of type ObjectB, with the same code

To do this effectively you can first put the codes into a HashSet<string> and then use a Contains() query to check if the B in question has a code that is contained in the hashset:
var codes = new HashSet<string>(listOfAs.Select(x => x.code));
var selectedBs = listOfBs.Where( x=> codes.Contains(x.code));

I would put the codes of the ObjectA list into a HashSet, otherwise your query would become an O(n2) operation. Like this it is an O(n) operation:
var aList = new List<ObjectA>();
var bList = new List<ObjectB>();
var aCodes = new HashSet<string>(aList.Select(a => a.code));
var result = bList.Where(b => aCodes.Contains(b.code));

Related

Join two lists of different objects and create a new list

I've got two lists of different objects.
List<ObjA> objAs = new List<ObjA>();
List<ObjB> objBs = new List<ObjB>();
They have the following class structures.
public class ObjA
{
public int Id;
public int ObjBId;
}
public class ObjB
{
public int Id;
public string Title;
}
Joining objA's ObjBId property to ObjB's Id property, I want to create a list of ObjA's Ids alongside ObjB's Titles. Something like this:
List<int, string> output = new List<int, string>();
// where int = ObjA's Id, string = ObjB's Title
How can I do this in LINQ? Are there any alternative than using Concat and creating a wrapper class?
You can use Join method and return a result as list of named tuples List<(int, string)> (available beginning with C# 7), becuase List<int, string> isn't a valid C# declaration.
var output = objAs.Join(objBs, a => a.ObjBId, b => b.Id, (a, b) => (a.Id, b.Title)).ToList();
You may also use anonymous objects instead of tuples, e.g. (a, b) => new { a.Id, b.Title}
Enumerable.Join should help you in this.
var result = objAs.Join(objBs,x=>x.ObjBId,y=>y.Id,(x,y)=>new {x.Id,y.Title})
.ToList();
You can use a join and return a list
var result = (from a in objAs
join b in objBs on a.ObjBId equals b.Id
select new
{
a.ObjBId,
b.Title
}).ToList();
So for every element of objAs, you want to take the Id, and if an object with the same Id is in objBs, you want the Id from objA and the title from objB.
In fact, since the Id of objA and objB are equal, you don't care if you take the Id from objA or from objB.
You didn't write what you want if there is no item in objBs with the same Id.
Let's assume you want null in that case.
var result = objAs.GroupJoin(objBs, // GroupJoin A with B
objA => objA.Id, // from every element in A take the Id
objB => objB.Id, // from every element in B take the Id
// ResultSelector: take all elements of A, each with the matching elements from B
(objA, matchingObjBs) => new
{
Id = objA.Id,
Title = matchingObjBs.Select(objB => objB.Title).FirstOrDefault(),
});
The nice thing about GroupJoin, is that you also get the element from A that have no matching B. And if there are more than one matching item in B, you take the first one.
If you don't want the items from A that have no matching Id in B, it is enough to take only the elements from B that have an Id in A:
var idsA = objAs.Select(objA => objA.Id);
var result = objBs.Where(objB => idsA.Contains(objB.Id));

Store and access a list inside of a list

I need to merge 2 list into one list based on a field in the two list and then access the information in the merged list.
Example pseudo code
List1.merge;
List2.merge2;
MergedList = List1.merge join List2.merge2;
MergedList.Merge2 or MergedList.Any Field in List
What about Concat?
var myList = list1
.Where(l => list2
.Any(l2 => l2.Field == l.Field)
.Concat(list2
.Where(l2 => list
.Any(l => l.Field == l2.Field))
.ToList();
how about a simple lambda join?
list1.Join(list2, l1 => l1.Field, l2 => l2.field, (l1,l2) => new SomeORAnonymousObjectType { ListItem1 = l1, ListItemType2 = l2 }).ToArray().ToList().ToEnumerable();
Here is a basic example that you should be able to adjust for you needs:
using System.Linq; // Add these two lines to the using statements
// at the top of your file
using System.Collections.Generic;
var l1 = new List<Uri>() { new Uri("http://www.facebook.com"), new Uri("http://www.yahoo.com"), new Uri("http://www.google.com") };
var l2 = new List<Uri>() { new Uri("http://www.google.com"), new Uri("http://www.nytimes.com"), new Uri("http://www.facebook.com") };
var q = from Uri uri1 in l1
join Uri uri2 in l2 on uri1.Host equals uri2.Host select uri2;
var commonUris = q.ToList(); // Result
The critical line is the one around equals
Be aware that this might not produce the expected result if any item occurs more than once in any one of the lists.
This problem is now solved. I created a list that referenced a class, that stored my information, and once I did this I was able to manage and access everything I needed.

Entity Framework - Join to a List

I need to retrieve a list of entities from my database that matches a list of items in a plain list (not EF). Is this possible with Entity Framework 4.1?
Example:
var list = new List<string> { "abc", "def", "ghi" };
var items = from i in context.Items
where list.Contains(i.Name)
select i;
This works great to return rows that match one property, but I actually have a more complex property:
var list = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
// i need to write a query something like this:
var items = from i in context.Items
where list.Contains(new Tuple<string,string>(i.Name, i.Type))
select i;
I know that is not valid because it will say it needs to be a primitive type, but is there any way to do what I'm trying to accomplish or will I need to resort to a stored procedure?
You have a few options:
1) You could, of course, write a stored procedure to do what you need and call it.
2) You could read the table into memory and then query the in memory list...that way you don't have to use primitives:
var items = from i in context.Items.ToList()
where list.Contains(new Tuple<string, string>(i.Name, i.Type))
select i;
3) You could also convert your query to use primitives to achieve the same goal:
var items = from i in context.Items
join l in list
on new { i.Name, i.Type } equals
new { Name = l.Item1, Type = l.Item2 }
select i;
I would go with the second option as long as the table isn't extremely large. Otherwise, go with the first.
You need to break it down to sub-properties. For example, something like (this might not compile, i'm not able to test at the moment, but it should give you something to work with):
var items = from i in context.Items
where list.Select(x => x.Item1).Contains(i.Name)
&& list.Select(x => x.Item2).Contains(i.Type)
select i;
You have to think about what the resulting SQL would look like, this would be difficult to do directly in SQL.
My suggestion would be you split out one field of the tuples and use this to cut down the results list, get back the query result then filter again to match one of the tuples e.g.
var list = new List<string> { "abc", "def" };
var list2 = new List<Tuple<string, string>>
{
new Tuple<string,string>("abc", "123"),
new Tuple<string,string>("def", "456")
};
var items = (from i in context.Items
where list.Contains(i.Name)
select i)
.AsEnumerable()
.Where(i => list2.Any(j => j.val1 == i.Name && j.val2 == i.Type);

Question About Querying Linq Results

When you query existing linq results, it's like they're stuck a layer deeper than the original result. Let me explain what I mean by this.
In the example below, after getting ResultSorted, to get to the data therein, you have to use RowSorted.All.TableData.Field, but in the unsorted Result, you could just do Row.TableData.Field. In the sorted data, you have to use .All to get to the rest of the data, which is like an extra layer to get to the data you're looking for.
How can I get it so I can query Result without getting this extra layer? Thanks Stack-O!
var Result =
from a in Db.Table
select new {TableData = a};
var ResultSorted =
from a in Result
orderby a.TableData.Field
select new {All = a};
foreach(var RowSorted in ResultSorted)
{
MessageBox.Show(RowSorted.All.TableData.ToString());
}
You can use
var Result =
from a in Db.Table
select a;
var ResultSorted =
from a in Result
orderby a.Field
select a;
foreach(var RowSorted in ResultSorted)
{
MessageBox.Show(RowSorted.ToString());
}
Edit:
The thing is that
select new {TableData = a};
creates a new anonymous type with a field called TableData, like this
class Tmp1
{
TableType TableData {get; set;}
}
and
select new {All = a};
creates a new anonymous type with a field called TableData, like this
class Tmp2
{
Tmp1 All {get; set;}
}
Edit 2:
If you select a directly you don't create the extra anonymous type, instead you return the TableType.
You are returning a new instance of an anonymous type in each of your LINQ queries:
select new {TableData = a};
select new {All = a};
What you are saying to the compiler is (in the first LINQ query), "Give me a new instance of an anoymous type. I want this anonymous type to have one property named TableData and I want the value for that property to be a."
If you simply return a instead of an anoymous type, you shouldn't need to go through the properties of the nested types to get the data. Try this:
var Result =
from a in Db.Table
select a;
var ResultSorted =
from a in Result
orderby a.TableData.Field
select a;
foreach(var RowSorted in ResultSorted)
{
MessageBox.Show(RowSorted.ToString());
}
var ResultSorted =
from a in Db.Table
orderby a.Field
select a.ToString();
Edit: Fixed, didn't see the first query. This should be identical now. There is no need to create anonymous objects all the time.

LINQ In Line Property Update During Join

I have two obects, A & B for this discussion. I can join these objects (tables) via a common relationship or foreign key. I am using linq to do this join and I only want to return ObjectA in my result set; however, I would like to update a property of ObejctA with data from ObjectB during the join so that the ObjectAs I get out of my LINQ query are "slightly" different from their original state in the storage medium of choice.
Here is my query, you can see that I would just like to be able to do something like objectA.SomeProperty = objectB.AValueIWantBadly
I know I could do a new in my select and spin up new OBjectAs, but I would like to avoid that if possible and simply update a field.
return from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId
// update object A with object B data before selecting it
select objectA;
Add an update method to your ClassA
class ClassA {
public ClassA UpdateWithB(ClassB objectB) {
// Do the update
return this;
}
}
then use
return from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId
// update object A with object B data before selecting it
select objectA.UpdateWithB(objectB);
EDIT:
Or use a local lambda function like:
Func<ClassA, ClassB, ClassA> f = ((a,b)=> { a.DoSomethingWithB(b); return a;});
return from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId
select f(objectA , objectA );
From the word "tables", it sounds like you are getting this data from a database. In which case; no: you can't do this. The closest you can do would to select the objects and the extra columns, and update the properties afterwards:
var qry = from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId
select new { A = objectA,
objectB.SomeProp, objectB.SomeOtherProp };
foreach(var item in qry) {
item.A.SomeProp = item.SomeProp;
item.A.SomeOtherProp = item.SomeOtherProp;
// perhaps "yield return item.A;" here
}
If you were doing LINQ-to-Objects, there are perhaps some hacky ways you could do it with fluent APIs - not pretty, though. (edit - like this other reply)
I am doing a left join here so I still have all the data from objectA even if the corresponding property in objectB is null. So if the corresponding property in objectB is null then you have to define what to do in objectA. I use this statement all the time for joining two sets of data. You do not need to exhaustively list all properties in objectA and how they map, you only need to list the values you want to update with objectB. Pre-existing values in objectA are safe unless a mapping to objectB is defined.
return from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId into combinedObj
from subObject in combinedObj.DefaultIfEmpty()
// update object A with object B data before selecting it
select ((Func<objectAType>)(() =>
{
objectA.property = ((subObject == null) ? "Object B was null" : subObject.property);
objectA.property = ((subObject == null) ? "Object B was null" : subObject.property);
return objectA;
}))()
First extend Linq to have an Each option by creating a class called LinqExtensions.
public static class LinqExtensions
{
public static void Each<T>(this IEnumerable<T> source, Action<T> method)
{
foreach (var item in source)
{
method(item);
}
}
}
Then you can use Join to return a list of new objects that contain the original objects with it's appropriate value. The Each will iterate over them allowing you to either assign or pass the values as parameters to each object.
Assignment example:
objectA.Join(objectB,a=>a.Id,b=>b.Id,(a,b) => new {a,b.AValueIWant}).Each(o=>o.a.SomeProperty=o.AValueIWant);
Parameter passing example:
objectA.Join(objectB,a=>a.Id,b=>b.Id,(a,b) => new {a,b.AValueIWant}).Each(o=>o.a.SomeMethod(o.AValueIWant));
The nice thing about this is that ObjectA and ObjectB do not have to be the same type. I have done this with a list of objects joined to a Dictionary (like a lookup). Bad thing is it isn't clear what is going on. You would be better to skip the Each extention and write it like this.
foreach(var change in objectA.Join(objectB,a=>a.Id,b=>b.Id,(a,b) => new {a,b.AValueIWant}))
{
change.a.SomeProperty = change.AValueIWant;
change.a.SomeMethod(change.AValueIWant);
}
But for more clarity I would probably do this:
foreach(var update in objectA.Join(objectB,objectA=>objectA.Id,objectB=>objectB.Id,(objectA,objectB) => new {objectA, Value = objectB.AValueIWant}))
{
update.objectA.SomeProperty = update.Value;
}
You will need to return the whole ObjectA in your new object, because it will be readonly and the only reason this works is because the objects in a collection are referenced allowing you to make your changes to properties on the objects.
But in the end it would be clearest to skip the LINQ join all together and just loop through the collections and look for matches, this will help with future maintenence. LINQ is awesome but just like when you have a hammer it doesn't make everything a nail, when you have a collection it doesn't mean LINQ is the answer.
can u try the let statement? (not at my dev machine to test this out myself):
return from objectA in GetObjectAs()
join objectB in GetObjectBs()
on objectA.Id equals objectB.AId
let objectA.SomeProperty = objectB.AValueIWantBadly
select objectA;
you can try by following..
var list1 = new List<ItemOne>
{
new ItemOne {IDItem = 1, OneProperty = "1"},
new ItemOne {IDItem = 2, OneProperty = null},
new ItemOne {IDItem = 3, OneProperty = "3"},
new ItemOne {IDItem = 4, OneProperty = "4"}
};
var list2 = new List<ItemTwo>
{
new ItemTwo {IDItem = 2, TwoProperty = "2"},
new ItemTwo {IDItem = 3, TwoProperty = "3"},
};
var query = list1.Join(list2, l1 => l1.IDItem, l2 => l2.IDItem, (l1, l2) =>
{
l1.OneProperty = l2.TwoProperty;
return l1;
});

Categories

Resources