I have the following two data tables (DataTable) that represents PC allocations to students for different timeslots:
Table1:
Timeslot PC1 PC2 PC3
1 A B
2 A B
3 D
4 D
Table2:
Timeslot PC1 PC2 PC3
1 C
2 C
3
4
Would it be possible to join these two DataTables together into one DataTable, as follows?
Wanted:
Timeslot PC1 PC2 PC3
1 A C B
2 A C B
3 D
4 D
I know that, in SQL, it would be quite straight forward left join, but I have no clue how I can do this in C# with DataTables.
A bit shorter and more efficient if the result is merged in one of the source tables:
Table1.Rows.Cast<DataRow>().Join(Table2.Rows.Cast<DataRow>(), a => a[0], b => b[0],
(a, b) => {a[1] += "" + b[1]; a[2] += "" + b[2]; a[3] += "" + b[3]; return a; }).Count();
Here it is using LINQ:
var joinData = t1.Select().Join(t2.Select(), j1 => j1["TimeSlot"], j2 => j2["TimeSlot"],
(j1, j2) => new { j1, j2 }).Select(j => new
{
TimeSlot = j.j1["TimeSlot"],
PC1 = (string)j.j1["PC1"] != "" ? j.j1["PC1"] : j.j2["PC1"],
PC2 = (string)j.j1["PC2"] != "" ? j.j1["PC2"] : j.j2["PC2"],
PC3 = (string)j.j1["PC3"] != "" ? j.j1["PC3"] : j.j2["PC3"]
});
var resultTable = new DataTable();
addColumns(resultTable);
foreach(var row in joinData)
{
var dr = resultTable.NewRow().ItemArray = new object[] { row.TimeSlot, row.PC1, row.PC2, row.PC3 };
resultTable.Rows.Add(dr);
WriteLine($"{dr[0]} {dr[1]} {dr[2]} {dr[3]}");
}
Related
How can i write a query in linq c#(EF Core 6) for total price and also map other fields of DTO along with total price.
sql Query:
SELECT (sum(c.ExtraPrice) + (a.PricePerSqM*10)) as TotalPrice FROM dbo.Cities a
JOIN dbo.CityExtras b ON a.CityId = b.CityId
JOIN dbo.Extras c ON b.ExtrasId = c.ExtrasId
where a.CityId = 1
group by PricePerSqM
Try the following query:
var query =
from a in ctx.Cities
from b in a.CityExtras
where a.CityId == 1
group new { a, b } by new { a.PricePerSqM } into g
select new
{
g.Key.PricePerSqM,
TotalPrice = g.Sum(x => x.b.ExtraPrice) + g.Key.PricePerSqM * 10
};
I'm working with EntityFramework but can use other ways if need be
Here's the case: I have an SQL Server Database with a scheme similar to:
A B C AhasB AhasC
________ ________ ________ __________ ___________
AId BId CId AId AId
... Btxt Ctxt BId CId
BParent ...
...
Where ... means other columns not important to the problem.
Tables C and AhasC are there to keep data during a lengthy process and are cleared on process completion, so i always start with both empty
Now, the process get's a lot of data (1000+ records) from online sources and stores it in C. After C is filled, I want to fill table AhasC based on the following:
INSERT INTO C (AId, CId) VALUES (
SELECT A.AId, C.CId
FROM A, B, C, AhasB
WHERE A.AId = AhasB.AId AND B.BId = AhasB.BId AND
C.CTxt IN (
SELECT D.BTxt
FROM B AS D
WHERE D.BId = B.BId OR ??
)
)
Before i explain what i need in ?? let me run through what i have here:
I want to insert into table AhasC the pair A.AId, C.CId, so that in all pairs, C.CTxt is the same as a B.Btxt that is connected to A in AhasB.
Moreover (and here enters the ??) i also want it to match the B.Btxt of any parent of B.
Example:
A B
_______ ____________________________________
AId = 1 BId = 1, BTxt = 'a', BParent = Null
AId = 2 BId = 2, BTxt = 'b', BParent = 1
AId = 3 Bid = 3, BTxt = 'c', BParent = 2
AId = 4 BId = 4, BTxt = 'x', BParent = Null
C AahsB
_____________________ _________
CId = 1, Ctxt = 'b' AId = 1, BId = 3
CId = 2, CTxt = 'z' AId = 3, BId = 4
This should result in:
AhasC
____________
AId = 1, CId = 1
So again, AhasC must connect A and C if A is connected to a B that either has BTxt equal to CTxt, or who's parent (or grand-parent and so on) has a BTxt that is the same as CTxt.
Hope i didn't overcomplicate my explaining here :p
EDIT1: as per #dotctor's coments, here's an image of my real shema (not that i think it will add much to the question)
A = Contatos
B = Termos
C = ConcursosPublicos
AhasB = TermosContatos
AhasC = ConcursosContatos
A.AId = Contatos.Id
B.BId = Termos.Id
C.CId = ConcursosPublicos.Id
B.BTxt = Termos.Area
C.CTxt = ConcursosPublicos.Area
B.BParent = Termos.Pai
And here's my real code doing this work presently:
public static void Connect(ProgressBar progress)
{
lock (Locker)
using (var ctx = new ConcursosContainer())
{
int i = 0;
IList<Contatos> contatos = ctx.Contatos.ToList();
progress.Invoke((MethodInvoker) (() =>
{
progress.Value = 0;
progress.Maximum = contatos.Count;
}));
foreach (Contatos contato in contatos)
{
Console.WriteLine(contato.Id);
List<Termos> tree = GetTree(ctx, contato.Id).SelectMany(x => x.ToArray()).ToList();
List<int> attr = ctx.ConcursosContatos.Where(x => x.ContatoId == contato.Id).Select(x => x.ConcursoId).ToList();
IList<ConcursosPublicos> concursosPublicos = ctx.ConcursosPublicos.Where(x => !attr.Contains(x.Id)).ToList();
foreach (ConcursosPublicos concursosPublico in concursosPublicos)
{
if (tree.Any(termo => (termo.Tipo == concursosPublico.TipoConc) && concursosPublico.Area.Trim().EndsWith(termo.Area)))
{
ctx.ConcursosContatos.Add(new ConcursosContatos
{
ContatoId = contato.Id,
ConcursoId = concursosPublico.Id
});
i++;
}
if (i == 9)
{
ctx.SaveChanges();
i = 0;
}
}
progress.Invoke((MethodInvoker) (progress.PerformStep));
}
if (i > 0)
ctx.SaveChanges();
}
}
private static IEnumerable<Stack<Termos>> GetTree(ConcursosContainer ctx, int id)
{
var res = new List<Stack<Termos>>();
IQueryable<Termos> terms = ctx.Termos.Where(x => ctx.TermosContatos.Any(y => (y.ContatoId == id) && (y.TermoId == x.Id)));
foreach (Termos term in terms)
{
var stack = new Stack<Termos>();
if (term.Pai.HasValue)
AddParent(ctx, stack, term);
stack.Push(term);
res.Add(stack);
}
return res;
}
private static void AddParent(ConcursosContainer ctx, Stack<Termos> stack, Termos term)
{
Termos pai = ctx.Termos.First(x => x.Id == term.Pai.Value);
if (pai.Pai.HasValue)
AddParent(ctx, stack, pai);
stack.Push(pai);
}
This code does the job but for 1000+ members of ConcursosPublicos and 7000+ members of Contatos (with contatos on way to grow in the future) it can take between 15 to 20 hours to complete. Since this a daily process i need a more efficient way to fill in ConcursosContatos
You need some recursion to get the family tree on your B table. In SQL Server you can do this with a CTE:
;with chld as (
select B.BId, B.BTxt, B.BParent
from dbo.B as B
union all
select chld.BId , b1.BTxt, b1.BParent
from dbo.B as B1
inner join chld
on B1.BId = chld.BParent
)
select BId, BTxt from chld option(maxrecursion 32767)
The result set:
BId BTxt
1 a
2 b
3 c
4 x
3 b
3 a
2 a
If this isn't correct, no need to go any further. Otherwise, you can join this to the other tables as needed to populate your AhasC table.
I have 2 datatables.
DataTable wdt = new DataTable();
wdt.Columns.Add("wName", typeof(string));
wdt.Columns.Add("userID1", typeof(string));
wdt.Columns.Add("userID2", typeof(string));
wdt.Columns.Add("userID3", typeof(string));
wdt.Columns.Add("dttime", typeof(DateTime));
DataTable mdt = new DataTable();
mdt.Columns.Add("iD", typeof(string));
mdt.Columns.Add("firstname", typeof(string));
mdt.Columns.Add("lastname", typeof(string));
DataTable dt = new DataTable();
dt.Columns.Add("wName", typeof(string));
dt.Columns.Add("user1", typeof(string));
dt.Columns.Add("user2", typeof(string));
dt.Columns.Add("user3", typeof(string));
dt.Columns.Add("dttime", typeof(DateTime));
for (int i = 0; i < wdt.Rows.Count; i++)
{
DataRow ndr = dt.NewRow();
ndr[0] = wdt.Select()[i][0].ToString();
ndr[1] = (from r in mdt.AsEnumerable()
where r.Field<int>("iD") == Convert.ToInt32(wdt.Rows[i][1])
select r.Field<string>("firstName") + " " + r.Field<string>("lastName")).First<string>();
ndr[2] = (from r in mdt.AsEnumerable()
where r.Field<int>("iD") == Convert.ToInt32(wdt.Rows[i][2])
select r.Field<string>("firstName") + " " + r.Field<string>("lastName")).First<string>();
ndr[3] = (from r in mdt.AsEnumerable()
where r.Field<int>("iD") == Convert.ToInt32(wdt.Rows[i][3])
select r.Field<string>("firstName") + " " + r.Field<string>("lastName")).First<string>();
ndr[4] = wdt.Select()[i][4].ToString();
dt.Rows.Add(ndr);
}
In the above code, i get a new datatable dt from calculating the data from wdt & mdt. But here, i have to run the LINQ syntaxes in a loop. Is it possible to avoid loop & the same work to be done in a single LINQ loop?
Datatable1 :
iD firstname lastname
1 b v
2 d c
3 f g
4 s o
....
Datatable2 :
Code userid1 userid2 userid3 work
1f 1 3 6 gg
2g 1 4 7 gg
3b 3 4 7 gg
4v 4 3 8 gg
Expected New Datatable :
Code username1 username2 username3 work
1f a b c gg
2g d f r gg
3b c h g gg
4v d s h gg
Here, iD from datatable1 & userID1, userID2, userID3 are same.
username1 you can Make a query using left join and return new on the fly object witch have all property you need as below
var newData = (from a in wdt.AsEnumerable()
join user1Info in mdt.AsEnumerable() on a["userID1"] equals user1Info["iD"] into lUser1Info
join user2Info in mdt.AsEnumerable() on a["userID2"] equals user2Info["iD"] into lUser2Info
join user3Info in mdt.AsEnumerable() on a["userID3"] equals user3Info["iD"] into lUser3Info
from user1Info in lUser1Info.DefaultIfEmpty()
from user2Info in lUser2Info.DefaultIfEmpty()
from user3Info in lUser3Info.DefaultIfEmpty()
select new
{
wName = a["wName"].ToString(),
username1 = user1Info == null ? string.Empty : user1Info["firstname"].ToString() + user1Info["lastname"],
username2 = user2Info == null ? string.Empty : user2Info["firstname"].ToString() + user2Info["lastname"],
username3 = user3Info == null ? string.Empty : user3Info["firstname"].ToString() + user3Info["lastname"],
dttime = a["dttime"]
}).ToList();
I wonder what is the best solution for the given problem, simplified here:
I have two locally stored sql tables which I want to join (left join) with Default If Empty property, then I need to group these data
I don't want to check for (obj == null) before accessing obj.column, which will throw an error if join was no successful for a given row
Data
LeftTable RightTable OUTPUT
A B C A B Z A B C Z
1 1 1 1 1 5 1 1 1 5
1 2 2 1 2 6 1 2 2 6
5 6 7 5 6 7 null
Code
var RightTable = from row in Source
where row.X > 10
select new { // anonymous type that I want to keep
A = row.AAA,
B = row.BBB,
Z = row.ZZZ
};
var test = from left in LeftTable
from right in RightTable
.Where(right => right.A == left.A
&& right.B == left.B )
.DefaultIfEmpty( /* XXXXX */ ) //<-- this line is interesting
group left by new {
left.A
left.B
left.C
right.Z //<-- this will throw null exception error
} into g // but I don't want to change it to
select g; // Z = (right != null) ? right.Z : (string) null
Question:
Can I fill an argument in DefaultIfEmpty with anything that I can dynamically get from this code?
I know I can create a helper type like below and replace the anonymous type in RightTable select and use it inside default if empty like:
DefaultIfEmpty(new Helper())
but I dont want to do it as I have to deal with 20,30+ columns in real life scenario.
public class Helper {
public string A,
public string B,
public string C
}
Thanks a lot for your time if you read until here. Hope to get some solution here.
Thanks.
i think the code says everything:
var LeftTable = new[]
{
new { A = 1, B=1, C=1 },
new { A = 1, B=2, C=2 },
new { A = 5, B=6, C=7 }
}
.ToList();
var RightTable = new[]
{
new { A = 1, B=1, Z=5 },
new { A = 1, B=2, Z=6 }
}
.ToList();
var query = (from left in LeftTable
join right in RightTable
on new { left.A, left.B } equals new { right.A, right.B }
into JoinedList
from right in JoinedList.DefaultIfEmpty(new { A = 0, B = 0, Z = 0 })
group left by new
{
left.A,
left.B,
left.C,
right.Z
} into g
select g)
.ToList();
I had the same problem as you, I had to find a way, came up to something like this (very simplified example):
var toReturn = from left in bd.leftys
join rights in myAnonimousGroupedList on left.Id equals rights.leftId into joinings
from right in joinings.DefaultIfEmpty()
select new {
left.A
left.B
left.C
// Z = right != null ? right.Z : 0
Z = joinings.Any() ? right.Z : 0 // see what i did here?
};
Given the following tables I'd like to return the localised text for given culture or the text for the default culture where no row exist for the given culture.
diagram http://lh4.ggpht.com/_gjsCWAV_CZc/ShW6hC-eozI/AAAAAAAACbY/mXaBfiZtBY8/s400/diagram.png
So with the folowing data
Resources
ID Name
1 Donkey
2 Elephant
LocaleStrings
ID CultureID ResID LocaleText
1 1 1 Donkey
2 1 2 Elephant
3 2 1 baudet
I'd like to be able to return the following for the French culture
baudet
elephant
I've tried various queries based around LEFT JOINS samples I've seen but I'm stuck.
var ct = from r in db.Resources
join lt in db.LocaleStrings
on r.ID equals lt.ResID into res
from x in res.DefaultIfEmpty()
select new
{
CultureID = x.CultureID,
LocaleText = x.LocaleText,
ResID = x.ResID
};
var text =
from c in db.Cultures
join t in ct
on c.ID equals t.CultureID into cults
from x in cults.DefaultIfEmpty()
select x;
I'm sure there's a better way, but this seems to work:
var ct =
from c in db.Cultures
from l in db.LocaleStrings
from r in db.Resources
where r.ID == l.ResID
select new
{
CultureID = c.ID,
LocaleText = l.CultureID == c.ID ? l.LocaleText : r.Name,
ResID = r.ID,
LSID = l.CultureID == c.ID ? l.ID : 0
};
var text =
from t in ct
where t.LSID != 0 || (t.LSID == 0 && !((from ac2 in ct
where ac2.LSID > 0 && ac2.CultureID == t.CultureID
select ac2.ResID).Contains(t.ResID)))
select new
{
CultureID = t.CultureID,
LocaleText = t.LocaleText,
ResID = t.ResID
};