Linq pairing two Lists of different classes with join - c#

I have the code below
var allA = // holds a List<classA>
var allB = //holds a List<ClassB>
var res = from A in allA
join B in allB on A.Id equals B.Id
select new Tuple<string,string,string,string,string>
(B.val1,B.val2,A.val1,A.val2,A.val3);
var resList = res as List<Tuple<string, string, string, string, string>>;
Now the issue is, with the way im doing it I'd have to remember which item in my tuples hold what value. I don't why resList = res as a List<Tuple<...>> doesn't work either, it doesn't hold any values.
How can I structure this where I have a List<Tuple<ClassA,ClassB>> and in each tuple, ClassA and ClassB are the joined pair in Linq select statement?

Let's consider the following two classes and lists
class A {
public int Id {get;set;}
public string Name {get;set;}
}
class B {
public int Id {get;set;}
public decimal Size {get;set;}
}
(...)
var la = new A[]{ new A { Id = 1, Name = "Snake"}, new A { Id = 2, Name = "Adam"}};
var lb = new B[]{ new B { Id = 1, Size = 0.8m}, new B { Id = 2, Size = 1}};
You can create an object with two properties:
var lab = from a in la
join b in lb on a.Id equals b.Id
select new {a, b}; // or select new { A = a, B = b};
I used anonymous type, but you can create a type the has two properties A and B and use that.
If you wish for a tuple, use a modern tuple with named fields:
select (A: a, B: b);
Having said that, maybe an object with the properties that you need is the best choice.
var lab = from a in la
join b in lb on a.Id equals b.Id
select new
{
Id = a.Id,
Name = a.Name,
Size = b.Size
};

Related

Does C# linq support "anti join" semantics?

I've googled a little while and didn't find a direct anti-join semantics example. How to do this in C# LINQ as an example?
An anti-join as basically a set of data that is not conained in another set, can be represented in Linq with a an IEnumerable.Except like this:
double[] numbers1 = { 2.0, 2.0, 2.1, 2.2, 2.3, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.2 };
IEnumerable<double> onlyInFirstSet = numbers1.Except(numbers2);
foreach (double number in onlyInFirstSet)
Console.WriteLine(number);
This of course requires the definition of an IEqualityComparer for custom classes.
An alternative syntax using where would be:
var antiJoin = numbers1.Where(number => !numbers2.Contains(number));
Read more on Enumerable.Except Method on Microsoft docs.
Edit:
As for "db driven linq" here is an example that will work for Entity Framework using Except:
var filteredProducts = db.Products.ToList()
.Except(db.Orders.Where(o => o.OrderId = 123)
.Select(o => o.Product).ToList())
.ToList();
as for the where alternative:
var filterProducts = db.Orders.Where(o => o.OrderId = 123)
.Select(o => o.Product).ToList();
var antiJoinProducts = db.Products.Where(p => !filterProducts.Contains(p));
Assuming this relates to your previous question -
If you want to include in your query employees for which you couldn't find the department (essentially a left outer join) you can do this:
var result = from e in employees
join d in departments on e.DeptId equals d.DeptId into gj
from subdept in gj.DefaultIfEmpty()
select new { e.EmpName, subdept?.DeptName };
If you want to retrieve only the employees for which you couldn't match a department (that would be your anti join I guess) then you just add a subdept is null contition like so:
var result = from e in employees
join d in departments on e.DeptId equals d.DeptId into gj
from subdept in gj.DefaultIfEmpty()
where subdept is null
select new { e.EmpName, subdept?.DeptName };
For more info on left outer joins in C# Linq you can check this out.
I think there is no direct method to achieve that, but it is easy with couple of extension methods.
Setup:
public class Class1
{
public int Id;
public string Info;
}
public class Class2
{
public int Id;
public string Data;
}
Usage:
List<Class1> l1 = new List<Class1>() { new Class1() { Id = 1, Info = "abc" }, new Class1() { Id = 2, Info = "123" } };
List<Class2> l2 = new List<Class2>() { new Class2() { Id = 2, Data = "dsfg" }, new Class2() { Id = 3, Data = "asdfsaf" } };
l1 = l1.Where(c1 => ! l2.Select(c2 => c2.Id).Contains(c1.Id)).ToList();
Also, if you'd have the same lists of entites/types you could use Except methpod (you would need to define own comparator).

Using LINQ to get information from two tables, with a join

I have a linq statement to populate two labels. The thing is, this information comes from two tables. I Have a join to join the two tables, except i cant get my Terms and Conditions from my Campaign table. Its only picking up the RedemptionLog table columns. Anyone to help with this?
MSCDatabaseDataContext MSCDB = new MSCDatabaseDataContext();
var q = from row in MSCDB.Tbl_RedemptionLogs
join d in MSCDB.Tbl_Campaigns on row.CampaignId equals d.CampaignId
orderby row.VoucherCode descending
select row;
var SeshVoucherDisplay = q.First();
lblCode.Text = SeshVoucherDisplay.VoucherCode;
lblTerms.Text = SeshVoucherDisplay
For the SeshVoucherDisplay variable, it only picks up from the RedemptionLogs table, yet i did a join? Any help?
Try something like this :
var SupJoin = from row in MSCDB.Tbl_RedemptionLogs
join d in MSCDB.Tbl_Campaigns on row.CampaignId equals d.CampaignId
orderby row.VoucherCode descending
select new { Id = row.ID, SupplierName = row.SupplierName,
CustomerName = d.CompanyName };
The column names are just for example purpose. Put your own there. And thereafter, you can apply First on it and use that particular variable.
Hope this helps.
Well, by writing select row you asked LINQ to give back to you only row.
If you want both elements, you need to ask for both of them, e.g. by writing select new { row, d }.
In this example
var foo =
new []
{
new { Id = 1, Name = "a" },
new { Id = 2, Name = "b" },
new { Id = 3, Name = "c" }
};
var bar =
new []
{
new { Id = 1, Name = "d" },
new { Id = 2, Name = "e" },
new { Id = 3, Name = "f" }
};
var baz =
from a in foo
join b in bar on a.Id equals b.Id
select new { a, b };
var qux =
from a in foo
join b in bar on a.Id equals b.Id
select new { a, b };
In baz you'll find only a list of foos, in qux you'll find a list of both foos and their bar.
Try this:
var query = (from row in MSCDB.Tbl_RedemptionLogs
join d in MSCDB.Tbl_Campaigns on row.CampaignId equals d.CampaignId)
orderby row.VoucherCode descending
select new
{
columnname = row.columnname
});
When writing select row you relate to the row you defined in from row in MSCDB.Tbl_RedemptionLogs.
However if you want the data from both tables you have to write something similar to this:
select new {
// the properties of row
Redemption = row.redemption,
// the properties of d
Campaign = d.CampaignID // alternativly use may also use row.CampaignID, but I wanted to show you may acces all the members from d also
}

How to union two LINQ queries but the second query need have more fields

I need make union between two LINQ queries, but the second query need have more fields that the first. How can I do it?
Example:
public static void Dummy()
{
var query1 = this.Db.Table1.Select(s => new MyObject() { A = s.Field1, B = s.Field2 });
var query2 = this.Db.Table2.Select(s => new MyObject() { A = s.Field1, B = s.Field2, C = s.Field3 });
var result = query1.Union(query2);
}
When I calls result.ToList(), occurs the following error:
The type 'MyObject' appears in two structurally incompatible
initializations within a single LINQ to Entities query. A type can be
initialized in two places in the same query, but only if the same
properties are set in both places and those properties are set in the
same order.
How Can I resolve this problem?
Obs.: I can't put the Field3 in the query1 (I don't have access to the query one, because this I Can't changed it)
You don't have to put Field3 in first query but Union requires same number of columns and in same order. Specify a dummy value for third column/field C like:
var query1 = this.Db.Table1.Select(s => new MyObject()
{ A = s.Field1, B = s.Field2 , C= ""});
Assign C whatever is the default value of Field3, may be null for reference type and 0 for numbers etc.
If you don't have access to it modify query1 then create a new query using query1 like:
var newQuery = query1.Select(s=> new MyObject()
{ A = A, B = B , C= ""});
and then use that in Union
var result = newQuery.Union(query2);
As-is, you can't. You can only union 2 sets that have the same structure. If you don't mind modifying query1, however:
var query1 = this.Db.Table1.Select(s => new MyObject()
{ A = s.Field1, B = s.Field2, C = null });
This would allow them to union properly, as they have the same structure.
You can do it, like this:
Create a object devired from MyObject
class MyObjectUnion : MyObject{
}
So, the method goes like this:
public static void Dummy()
{
var query1 = this.Db.Table1.Select(s => new MyObject() { A = s.Field1, B = s.Field2 });
var query1modified = this.Db.Table2.Select(s => new MyObjectUnion() { A = s.Field1, B = s.Field2, C = null });
var query2 = this.Db.Table2.Select(s => new MyObjectUnion() { A = s.Field1, B = s.Field2, C = s.Field3 });
var result = query1modified.Union(query2);
}
It works
Because records in query1 will never have a property "C", and all records in query2 will have a property "C", it is unlikely that a record in query1 will be equivalent to a record in query2. The only reason for using Union over Concat is to remove duplicates and since you can't have any, you should likely be using Concat instead of Union.
public static void Dummy()
{
var query1 = this.Db.Table1.Select(s => new MyObject() { A = s.Field1, B = s.Field2 });
var query2 = this.Db.Table2.Select(s => new MyObject() { A = s.Field1, B = s.Field2, C = s.Field3 });
var result = query1.ToList().Concat(query2);
}
There are exceptions, as if you have a custom IEqualityComparer for MyObject that ignores the "C" property, or the default for the "C" property may exist in a record for table2, and you wanted to remove the duplicate, or if there possibly exists duplicates within either query1 or query2 and you wanted them removed then you can still use Concat, but you need to use Distinct before the Concat.
Editted to force query1 to be materialized before concatenation via .ToList()
Double checked with LinqPad, and the following executable had no issues, using a datasource that had both Categories and Cities tables of which were completely different schemas:
void Main()
{
var query1 = Categories.Select(s => new MyObject { A = s.id, B = s.name });
var query2 = Cities.Select(s => new MyObject { A = s.id, B = s.city_name, C = s.location });
var result = query1.ToList().Concat(query2);
result.Dump();
}
public class MyObject
{
public int A {get;set;}
public string B {get;set;}
public object C {get;set;}
}

Linq to entities hard code list

This is my view model
public class ProcurementDisplayData
{
public string Key { get; set;}
public string Value { get; set;}
}
How can I hard code this list. Basically what will be the syntax inside the query
var query = (from u in context.Jobs
join q in context.Quotations on u.QuotationId equals q.QuotationId
join v in context.Vessels on q.VesselId equals v.VesselId
join c in context.Customers on q.CustomerId equals c.CustomerId
where u.JobNo == JobNo
select new List<ProcurementDisplayData>
{
new { Key = "a" ,Value=u.JobNo},
new { Key = "b" ,Value=u.Vessel}
}).ToList();
return query;
Get JobNo and Vessel from database. Then create lists in memory:
var query = from u in context.Jobs
join q in context.Quotations on u.QuotationId equals q.QuotationId
join c in context.Customers on q.CustomerId equals c.CustomerId
where u.JobNo == JobNo
select new { u.JobNo, u.Vessel };
return query.AsEnumerable() // moves further processing to memory
.Select(x => new List<ProcurementDisplayData> {
new { Key = "a", Value = x.JobNo },
new { Key = "b", Value = x.Vessel }
}).ToList();

linq combine 2 tables into one list

I have 2 tables in db - one is EmploymentRecords one is EmploymentVerificationRecords
I want to query both tables and return one List
I have a Model (simplified example):
Public Class Record
{
int ID {get; set;}
string name {get; set;}
bool IsVerification {get; set;}
}
I want to have some type of query in LINQ like :
var records = from a in _context.EmploymentRecords
join b in _context.EmploymentVerificationRecords on a.id equals b.id
where a.UserID = 1
select new Record() { .ID = a.id, .name = a.name, .IsVerification = false}
// I also want to select a new Record() for each b found
see - I also want a new Record() added to the results for each record found in the second table , for these results IsVerification would be True
You can select everything from DB as it is selected now (but I would rather use join/into to do that) and then flatten results into one big collection using LINQ to Objects.
Following should do the trick:
var records
= (from a in _context.EmploymentRecords
join b in _context.EmploymentVerificationRecords on a.id equals b.id into bc
where a.UserID = 1
select new {
a = new Record() { ID = a.id, name = a.name, IsVerification = false},
b = bc.Select(x => new Record() { ID = x.ID, name = b.name, IsVerification = true })
}).AsEnumerable()
.SelectMany(x => (new [] { x.a }).Concat(x.b));

Categories