Does C# linq support "anti join" semantics? - c#

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).

Related

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;}
}

How to add an order(autoIncrement) property to collection using Linq?

Having:
Initialize an anonymouse collection (I would send it as json)
var myCollection = new[]
{
new
{
Code = 0,
Name = "",
OtherAttribute = ""
}
}.ToList();
myCollection.Clear();
And get the data.
myCollection = (from iPeople in ctx.Person
join iAnotherTable in ctx.OtherTable
on iPeople.Fk equals iAnotherTable.FK
...
order by iPeople.Name ascending
select new
{
Code = iPeople.Code,
Name = iPeople.Name,
OtherAttribute = iAnotherTable.OtherAtribute
}).ToList();
I want to add an Identity column, I need the collection ordered and a counted from 1 to collection.count. Is for binding this counter to a Column in a table (jtable).
var myCollection = new[]
{
new
{
Identity = 0,
Code = 0,
Name = "",
OtherAttribute = ""
}
}.ToList();
myCollection = (from iPeople in ctx.Person
join iAnotherTable in ctx.OtherTable
on iPeople.Fk equals iAnotherTable.FK
...
order by iPeople.Name ascending
select new
{
Identity = Enum.Range(1 to n)//Here I donĀ“t know how to do; in pl/sql would be rownum, but in Linq to SQL how?
Code = iPeople.Code,
Name = iPeople.Name,
OtherAttribute = iAnotherTable.OtherAtribute
}).ToList();
If you are using linq to entities or linq to sql, get your data from the server and ToList() it.
Most likely this answer will not translate to sql but I have not tried it.
List<string> myCollection = new List<string>();
myCollection.Add("hello");
myCollection.Add("world");
var result = myCollection.Select((s, i) => new { Identity = i, Value = s }).ToList();
As Simon suggest in comment, that could would look like below:
int counter = 0; //or 1.
myCollection = (from iPeople in ctx.Person
join iAnotherTable in ctx.OtherTable
on iPeople.Fk equals iAnotherTable.FK
...
order by iPeople.Name ascending
select new
{
Identity = counter++,
Code = iPeople.Code,
Name = iPeople.Name,
OtherAttribute = iAnotherTable.OtherAtribute
}).ToList();
Is there any problem in executing this kind of code?
As Simon stated in his comments, consider the following, albeit contrived, example:
int i = 0;
var collection = Enumerable.Range(0, 10).Select(x => new { Id = ++i });
One solution that helped me to achieve the same goal:
Create a separate Function like this:
private int getMaterialOrder(ref int order)
{
return order++;
}
Then call it in your linq query like:
...
select new MaterialItem() { Order=getMaterialOrder(ref order),
...

Sum(with Coalesce) with Group By in linq (for EF6)

I have the following SQL that I would like to write as a single linq statement:
SELECT
P.PartyId,
P.PartyDate,
SUM(COALESCE(R.PaidAmount, 0)) AS AmountPaid
FROM
Party AS P
LEFT JOIN Reservation as R
ON P.PartyID = R.PartyID
GROUP BY P.PartyID, P.PartyDate
ORDER BY P.PartyDate DESC
The best I can do is use two sets of linq queries, like so:
var localList = from partyList in localDb.Parties
join reservationList in localDb.Reservations on
partyList.PartyID equals reservationList.PartyID into comboList
from newList in comboList.DefaultIfEmpty()
select new PartyAmounts {
PartyID = partyList.PartyID,
PartyDate = partyList.PartyDate,
AmountPaid = (newList.PaidAmount ?? 0) };
var secondList = from groupList in localList
group groupList by new {
groupList.PartyID,
groupList.PartyDate} into resList
select new PartyAmounts {
PartyID = resList.Key.PartyID,
PartyDate=resList.Key.PartyDate,
AmountPaid = resList.Sum(x => x.AmountPaid)};
I don't care if it's a method chain or a lambda but I would love to know how this is supposed to go together. I can only barely understand the two I've got now.
Thanks for the help.
var list = from partyList in localDb.Parties
join reservationList in localDb.Reservations on partyList.PartyID equals reservationList.PartyID into comboList
from details in comboList.DefaultIfEmpty() // Left join
group details by new {partyList.PartyID, partyList.PartyDate} into grouped // So that the group have both keys and all items in details
select new PartyAmounts
{
PartyID = grouped.Key.PartyID,
PartyDate = grouped.Key.PartyDate,
AmountPaid = grouped.Sum(x => x.AmountPaid ?? 0)}
};

linq to sql join on multiple columns using lambda

Can someone help me to translate this
var query = from s in context.ShoppingMalls
join h in context.Houses
on
new { s.CouncilCode, s.PostCode }
equals
new { h.CouncilCode, h.PostCode }
select s;
into lambda query?
Thanks.
var query = context.ShoppingMalls
.Join(
context.Houses,
s => new { s.CouncilCode, s.PostCode },
h => new { h.CouncilCode, h.PostCode },
(s, h) => s);
Although the example and answer given by #Thomas Levesque works for columns that match, I wanted to also supply the answer if you have columns to join on but they have different names. This is what I needed for my googling and this question got me close.
The difference of course is the explicit declaration of the columns as a variable to identify on.
var query = context.MapKitsToResources
.Join(
context.Resources,
o => new { Id = o.ResourceId, Type = o.ResourceTypeId},
i => new { Id = i.Id, Type = TypeId},
(o, i) = new { rType : i };

Categories