I have two Datatables in my C# Windows Forms application;
DataTable dtProduct;
DataTable dtDetails;
dtProduct is being populated from MS Access and dtDetails is from MySql, those have following like records
dtProduct
code | pNameOrignal
----------------------
101 | product one
220 | product two
65 | product three
104 | product four
221 | product five
654 | product six
dtDetails
id | tid | code | pNameLocal | qty | price
-------------------------------------------------
1 |101 | 101 | some_local_name | 2 |20.36
2 |102 | 202 | some_local_name | 1 |15.30 // same entry as same tid and all same
3 |102 | 202 | some_local_name | 1 |15.30 //same entry as same tid and all same
4 |102 | 202 | some_local_name | 1 |10.00 //same entry as same tid but price is different
5 |102 | 202 | some_local_name | 2 |15.30 //same entry as same tid but different qty
6 |102 | 202 | some_local_name2 | 1 |15.30 //same entry as same tid but pNameLocal different
7 |103 | 202 | some_local_name | 1 |15.30 // different entry of same product see different tid
8 |104 | 65 | some_local_name | 5 |05.00
9 |105 | 700 | some_local_name | 2 |07.01 // does not exist in "dtProduct"
I am currently looping through all records of dtdetails with dtProduct to replace original name of product pNameOrignal from dtProduct on basis of uniqueness of code column in both tables. my existing code look like this;
dtDetails.Rows
.Cast<DataRow>()
.Join
(
dtProduct.Rows.Cast<DataRow>(),
r1 => new { p1 = r1["code"], p2 = r1["code"] },
r2 => new { p1 = r2["code"], p2 = r2["code"] },
(r1, r2) => new { r1, r2 }
)
.ToList()
.ForEach(o => o.r1.SetField("pNameLocal", o.r2["pNameOrignal"]));
What is now required
need to merge or make one row for same entries with same tid and all same to single row by making qty to 2 (as in current records) by adding both and price to 30.60 (as in current records)
Explanation: multiple records which are exactly same (duplicates) merged as one row by retaining their qty and price as per number of records.
entry against tid = 105 which has code = 700 does not exist in dtProduct, it will not replace pOrignalName from there, will keep same name as currently my code do, I need to add or concatenate "NotFound" with that name.
For second point replacing the name with matched records and adding "NotFound" for non matched records.
var k = (from d in dtDetails.AsEnumerable() join
k1 in dtProduct.AsEnumerable() on d["code"] equals k1["code"] into dk1
from subset in dk1.DefaultIfEmpty()
select new { d, subset }).ToList();
foreach (var m in k)
{
if (m.subset != null)
{
if (string.Equals(m.d["code"], m.subset["code"]))
{
m.d.SetField("pNameLocal", m.subset["pNameOrignal"]);
}
}
else
{
m.d.SetField("pNameLocal", m.d["pNameLocal"] +"NotFound");
}
}
for first one try this
dtDetails = dtDetails.AsEnumerable()
.GroupBy(r => new { Col1 = r["tid"], Col2 = r["code"], Col3 = r["pNameLocal"] })
.Select(g =>
{
var row1 = dtDetails.NewRow();
row1["tid"] = g.Key.Col1;
row1["code"] = g.Key.Col2;
row1["pNameLocal"] = g.Key.Col3;
row1["qty"] = g.Sum(r => r.Field<int>("qty"));
row1["price"] = g.Sum(r => r.Field<decimal>("price"));
return row1;
})
.CopyToDataTable();
For your mentioned point 2, this can be done:
foreach (DataRow item in dtDetails.Rows)
{
var pNameOriginal = dtProduct.AsEnumerable().FirstOrDefault(i => i.Field<int>("code") == item.Field<int>("code"));
var globalName = "NotFound";
if (pNameOriginal != null && !string.IsNullOrEmpty(Convert.ToString(pNameOriginal.ItemArray[1])))
{
globalName = Convert.ToString(pNameOriginal.ItemArray[1]);
}
item["pNameLocal"] += globalName;
}
I am still not clear what do you want in point 1. How can the count be 2 for a group by in your case. Can you explain by taking tid 102 ?
Related
I have two tables:
Student Conference
+----+------+ +----+-------+---------------+
| id | name | | id | name | ref_studentId |
+----+------+ +----+-------+---------------+
| 1 | jack | | 1 | Rose | 1,12 |
| 2 | kai | | 2 | White | 12 |
| 3 | wind | | 3 | Black | 1,12,12356 |
+----+------+ +----+-------+---------------+
And I want to join them together with studentid or search for studentid in conference table.
You can use the LINQ Join method to achieve what you want:
var result = dataContext.Students.Join(dataContext.Conferences,
st => st.id,
cf => cf.ref_studentId,
(student, conference) => new { ... });
Although I would strongly recommend using the Entity Framework Navigation Properties, using which the above can be done more easily:
var result = dataContext.Student.Include(st => st.conference);
Update:
Please note that the above LINQ Query will fail to execute unless you will fix your Conference Table design, which is against SQL Normalization Forms (specifically the 1st Normal Form), saying that:
Each sell should be single-valued.
That means that you shouldn't have a comma (or any other character) separated values in your table columns.
To make the above query work, you have to make the ref_studentId contain only a single value for the StudentId:
Conference
+----+--------+-----------+
| ID | Name | StudentId |
+----+--------+-----------+
| 1 | Rose | 1 |
| 2 | Rose | 12 |
| 3 | White | 12 |
| 4 | Black | 1 |
| 5 | Black | 12 |
| 6 | Black | 12356 |
+----+--------+-----------+
But to be honest, this one too will not match the SQL Normalization rules.
Specifically, the 2nd Normal Form, saying that:
All Attributes (non-key columns) should be dependent on a Key.
And the 4th Normal form, saying that:
There should be no multi-value dependencies (Rose ⟶ 1, Rose ⟶ 12)
The correct solution would be to create another table for the Student⟶Conference relations.
Conference ConferencesStudents
+----+-------+ +----+--------------+-----------+
| ID | Name | | ID | ConferenceID | StudentId |
+----+-------+ +----+--------------+-----------+
| 1 | Rose | | 1 | 1 | 1 |
| 2 | White | | 2 | 1 | 12 |
| 3 | Black | | 3 | 2 | 12 |
+----+-------+ | 4 | 3 | 1 |
| 5 | 3 | 12 |
| 6 | 3 | 12356 |
+----+--------------+-----------+
Now, for this one the LINQ query will be:
dataContext.ConferencesStudents.Join(dataContext.Students,
cf => cf.ConferenceID,
st => st.ID,
(conferencesStudents, student) => new { conferencesStudents, student })
.Join(dataContext.Conferences,
cfSt => cfSt.conferencesStudents.ConferenceID,
cf => cf.ID,
(cfSt, conference) =>
new
{
Student = cfSt.student,
Conference = conference
});
Note: for the above, I've used the Anonymous Types just for demonstration and my strong advice would be to use real class Models instead.
OR
By using the same Navigation Properties (in case if you correctly had defined the EF relations) you can have a simpler version:
dataContext.ConferenceStudents.Include(cf => cf.Conferences)
.Include(cf => cf.Students)
Update 2:
I hate to say it but there is a kind of workaround in case if you can't change the Database design:
var results = (
from c in dataContext.Conference.ToList() // populating all Conferences
from s in dataContext.Students.ToList() // populating all Students
where c.ref_studentId.Split(',').Contains(s.id.ToString())
select new
{
Student = s,
Conference = c
}).ToList();
Note: this WILL NOT BE EFFECTIVE from the application performance perspective.
The better alternative compared to the above could be to write Stored Procedure and call it from the EF.
I have prepared a fiddle for you - if there are any questions feel free to ask.
Btw: This solution solves your question. The data-structure itself is ugly and should be normalized.
void Main()
{
var students = new List<Student>
{
new Student { Id = 1, Name = "jack" },
new Student { Id = 12, Name = "kai" },
new Student { Id = 12356, Name = "wind" }
};
var conferences = new List<Conference>
{
new Conference { Id = 1, Name = "Rose", RefIds = "1,12" },
new Conference { Id = 2, Name = "White", RefIds = "12" },
new Conference { Id = 25, Name = "Black", RefIds = "1,12,12356" }
};
var result = students.Select(s => new Tuple<int, Conference[]>(s.Id, conferences.Where(c => c.RefIds.Split(",").Contains(s.Id.ToString())).ToArray()));
}
// Define other methods, classes and namespaces here
public class Student
{
public int Id {get; set;}
public string Name {get;set;}
}
public class Conference
{
public int Id {get; set;}
public string Name {get; set;}
public string RefIds {get; set;}
}
List 1:
| User Id | Latest |
+---------------------+------------------+
| 1 | 1 |
| 2 | 3 |
| 3 | 3 |
| 4 | 0 |
List 2:
| User Id | Latest | Rating |
+---------------------+------------------+------------------+
| 1 | null | 10 |
| 2 | null | 12 |
| 3 | null | 11 |
| 4 | null | 16 |
I want to insert the values of the Latest column of List1 into the Latest column of List2 based on joining/comparing values of the User Id column in both lists.
I can use a foreach loop but that would run n*m I guess and look ugly. Is there a way to do it with LINQ or efficiently?
Regards.
Junaid
var result = from i1 in List1
join i2 in List2
on i1.UserId equals i2.UserId
select new
{
i2.UserId,
i1.Latest,
i2.Rating
};
you can do it with LINQ :
Try this code :
List2.ForEach(item1 =>
{
item1.Latest = List1.FirstOrDefault(item2 => item2.UserId == item1.UserId)?.Latest;
});
Note That, Latest must be Nullable.
LINQ will never change any of the source sequences, it can only extract data from it.
You will have to enumerate over the extracted data to update your original tables.
var recordsToUpdate = List2.Join(List1, // join List2 and List1
list2Row => list2Row.UserId, // from every row in List2 take UserId
list1Row => list1Row.UserId, // from every row in List1 take UserId
(list2Row, list1Row) => new // when they match make one new object
{
Id = list2Row.UserId, // take UserId from list2
Latest = list1Row.Latest, // take Latest from list1
Rating = list2Row.Rating, // take Rating from list2
})
.ToList(); // execute the query
I don't know how you update your records. Entity framework? SQL? it will be something like this:
foreach (var recordToUpdate in recordsToUpdate)
{
UpdateRecord(recordToUpdate.UserId, recordToUpdate.Latest, recordToUpdate.Rating)
// TODO: implement this function
}
Try something like this. this may fix your issue with adding the Latest value from List1 to List2.
List2.AddRange(List1.Select(user => new List1{
Latest = user.Latest,
UserID = user.UserID
}));
I know that there are some examples but I could not apply them on my code. I am pretty new to Linq and SQL. I have two tables I want to join.
First Table:
--------------
| Id | Count |
--------------
| 1 | 10 |
--------------
| 2 | 4 |
--------------
Second Table:
--------------
| Id | Name |
--------------
| 1 | Tom |
--------------
| 2 | John |
--------------
| 3 | Nick |
--------------
| 4 | Max |
--------------
As you can see, the second table has more records than the first. My goal is to join them based on the Id. The problem is that after I have joined the tables it only displays the matching records, which is Id 1 and 2. Though I want to display every Id (from 1 to 4) and if there is no match in both tables, there should be a default value 0.
It should look like this:
----------------------
| Id | Name | Count |
----------------------
| 1 | Tom | 10 |
----------------------
| 2 | John | 4 |
----------------------
| 3 | Nick | 0 |
----------------------
| 4 | Max | 0 |
----------------------
So far I have got this code:
// first table
var listCount = entity.tblKundes.Where(x => x.Studio == 2)
.Select(x => new { x.Id, x.Name})
.GroupBy(x => x.Name).ToList();
// second table
var listBerater = entity.tblUsers.Where(x => x.Studio == 2)
.Select(x => new { x.Id, x.Name})
.ToList();
// This join should be edited so that it displays non matching records as well
var test = listCount.Join(
listBerater,
count => count.Key,
berater => berater.Id,
(count, berater) => new { listCount = count, listBerater = berater }
).ToList();
Edit:
var test2 = (from list in listCount
join berater in listBerater on list.Berater equals berater.Id into gj
from sublist in gj.DefaultIfEmpty()
select new { sublist.Id, sublist.Nachname, sublist.Vorname }).ToList();
There is a typical concept in every Structured Query Languages which is called "Left join". Left-Join means that you will have all rows of data from first table even there is no equivalent in the second one. "Inner-Join" is a little different and only looks for matched rows of data.
Here you can find enough and complete information about your issue.
Left Join
I have the following table (table A):
ID | Data |
1 | Data1 |
2 | Data2 |
3 | Data3 |
4 | Data4 |
I have table B that has 1 row:
DummyID | Dummy |
1 | Dummy1 |
I have to join table A with table B in the following way:
ID | Data |DummyID | Dummy |
1 | Data1 |1 | Dummy1 |
2 | Data2 |1 | Dummy1 |
3 | Data3 |1 | Dummy1 |
4 | Data4 |1 | Dummy1 |
Obviously I can't use any ID in the on clause.
from item in context.TableA
join dummy in context.TableB on ? = ?
select new
{
RowA=item,
Dummy=dummy
}
How could I do that with LINQ?
That's a cross join which you can get via Linq in the following way
from item in context.TableA
from dummy in context.TableB
select new
{
RowA=item,
Dummy=dummy
}
Or the following in method syntax
context.TableA.SelectMany(
item => context.TableB.Select(dummy => new { RowA = item, Dummy = dummy }));
Note that if TableB every has more than one row you'll end up with N times M rows where N is the number of rows in TableA and M is the number of rows in TableB.
No need to join at all.
from item in context.TableA
select new
{
RowA = item,
Dummy = context.TableB.FirstOrDefault()
}
Having said that, I'd have to question why you're doing this. The idea of LINQ is to get your relational data into an object-oriented form. Why not just retrieve the TableB information once and do whatever processing you need to do in-memory? It would reduce the size of the payload you're transferring from the database back to the application.
Why do you want to use join. You want to use each item in your old sequence to create a different item in your new sequence. In LINQ you would use Enumerable.Select for this:
var dummy = context.Dummy.FirstOrDefault();
var newSequence = context.TableA
.Select(itemInTableA =>
new
{
RowA = itemInTableA,
Dummy = dummy,
});
I have one master table Category.
ID | Name
----------
1 | Category1
2 | Category2
3 | Category3
4 | Category4
And Another Table Details have field like
ID | CategoryId | Detail
--------------------
1 | 1,2,3 | Test1
2 | 3,4 | Test2
Here the Category Id stored as comma separated values.
Now i want the result as
ID | CategoryName
----------------
1 | Category1,Category2,Category3
2 | Category3,Category4
AnyOne Have idea ..??
You can use link this:
private static void commaSeperate(List<classname> obj)
{
string delimeter = ",";
Console.WriteLine(obj.Aggregate((i, j) => new classname { Name = (i.Name + delimeter + j.Name) }).Name);
Console.ReadKey();
}
This is just a sample, please modify according to your conditions.
Hope this will help you.
This could be the solution if our retrieve the data into memory first.
var q = from d in Details
from m in Master
select new {Id = d.Id, CategoryName = String.Join(m.Where(i=> d.CategoryId.Split(',').Cast<int32>().Contains(i.Id).Select(i => i.Name).ToArray(), ',')}
You can't join these two tables but I think below query would work for your result:
(from de in datacontextobj.Details
from ca in datacontextobj.Category
where de.CategoryId.Contains(ca.ID)
select de.ID, ca.Name).ToList();