Nested foreach loop slow - c#

The following piece of code achives the desired results, but performance is extremely slow:
SearchResultCollection absaUsers = ABSAds.FindAll();
SearchResultCollection srcUsers = ds.FindAll();
foreach (SearchResult users in srcUsers)
{
string cn = users.Properties["cn"][0].ToString();
string sn = users.Properties["sn"][0].ToString();
string userID = users.Properties["uid"][0].ToString();
string description = users.Properties["PersonnelAreaDesc"][0].ToString();
string jobCodeID = users.Properties["JobcodeID"][0].ToString();
string CostCentreID = users.Properties["costCentreID"][0].ToString();
string CostCentreDescription = users.Properties["CostCentreDescription"][0].ToString();
string givenName = users.Properties["givenName"][0].ToString();
string employmentStatus = users.Properties["EmploymentStatus"][0].ToString();
string EmploymentStatusDescription = users.Properties["EmploymentStatusDescription"][0].ToString();
foreach (SearchResult absaUser in absaUsers)
{
string absaUID = absaUser.Properties["uid"][0].ToString();
string absaEmploymentStatus = absaUser.Properties["EmploymentStatus"][0].ToString();
string absaEmploymentStatusDescription = absaUser.Properties["EmploymentStatusDescription"][0].ToString();
string absaEmployeeNumber = absaUser.Properties["employeeNumber"][0].ToString();
if (absaUID == cn && absaEmployeeNumber==userID)
{
Console.WriteLine("User Record Found:" + cn);
sw.WriteLine("Modify" + "," + cn + "," + description + "," + userID + "," + givenName + "," + sn + "," + jobCodeID + "," + CostCentreID + "," + CostCentreDescription + "," + sn + "," + cn + "," + employmentStatus + "," + EmploymentStatusDescription);
sw.Flush();
break;
}
}
}
It loops through 2 collections and mtaches the outer loops attributes with the inner's. Any suggestions on how I can optimise the performance?

It would be faster if you extracted all the absaUID values to a lookup first:
var lookup = absaUsers.Cast<SearchResult>()
.ToLookup(x => x.Properties["uid"][0].ToString());
Then you can just use:
foreach (SearchResult users in srcUsers)
{
string cn = users.Properties["cn"][0].ToString();
foreach (SearchResult matches in lookup[cn])
{
...
}
}
You haven't shown how absaUsers is defined - if it's a LINQ query expression, then it could be that your existing code will be going to the database on every iteration at the moment - whereas the above won't. On the other hand, if srcUsers is also a LINQ query expression talking to a database, you should consider doing all the matching at the database using a join instead.

you could use LINQ join, some examples are here, I'm assuming whoever built it into .NET found a pretty optimal way of doing that, and then loop through that. on a sidenote: what are your collection types? please add their declaration to the code snippet.

Use Lamda expressions:
Below is the sample one , You can optizime this to another level.
List<SearchResult> allResultGroups=new List<SearchResult>();
foreach (SearchResult absaUser in absaUsers)
{
resultGroups = srcUsers.Where(g => g.cn == absaUser.absaUID && absaUser.absaEmployeeNumber==g.userID ).ToList();
}

Related

sort data from csv to txt

I am writing a application which sorts out data from a csv to txt.
I have witten but I cannot get the required output.
Can someone please help, I do not see were I went wrong.
I initially thought its the File.WriteAllLines which was the problem but even when I write to a console I get the same results.
My file looks something like this
Georgina,Sinclair,408999703657,cheque,First National Bank,Fourways,275.00,12/01/2012
Zachary,Whitehead,409122372301,cheque,ABSA,Irene,70.25,12/01/2012
Toby,Henderson,401255489873,cheque,First National Bank,Edenvale,181.03,12/13/2012
Katherine,Cooke,409155874935,savings,ABSA,Southdowns,975.89,01/01/2013
Bradley,James,409254998,savings,ABSA,Melville,207.74,12/09/2012
Sophie,Lane,409771987,savings,ABSA,Roodepoort,207.74,12/31/2012
My output should be something like this
First National B0020000045603
GSinclair 408999703657 CH Fourways 002750001122012
THenderson 401255489873 CH Edenvale 001810313122012
ABSA 0040000146162
ZWhitehead 409122372301 CH Irene 000702501122012
KCooke 409155874935 SAVSouthdowns009758901012013
BJames 409254998 SAVMelville 002077409122012
SLane 409771987 SAVRoodepoort002077431122012
The code I currently have only returns the header and 2 lines which looks as follows.
ABSA 0040000146162
KCooke 409155874935 SAVSouthdowns 009758901012013
Please assist.
My code looks as follows
string text = #"C:\\Test\\output.txt";
var inputEntries = File.ReadLines(#"C:\\Test\\debitorders.csv").Select(line =>
{
var values = line.Split(',');
return new
{
accountholder = values[0].Trim().Substring(0, 1) + values[1].Trim(),
accountnumber = long.Parse(values[2].Trim()),
accounttype = values[3].Trim(),
bankname = values[4].Trim(),
branch = values[5].Trim(),
amount = 100 * double.Parse(values[6].Trim()),
date = DateTime.Parse(values[7].Trim())
};
});
var banks = inputEntries
.OrderBy(e => e.bankname)
.GroupBy(e => e.bankname, e => e);
foreach (var bank in banks)
{
var AccountName = bank.Key;
if (AccountName.Length >= 20)
{
AccountName = AccountName.Substring(0, 16);
}
else
{
AccountName += new string(' ', 20 - AccountName.Length);
}
var NumberOfAccounts = bank.Count();
var TotalAmount = bank.Select(acc => acc.amount).Sum();
var Header = AccountName + "\t" + NumberOfAccounts.ToString("000") + TotalAmount.ToString("0000000000");
var sortedAccounts = bank
.OrderBy(acc => acc.accountholder)
.OrderByDescending(acc => acc.amount);
foreach (var account in sortedAccounts)
{
var outputLine =
account.accountholder + "\t" +
account.accountnumber + "\t" +
//get first 2 characters
account.accounttype.Substring(0, 3).ToUpper() + account.branch + "\t" + "00" +
account.amount +
account.date.ToString("ddMMyyyy");
for (int i = 0; i < 15; i++)
{
File.WriteAllText(text, Header + Environment.NewLine + outputLine);
Console.WriteLine(Header + outputLine);
Console.ReadLine();
}
}
}
A better and cleaner solution will be to make use of List<string> to which you add your text. At the end of the code just convert the list to an array and write all lines to a file.
List<string> outputLine = new List<string>(); //note this addition to the code
foreach (var bank in banks)
{
//do header formatting stuff here
var Header = somecode
outputLine.Add(Header); //Add Header to outputLine
var sortedAccounts = bank.OrderBy(acc => acc.accountholder)
.OrderByDescending(acc => acc.amount);
foreach (var account in sortedAccounts)
{
var tempStringBuilder =
account.accountholder + "\t" +
account.accountnumber + "\t" +
//get first 2 characters
account.accounttype.Substring(0, 3).ToUpper() + account.branch + "\t" + "00" +
account.amount +
account.date.ToString("ddMMyyyy");
outputLine.Add(tempStringBuilder); //Add tempStringBuilder to outputLine
}
}
File.WriteAllLines("destination path", outputLine.ToArray()); //Write everything to your output file in one go
Alternative Excel solution:
Microsoft Excel has a really powerful tool called Pivot Tables, which is ideally suited to your needs. If you are unfamiliar with it, read some tutorials about it. At first it's a process to get your head around the workflow to use it but it is quite simple once you've grasped it. You just drag and drop fields by which you want to group.
You might also want to consider using Data Connections to link to your original data, which is also quite simple given the dataset you have.
I think i found the solution:
File.AppendAllText(text, Header + Environment.NewLine + outputLine + Environment.NewLine);
Use File.AppendAllText instead of File.WriteAllText. With WriteAllText you always deleted the old content.
But consider to clean the file (File.WriteAllText(text, "");) before you begin to write on it, otherwise you will have the old data from last run also in it.
Try to use String.Format("{0,-10}", name) which means that the length of the name is filled up with spaces up to the length of 10. Minus means left alignment and positive causes right alignment.
I updated your code to:
string text = #"D:\C#\output.txt";
File.WriteAllText(text, "");
var inputEntries = File.ReadLines(#"D:\c#\debitorders.csv").Select(line =>
{
var values = line.Split(',');
return new
{
accountholder = values[0].Trim().Substring(0, 1) + values[1].Trim(),
accountnumber = long.Parse(values[2].Trim()),
accounttype = values[3].Trim(),
bankname = values[4].Trim(),
branch = values[5].Trim(),
amount = 100 * double.Parse(values[6].Trim()),
date = DateTime.ParseExact(values[7].Trim(), "MM/dd/yyyy", CultureInfo.InvariantCulture)
};
});
var banks = inputEntries.OrderBy(e => e.bankname)
.GroupBy(e => e.bankname, e => e);
foreach (var bank in banks)
{
var AccountName = bank.Key;
var NumberOfAccounts = bank.Count();
var TotalAmount = bank.Select(acc => acc.amount).Sum();
var Header = String.Format("{0,-20} {1,-10} {2}", AccountName, NumberOfAccounts.ToString("000"), TotalAmount.ToString("0000000000"));
var sortedAccounts = bank.OrderBy(acc => acc.accountholder)
.OrderByDescending(acc => acc.amount);
File.AppendAllText(text, Header + Environment.NewLine);
Console.WriteLine(Header);
foreach (var account in sortedAccounts)
{
var outputLine = String.Format("{0,-11} {1,15} {2,-3} {3,-10} {4,7} {5,-10}",
account.accountholder,
account.accountnumber,
account.accounttype.Substring(0, 3).ToUpper(),
account.branch,
account.amount,
account.date.ToString("ddMMyyyy")
);
//get first 2 characters
//account.accounttype.Substring(0, 3).ToUpper() + account.branch + "\t" + "00" +
// what are the "00" for? didn't include them you may this do by yourself
File.AppendAllText(text, outputLine + Environment.NewLine);
Console.WriteLine(outputLine);
}
File.AppendAllText(text, Environment.NewLine);
Console.WriteLine();
}
Output is:
ABSA 004 0000146162
KCooke 409155874935 SAV Southdowns 97589 01012013
BJames 409254998 SAV Melville 20774 09122012
SLane 409771987 SAV Roodepoort 20774 31122012
ZWhitehead 409122372301 CHE Irene 7025 01122012
First National Bank 002 0000045603
GSinclair 408999703657 CHE Fourways 27500 01122012
THenderson 401255489873 CHE Edenvale 18103 13122012

how to increase the size of array or free the memory after each iteration. Error: Index was outside the bounds of the array c#

I read data from a text file which is 27 MB file and contains 10001 rows, I need to handle large data. I perform some kind of processing in each row of data and then write it back to a text file. This is the code I have am using
StreamReader streamReader = System.IO.File.OpenText("D:\\input.txt");
string lineContent = streamReader.ReadLine();
int count = 0;
using (StreamWriter writer = new StreamWriter("D:\\ft1.txt"))
{
do
{
if (lineContent != null)
{
string a = JsonConvert.DeserializeObject(lineContent).ToString();
string b = "[" + a + "]";
List<TweetModel> deserializedUsers = JsonConvert.DeserializeObject<List<TweetModel>>(b);
var CreatedAt = deserializedUsers.Select(user => user.created_at).ToArray();
var Text = deserializedUsers.Where(m => m.text != null).Select(user => new
{
a = Regex.Replace(user.text, #"[^\u0000-\u007F]", string.Empty)
.Replace(#"\/", "/")
.Replace("\\", #"\")
.Replace("\'", "'")
.Replace("\''", "''")
.Replace("\n", " ")
.Replace("\t", " ")
}).ToArray();
var TextWithTimeStamp = Text[0].a + " (timestamp:" + CreatedAt[0] + ")";
writer.WriteLine(TextWithTimeStamp);
}
lineContent = streamReader.ReadLine();
}
while (streamReader.Peek() != -1);
streamReader.Close();
This code helps does well up to 54 iterations as I get 54 lines in the output file. After that it gives error "Index was outside the bounds of the array." at line
var TextWithTimeStamp = Text[0].a + " (timestamp:" + CreatedAt[0] + ")";
I am not very clear about the issue if the maximum capacity of array has been violated, if so how can I increase it or If I can write the individual line encountered in loop through
writer.WriteLine(TextWithTimeStamp);
And clean the storage or something that can solve this issue. I tried using list insead of array , still issue is the same.Please help.
Change this line
var TextWithTimeStamp = Text[0].a + " (timestamp:" + CreatedAt[0] + ")";
to
var TextWithTimeStamp = (Text.Any() ? Text.First().a : string.Empty) +
" (timestamp:" + (CreatedAt.Any() ? CreatedAt.First() : string.Empty) + ")";
As you are creating Text and CreatedAt collection objects, they might be empty (0 total item) based on some scenarios and conditions.
Those cases, Text[0] and CreatedAt[0] will fail. So, before using the first element, check if there are any items in the collection. Linq method Any() is used for that purpose.
Update
If you want to skip the lines that do not contain text, change this lines
var TextWithTimeStamp = Text[0].a + " (timestamp:" + CreatedAt[0] + ")";
writer.WriteLine(TextWithTimeStamp);
to
if (Text.Any())
{
var TextWithTimeStamp = Text.First().a + " (timestamp:" + CreatedAt.First() + ")";
writer.WriteLine(TextWithTimeStamp);
}
Update 2
To include all the stringss from CreatedAt rather than only the first one, you can add all the values in comma separated strings. A general example
var strings = new List<string> { "a", "b", "c" };
var allStrings = string.Join(",", strings); //"a,b,c"

accessing data from database and displaying it in textbox

I am using this code for accessing data from database and displaying it in textboxes,but i am getting whole string columns in 1st textbox ,how do i split and display in respective textboxes,i am getting this exception Index was outside the bounds of the array. at this line of code txtOption2.Text = coldata[2];
public EditQuestionMaster(int qid_value)
{
InitializeComponent();
string columns = db.GetEditQuestions(qid_value);
string[] coldata=columns.Split('$');
txtQuestion.Text = coldata[0];
txtOption1.Text = coldata[1];
txtOption2.Text = coldata[2];
txtOption3.Text = coldata[3];
txtOption4.Text = coldata[4];
}
GetEditQuestions(qid_value) Code
public string GetEditQuestions(int qid)
{
string data = "";
try
{
string sql = "select QID,Question,Opt1,Opt2,Opt3,Opt4,AnsOp,Marks from Questions where QID IN(" + qid + ") ";
cmd = new OleDbCommand(sql, acccon);
rs = cmd.ExecuteReader();
if (rs.Read())
{
data = rs[0].ToString() + "~" + rs[1].ToString() + "~" + rs[2].ToString() + "~" + rs[3].ToString() + "~" + rs[4].ToString() + "~" + rs[5].ToString() + "~" + rs[6].ToString() + "~" + rs[7].ToString() + "$";
}
}
catch (Exception err)
{
}
return data;
}
thank you in advance for any help
You appear to split the string by $ but you build the string up using ~ as the separator. You need to split the string by ~ to get the appropriate number of columns i.e.
string[] coldata = columns.Split("~")
You are seeing that error because you only have 2 items in coldata. Try debugging and view the length of the coldata array to see how many items it contains.
Change your code to use this split instead:
string[] coldata=columns.Split('~');
Looking at your code sample you just need to change:
string[] coldata=columns.Split('$');
To
string[] coldata=columns.Split('~');
As your columns are delimited by the ~ character.

error: The query results cannot be enumerated more than once

Edit:
DataClassesDataContext dc = new DataClassesDataContext();
string _idCompany = Request["idCompany"];
var newes = dc.GetNewsCompany(Int64.Parse(_idCompany));
string date = "";
string newsHtml = "<center>";
if(newes.GetEnumerator().MoveNext()){
foreach (var item in newes)//say Error .......................
{
// date = calendar.GetDayOfMonth(item.DateSend) + "/" + calendar.GetMonth(item.DateSend) + "/" + calendar.GetYear(item.DateSend).ToString();
// newsHtml += "<li class='news-item'><a style='text-decoration:none' class=\"link\" onclick=\"$(\'#BodyNews\').text(\'" + HttpUtility.HtmlEncode(item.Body).Trim() + "\');$(\'#BodyNews\').dialog({resizable:false});\" href=\"#\" > " + item.Title.ToString() + "</a> " + date + " </li>";
}
newsHtml += "</center>";
}
else
{
// var propertyCompany = dc.GetPropertyCompanyById(Int64.Parse(_idCompany));
// newsHtml += "<li class='news-item'><a style='text-decoration:none' class=\"link\" );$(\'#BodyNews\').dialog({resizable:false});\" href=\"#\" > " + "!به صفحه شخصی شرکت " + propertyCompany.FirstOrDefault().NameCompany + " خوش آمدید " + "</a> " + date + " </li>";
}
return newsHtml;
say error:The query results cannot be enumerated more than once
how check var is empty or null with out enumerated;
Why bother with the if at all?
var newes = dc.GetNewsCompany(Int64.Parse(_idCompany));
//if (newes.GetEnumerator().MoveNext())//check is null or empty
var newesList = newes.ToList();
if (neweList.Count > 0)
{
...
}
You can always check the newesList.Count property afterward.
Not sure what's available as a member in newes, but if it's an object and depending on what dc.GetNewsCompany returns you could check for null
if (news == null) return;
or if it returns an empty collection/array, just check the count/length:
if (news.Count == 0) return;
if (news.Length == 0) return;
the error comes, because you are using .GetEnumerator() on newes and then using the newes again in a foreach Loop .. this causes the "double enumeration".
Generally avoid walking "such var"'s with a foreach, since the DataReader is locked the whole loop !. Means that you cannot use the same entitie in this loop.
Better .ToList() , you can the list.AsQuearable agian if you want to Linq on it
f.e. something like
var newes = dc.CompanyTable.Where(ln => ln.id.Equals(_idCompany));;
List<CompanyTable> newesList = newes.ToList();

Adding double quotes to # string

As far as i understood, a string with an # in required a set of double quotes to insert the quote in to the string?
I have tried that principle and to no avail. The following line works, but if i were to replace those strings with parameter values then i cant seem to get the correct compilation value
var node = doc.SelectSingleNode(#"//node[#label = ""Chemist Name""]/node[#label = ""John,Smith""]");
my attempt (of which i have tried several versions and ended up here, where i have now givn up !)
var node = doc.SelectSingleNode(#"//node[#label = " + ""+parentID+"" + "]/node[#label = " + ""+ name +"" + "]");
can anyone help me please?
Use single quotes:
var node = doc.SelectSingleNode
(#"//node[#label = 'Chemist Name']/node[#label = 'John,Smith']");
var node = doc.SelectSingleNode(
string.format(#"//node[#label = '{0}']/node[#label = '{1}']"
, parentID, name));
You are missing another double quote to close the string being appended and also # before each string containing "".
Try this:
var node =
doc.SelectSingleNode(#"//node[#label = """ + parentID + #"""]/node[#label = """ + name + #"""]");
var node = doc.SelectSingleNode(string.format(#"//node[#label = ""{0}""]/node[#label = ""{1}""]", parentId, name));
Write an extension method to extend string:
public static string Quote(this string input)
{
return string.Format(#"""{0}""", input);
}
And then use it as follows:
var node = doc.SelectSingleNode(#"//node[#label = " + parentID.Quote() + "]/node[#label = " + name.Quote() + "]");
Or simply:
var node = doc.SelectSingleNode(string.Format(#"//node[#label = {0}"]/node[#label = {1}"]",parentID.Quote(), name.Quote());

Categories