Trouble creating a third list using items from two lists - c#

I'm trying to put together a list (let's call this FinalList) using the combined values of two lists: Customers and Products. Suppose we have four Customers and one Product, FinalList should have a final result of four items (one for each customer).
For example:
Customer List:
Customer Code | Customer Name | Customer Branch ID
------------------|-----------------------|------------------------
001 | Tom | T001
002 | Dick | T002
003 | Harry | T003
004 | Jerry | T004
Product List:
Product Code | Product Name
------------------|---------------------
P001 | Apple
Currently I'm trying to do it this way:
var finalList = new List<ProductDetailDto>();
var customerList = new List<CustomerGroup>();
/// productsList is also type List<ProductDetailDto>();
for (var j = 0; j<= productsList.Count()-1; j++)
{
for (int i = 0; i <= customerList.Count() - 1; i++)
{
var singleDetail = new ProductDetailDto();
// Copy current products to groupDetail
singleDetail = productsList[j];
// Assemble rest of the info
singleDetail.CustCode = customerList[i].Customer.CustomerCode;
singleDetail.CustName = customerList[i].Customer.CustomerName;
singleDetail.CustBranchId = customerList[i].Customer.CustomerBranchId;
finalList.Add(singleDetail);
}
}
return finalList;
After executing this however, finalList only used Jerry as customer for all four items. I tried using foreach as well with the same results. I'm not really sure what I did wrong here, and I'm embarrassed that this seems basic to some, so I'm hoping for a fresh set of eyes to spot what mistake I made here...
Also, is there any way I can further optimize this?
Any help will be greatly appreciated, as always. Thank you.

Here:
// Copy current products to groupDetail
singleDetail = productsList[j];
You don't actually copy current product, but you copy a reference to your item from productsList, and on every inner loop iteration you override properties in the same productsList[j] element.
You may want to read more on how assignment works on reference types:
https://www.microsoftpressstore.com/articles/article.aspx?p=2454676
You need to create a new object if you want to make a cross product of two lists:
var finalList = new List<ProductDetailDto>();
var customerList = new List<CustomerGroup>();
/// productsList is also type List<ProductDetailDto>();
for (var j = 0; j<= productsList.Count()-1; j++)
{
for (int i = 0; i <= customerList.Count() - 1; i++)
{
var singleDetail = new ProductDetailDto
{
ProductCode = productsList[j].ProductCode,
ProductName = productsList[j].ProductName
// and whatever other properties your product have
};
// Assemble rest of the info (these can actually go to object initializer too)
singleDetail.CustCode = customerList[i].Customer.CustomerCode;
singleDetail.CustName = customerList[i].Customer.CustomerName;
singleDetail.CustBranchId = customerList[i].Customer.CustomerBranchId;
finalList.Add(singleDetail);
}
}
return finalList;
As for me, it is confusing that you have properties like CustCode, CustName and CustBranchId in your ProductDetailDto. Are these properties just empty for objects in productsList? Consider creating another class specially for these needs like CustomerProductDto, so that your intention becomes more clear.
You can optimize this using LINQ:
var items = from p in productsList
from c in customerList
select new ProductDetailDto
{
ProductCode = p.ProductCode,
ProductName = p.ProductName
CustCode = c.Customer.CustomerCode,
CustName = c.Customer.CustomerName,
CustBranchId = c.Customer.CustomerBranchId,
};
return items.ToArray();

This line of code:
singleDetail = productsList[j];
affects a pointer and not values, so in the end you have a list of the same pointer so you have only the last modification repeated customerList.Count()
So you must add the values one by one like customerList

Related

How to group by all employees with same manager in a list using c#?

I have a list that contains details of Employees(their name, email) and email of the managers they are reporting to.
Eg. List<> EmpD
abcd | abcd#gmail.com | abcdmgr#gmail.com
pqr | pqr#gmail.com | pqrmgr#gmail.com
pqrs | pqrs#gmail.com | pqrmgr#gmail.com
Now, i need to send mail to each employees and also to managers with mail body containing names of all the employees under him.
For this, i have used two loops.
One to get the manager email and other to check if manager email matches with the one we obtained from first loop. If yes, i send mail to those employees and add them to another new list of employees. After second loop is completed, i have list of all employees under that manager. So now i send him mail, with this new list.
Now first loop goes into second iteration and repeats.
The above way is working fine, but i want to optimise it. Currently, the same loop is being traversed twice and despite that i have to create another new list to store similar type of data.
Is there a way to group all the employees under same manager, maybe through Linq (i am new to it)?
My Code:
int countemp = 0;
while (countemp < listemp.Count())
{
var l = listemp[countemp];
List<EmpRecord> empR = new List<EmpRecord>();
foreach (var e in listemp)
{
if (l.Emp_reportingTo == e.Emp_reportingTo)
{
empR.Add(new EmpRecord() { Emp_Name = e.Emp_Name, EmpIn_Time = e.EmpIn_Time });
SendMailEmp(e.Emp_Name,e.Emp_MailID); //Send mail to Employee
countemp++;
}
}
SendMailMngr(l.Emp_reportingTo, empR); //Send mail to Manager
}
EDIT:
With the help of suggestions provided , I have tried another code :
var results = from p in listemp
group p.Emp_Name by p.Emp_reportingTo into g
select new { Emp_reportingTo = g.Key, empR = g.ToList() };
It works fine. However, i can only fetch one column(Emp_Name) in this. How do i get another column, say Emp_InTime too ?
You can do GroupBy on List<Employee> by Emp_reportingTo and then loop the grouped results like this.
var listemp = new List<Employee>();
var groupedEmpByMgr = listemp.GroupBy(x => x.Emp_reportingTo);
foreach (var empsByMgr in groupedEmpByMgr)
{
var empR = new List<EmpRecord>();
foreach (var emp in empsByMgr)
{
empR.Add(new EmpRecord() { Emp_Name = emp.Emp_Name, EmpIn_Time = emp.EmpIn_Time });
SendMailEmp(emp.Emp_Name, emp.Emp_MailID);
}
SendMailMngr(empsByMgr.Key, empR);
}

The right way to insert multiple records to a table using LINQ to Entities

As many of us have done, I set up a simple loop to add multiple records from a databse. A prototypical example would be something like this:
Method I:
// A list of product prices
List<int> prices = new List<int> { 1, 2, 3 };
NorthwindEntities NWEntities = new NorthwindEntities();
foreach (int price in prices)
{
Product newProduct = new Product();
newProduct.Price = price;
NWEntities.Products.AddObject(newProduct);
}
NWEntities.SaveChanges();
When I first set up the loop, however, I intuitively wrote:
Method II:
Product newProduct = new Product();
foreach (int price in prices)
{
newProduct.Price = price;
NWEntities.Products.Add(newProduct);
}
After doing a little reading, several individuals mentioned that if the Method II is used only one record would be added to the table. This seems counter intuitive. It's the Add() function which load a new insertion, and, I would think, creates an object after each call with the data passed in. Declaring my Product object outside the loop would seem to better utilize resources, as the only overhead consumed in each call would be the re-assignment of the object instance property, and not the re-construction of the object instance itself.
Can anyone please clarify? I could not find another post that deals with this question directly. If one is out there please point to it.
Simply move the instantiation of the new Product inside the loop. Your code as it is written will add a single instance multiple times which does not produce what you are after...you need a separate instance of each product...the Add method does not make a copy, it attaches the object to the context and marks it for insertion.
foreach (int price in prices)
{
Product newProduct = new Product();
newProduct.Price = price;
NWEntities.Products.Add(newProduct);
}
To see what is happening a bit more explicity consider the following:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Try to reuse same Instance:");
using (var ctx = new AdventureWorksEntities())
{
List<int> ids = new List<int> {1, 2, 3};
Product p1 = new Product();
Product reference = p1;
Product p2;
Console.WriteLine("Start Count: {0}", ctx.Products.Count());
foreach (var id in ids)
{
p1.ProductID = id;
p2 = ctx.Products.Add(p1);
Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
Console.WriteLine("p2 = reference? {0}", p2 == reference);
Console.WriteLine("State: {0}", ctx.Entry(p1).State);
var changes = ctx.ChangeTracker.Entries<Product>();
Console.WriteLine("Change Count: {0}", changes.Count());
}
}
Console.WriteLine();
Console.WriteLine("Distinct Instances:");
using (var ctx = new AdventureWorksEntities())
{
List<int> ids = new List<int> { 1, 2, 3 };
Product p2;
foreach (var id in ids)
{
var p1 = new Product {ProductID = id};
p2 = ctx.Products.Add(p1);
Console.WriteLine("p1 = p2 ? {0}", p1 == p2);
Console.WriteLine("State: {0}", ctx.Entry(p1).State);
var changes = ctx.ChangeTracker.Entries<Product>();
Console.WriteLine("Change Count: {0}", changes.Count());
}
}
Console.ReadLine();
}
}
In the first loop you are reusing the same product instance, but when you add it to the context you are just using the same reference each time. You can see that the change count remains at 1 regardless of how many times the loop is executed. Of course only the last values would be saved if you were to call ctx.SaveChanges().
In the second version, the change count is correctly incremented each time and you would be calling SaveChanges would save all of distinct entities as you would expect.
+1 For Terryt's answer. You need to stick with method one or something similar.
In Entity framework 6 version, there is a new method to add a set of data in a single statement. This is the AddRange Method.
I would like to add that I find the AddRange method elegant when you want to add entities based on an existing list (or IEnumerable).
In your case it could be done somewhat like this:
NWEntities.Products.AddRange(
Prices.Select(priceitem =>
new Product{price = priceitem})
)
Semantically this should be similar to your method 1. One Product object is instanciated per price in the price list. There is however one difference, it is done anonymously, so there are no explicit defined reference variables pointing to the new object.
If performance is important then this question might give you some further information: Fastest Way of Inserting in Entity Framework
Hope this give you some help.
We wouldn't need the help of loop. We can do this by linq. As by the below code, names are have to add into Employee table from the nameList with the bit field IsDeleted.
db.Employee.AddRange(
nameList.Select(name =>
new Employee
{
Name = name,
IsDeleted = false
})
);
I had a similar problem. In my issue, I had this code:
var cratelist = db.TruckContainerLoads.Where(x => x.TruckID == truckid).Select(x => x.ContainerID);
if (!cratelist.Any())
{
return;
}
foreach (var crateid in cratelist) {
TruckContainerLoad crInstance = new TruckContainerLoad();
crInstance.ContainerID = crateid;
try
{
db.TruckContainerLoads.Add(crInstance);
db.SaveChanges();
}
catch
{
return;
}
}
My query only added the first record in my foreach. The issue was that I needed to call my db.SaveChanges() outside of the foreach loop, after adding multiple records. For me, the answer to my issue was actually in the question. So, I'm upvoting the question.

Fast lookup in a multidimensional System.Collection.IList

I need your advice on the following.
I have a multi-dimensional IList containing items which have an index, Id and Text. Normally I know the value of Id and based on that I need to get the Text. Both Id and Text values are read from a database.
What we are currently using to get the value of Text field is:
foreach (Object myObj in List)
{
if (((MessageType)myObj).Id == id)
{
return ((MessageType)myObj).Text;
}
}
When count in IList becomes large (more than 32K), it takes some time to process.
Question: Is there a way to efficiently get the Text value without iterating through the IList?
Things I tried without success:
Use List.IndexOf(Id) - did not work because IndexOf applies to text only.
Converting List to multi-dimensional array - failed on List.CopyTo(array,0) my guess because it is multi-dimensional:
string[] array=new string[List.Count,List.Count];
List.CopyTo(array,0);
I can not use a AJAX/JQuery solution because it is an existing(live) project and it will take too much to re-code.
Thanks
If you want fast searching by some identifier in a collection with 32k elements, you should use Dictionary<K,V> as your collection.
var dict = new Dictionary<IDType, MessageType>();
A Dictionary is basically a search tree where the elements are stored in a sorted way so an element with a specific key (in your case Id) can be found without looking at all elements. For more information see MSDN.
If you cannot refactor the collection to be a dictionary, you may initially fill the dictionary (slow) and then search in the dictionary (fast). This will only be faster if you do multiple searches before you fill the dictionary again, i.e. if your list does not change often.
foreach(object o in List)
{
var msg = (MessageType)o;
dict.Add(msg.Id, msg);
}
Searching then is easy:
MessageType msg = dict[id];
EDIT: Well, I was curious and wrote a test routine which compares the linear search and the dictionary approach. Here's what I used:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
class MessageType
{
public string Id;
public string Text;
}
class Program
{
static void Main(string[] args)
{
var rand = new Random ();
// filling a list with random text messages
List<MessageType> list = new List<MessageType>();
for (int i = 0; i < 32000; i++)
{
string txt = rand.NextDouble().ToString();
var msg = new MessageType() {Id = i.ToString(), Text = txt };
list.Add(msg);
}
IList List = (IList)list;
// doing some random searches
foreach (int some in new int[] { 2, 10, 100, 1000 })
{
var watch1 = new Stopwatch();
var watch2 = new Stopwatch();
Dictionary<string, MessageType> dict = null;
for (int i = 0; i < some; i++)
{
string id = rand.Next(32000).ToString();
watch1.Start();
LinearLookup(List, id);
watch1.Stop();
watch2.Start();
// fill once
if (dict == null)
{
dict = new Dictionary<string, MessageType>();
foreach (object o in List)
{
var msg = (MessageType)o;
dict.Add(msg.Id, msg);
}
}
// lookup
DictionaryLookup(dict, id);
watch2.Stop();
}
Console.WriteLine(some + " x LinearLookup took "
+ watch1.Elapsed.TotalSeconds + "s");
Console.WriteLine("Dictionary fill and " + some
+ " x DictionaryLookup took "
+ watch2.Elapsed.TotalSeconds + "s");
}
}
static string LinearLookup(IList List, string id)
{
foreach (object myObj in List)
{
if (((MessageType)myObj).Id == id)
{
return ((MessageType)myObj).Text;
}
}
throw new Exception();
}
static string DictionaryLookup(Dictionary<string, MessageType> dict,
string id)
{
return dict[id].Text;
}
}
}
The results I got in Release / x86:
Number of | Time [ms] with | Time[ms] with | Speedup (approx.)
searches | linear search | dictionary(*) | with dictionary
----------+----------------+---------------+-----------------
2 | 1.161 | 2.006 | 0.6
----------+----------------+---------------+-----------------
10 | 2.834 | 2.060 | 1.4
----------+----------------+---------------+-----------------
100 | 25.39 | 1.973 | 13
----------+----------------+---------------+-----------------
1000 | 261.4 | 5.836 | 45
----------+----------------+---------------+-----------------
(*) including filling the dictionary once.
So, I was a bit optimistic to say that searching twice would already pay off. In my test application I have to search 10 times for the dictionary to be faster.
I'm sorry I could not make a more realistic example, my Ids are all sorted. Feel free to try modifying and experimenting though ;-)
From the looks of it you have a List<MessageType> here, which is not multi-dimensional. Rather the objects inside the list have multiple properties.
You could easily get them out with LINQ much faster than a loop most likely:
var text = (from MessageType msgType in myList
where msgType.Id == id
select msgType.Text).FirstOrDefault();
Or even easier with an inline LINQ statement:
var text = myList.Where(s => s.Id == id).Select(s => s.Text).FirstOrDefault();
NOTE: As mentioned in comments above, the speed of these LINQ statements are only as good as the object's position in the List. If it is the last object in the list, you will likely see the same performance discrepancy. Dictionary<Index, MessageType> is going to be much more performant.
Better way is to use ILookup.
For example:
var look = query.ToLookup(x => x.SomeID, y=> y.Name)
and use:
if (look.Contains(myID)){
var name = look[myID].First();
}

Copy each list item to another list item attribut

Is there an easy way, and most importantly, more optimized to do the fowling code:
class Chair{
int numberOfLegs=4;
}
class House{
Chair chair;
String name="My Home";
}
// add chairs to each house
IList<Chair> chairs = new List<Chair>(10); // let us imagine that we have 10 different chairs...
// Code to replace:
IList<House> houses = new List<House>(chairs.Count());
for (int i = 0; i < houses.Count(); i++){
houses[i].chair = chairs[i]
}
My own suggestion it is replacing the for with Linq
// Code to replace:
IList<Homes> homes = chairs.Select(c => new Home{ Chair = c}).ToList();
Is it faster than the old code?
Do you have a better idea? Maybe using chairs.AsParallel?

Entity Framework 6 : Given multiplicity constraints, a corresponding 'CProperty' must also in the 'Deleted' state

I tried following code with following designed model.
First call to SaveChanges() is succeed but not when it comes to second call.
I have already worked 18 hours on this and can't figure out what is the problem.
Specially when I can achieve the code goal manually with MSSQL server explorer !
Can anyone provide me a solution ?
var mc = new Model1Container1();
mc.Categories.Add(new Category() { Text = "Laptop" });
mc.Categories.Add(new Category() { Text = "TV" });
mc.SaveChanges();
var cat = mc.Categories.Where(c => c.Text == "Laptop").FirstOrDefault();
CKey ck = new CKey() { Key = "RAM" };
cat.CKeys.Add(ck);
for (int i = 1; i < 100; i++)
{
var ia = new Item() { Text = "MSI GX780-R", Category = cat };
ia.CProperties.Add(new CProperty() { Value = "4GB", CKey = ck });
mc.Items.Add(ia);
mc.SaveChanges();
}
You are trying to associate the same CKey instance to 100 instances of CProperty, but according to the graph of multiplicities, a given instance of CKey can only be associated to at most 1 CProperty.
Either create a new instance of CKey in each iteration of the loop, or modify your schema.

Categories

Resources