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;
});
Related
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));
In my Controller Constructor I initialise my IEnumerable list which I will be passing it as model to my Views.
IEnumerable<classB> mdata;
public MyController()
{
DataContext context = new DataContext(//connectionstring code missing//);
mdata = context.GetTable<classB>().ToArray();
}
In my Action now I want to "add" a column to the mdata from a var list I retrieve from a Session as below:
var list = (List<Tuple<int,int>>)Session["ids"];
The list above contains id & Level both type int. The "id" will be used to match the rows in mdata which also have column "id". So once it finds a match it should add "Level" to the IEnumerable list. So far I have:
mdata = mdata.Where(x => list[].Item1.Contains(x.Id)); //not working
You cannot change your ClassB definition (add/remove properties at runtime) - Level property should exist there. But you can join both sequences by id and then select new anonymous object which will have level and joined ClassB instance:
var query = from b in mdata
join t in list on b.Id equals t.Item1
select new {
B = b,
Level = t.Item2
};
Updating Level property in ClassB:
foreach(var pair in query)
pair.B.Level = pair.Level;
Another option (you will avoid anonymous objects creation):
var levels = list.ToDictionary(t => t.Item1, t => t.Item2);
foreach(var b in mdata)
{
int level;
if (levels.TryGetValue(b.Id, out level))
b.Level = level;
}
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));
When writing LINQ queries in C#, I know I can perform a join using the join keyword. But what does the following do?
from c in Companies
from e in c.Employees
select e;
A LINQ book I have say it's a type of join, but not a proper join (which uses the join keyword). So exactly what type of join is it then?
Multiple "from" statements are considered compound linq statments. They are like nested foreach statements. The msdn page does list a great example here
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };
this statement could be rewritten as:
SomeDupCollection<string, decimal> nameScore = new SomeDupCollection<string, float>();
foreach(Student curStudent in students)
{
foreach(Score curScore in curStudent.scores)
{
if (curScore > 90)
{
nameScore.Add(curStudent.LastName, curScore);
}
}
}
This will get translated into a SelectMany() call. It is essentially a cross-join.
Jon Skeet talks about it on his blog, as part of the Edulinq series. (Scroll down to Secondary "from" clauses.)
The code that you listed:
from c in company
from e in c.Employees
select e;
... will produce a list of every employee for every company in the company variable. If an employee works for two companies, they will be included in the list twice.
The only "join" that might occur here is when you say c.Employees. In an SQL-backed provider, this would translate to an inner join from the Company table to the Employee table.
However, the double-from construct is often used to perform "joins" manually, like so:
from c in companies
from e in employees
where c.CompanyId == e.CompanyId
select e;
This would have a similar effect as the code you posted, with potential subtle differences depending on what the employees variable contains. This would also be equivalent to the following join:
from c in companies
join e in employees
on c.CompanyId equals e.CompanyId
select e;
If you wanted a Cartesian product, however, you could just remove the where clause. (To make it worth anything, you'd probably want to change the select slightly, too, though.)
from c in companies
from e in employees
select new {c, e};
This last query would give you every possible combination of company and employee.
All the first set of objects will be joined with all the second set of objects. For example, the following test will pass...
[TestMethod()]
public void TestJoin()
{
var list1 = new List<Object1>();
var list2 = new List<Object2>();
list1.Add(new Object1 { Prop1 = 1, Prop2 = "2" });
list1.Add(new Object1 { Prop1 = 4, Prop2 = "2av" });
list1.Add(new Object1 { Prop1 = 5, Prop2 = "2gks" });
list2.Add(new Object2 { Prop1 = 3, Prop2 = "wq" });
list2.Add(new Object2 { Prop1 = 9, Prop2 = "sdf" });
var list = (from l1 in list1
from l2 in list2
select l1).ToList();
Assert.AreEqual(6, list.Count);
}
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.