I have a database called ebookstore.db as below:
and JSON as below:
I want when slug on JSON is not the same as a title in the database, it will display the amount of data with a slug on JSON which is not same as a title in the database in ukomikText.
Code:
string judulbuku;
try
{
string urlPath1 = "https://...";
var httpClient1 = new HttpClient(new HttpClientHandler());
httpClient1.DefaultRequestHeaders.TryAddWithoutValidation("KIAT-API-KEY", "....");
var values1 = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("halaman", 1),
new KeyValuePair<string, string>("limit", 100),
};
var response1 = await httpClient1.PostAsync(urlPath1, new FormUrlEncodedContent(values1));
response1.EnsureSuccessStatusCode();
if (!response1.IsSuccessStatusCode)
{
MessageDialog messageDialog = new MessageDialog("Memeriksa update Komik gagal", "Gangguan Server");
await messageDialog.ShowAsync();
}
string jsonText1 = await response1.Content.ReadAsStringAsync();
JsonObject jsonObject1 = JsonObject.Parse(jsonText1);
JsonArray jsonData1 = jsonObject1["data"].GetArray();
foreach (JsonValue groupValue in jsonData1)
{
JsonObject groupObject = groupValue.GetObject();
string id = groupObject["id"].GetString();
string judul = groupObject["judul"].GetString();
string slug = groupObject["slug"].GetString();
BukuUpdate file1 = new BukuUpdate();
file1.ID = id;
file1.Judul = judul;
file1.Slug = slug;
List<String> title = sqlhelp.GetKomikData();
foreach (string juduldb in title)
{
judulbuku = juduldb.Substring(juduldb.IndexOf('.') + 1);
if (judulbuku != file1.Slug.Replace("-", "_") + ".pdf")
{
BukuData.Add(file1);
ListBuku.ItemsSource = BukuData;
}
else
{
ukomikText.Text = "belum tersedia komik yang baru";
ukomikText.Visibility = Visibility.Visible;
}
}
}
if (ListBuku.Items.Count > 0)
{
ukomikText.Text = BukuData.Count + " komik baru";
ukomikText.Visibility = Visibility.Visible;
jumlahbuku = BukuData.Count;
}
else
{
ukomikText.Text = "belum tersedia komik yang baru";
ukomikText.Visibility = Visibility.Visible;
}
public static List<String> GetKomikData()
{
List<String> entries = new List<string>();
using (SqliteConnection db =
new SqliteConnection("Filename=ebookstore.db"))
{
db.Open();
SqliteCommand selectCommand = new SqliteCommand
("SELECT title FROM books where folder_id = 67", db);
SqliteDataReader query = selectCommand.ExecuteReader();
while (query.Read())
{
entries.Add(query.GetString(0));
}
db.Close();
}
return entries;
}
BukuUpdate.cs:
public string ID { get; set; }
public string Judul { get; set; }
public string Slug { get; set; }
I have a problem, that is when checking slugs on JSON, then the slug that is displayed is the first slug is displayed repeatedly as much data in the database, after that show the second slug repeatedly as much data on the database, and so on, as below:
How to solve it so that slug on JSON is not displayed repeatedly (according to the amount of data on JSON)?
The problem is that you have two nested foreach loops. What the code does in simplified pseudocode:
For each item in JSON
Load all rows from DB
And for each loaded row
Check if the current JSON item matches the row from DB and if not, output
As you can see, if you have N items in the JSON and M rows in the database, this inevitably leads to N*M lines of output except for those rare ones where the JSON item matches a specific row in database.
If I understand it correctly, I assume that you instead want to check if there is a row that matches the JSON item and if not, output it. You could do this the following way:
List<String> title = sqlhelp.GetKomikData();
HashSet<string> dbItems = new HashSet<string>();
foreach (string juduldb in title)
{
judulbuku = juduldb.Substring(juduldb.IndexOf('.') + 1);
dbItems.Add( judulbuku );
}
...
foreach ( JsonValue groupValue in jsonData1 )
{
...
//instead of the second foreach
if ( !dbItems.Contains( file1.Slug.Replace("-", "_") + ".pdf" ) )
{
//item is not in database
}
else
{
//item is in database
}
}
Additional tips
Avoid calling GetKomikData inside the foreach. This method does not have any arguments and that means you are just accessing the database again and again without a reason, which takes time and slows down the execution significantly. Instead, call GetKomikData only once before the first foreach and then just use title variable.
Don't assign ItemsSource every time the collection changes. This will unnecessarily slow down the UI thread, as it will have to reload all the items with each loop. Instead, assign the property only once after the outer foreach
write your code in one language. When you start mixing variable names in English with Indonesian, the code becomes confusing and less readable and adds cognitive overhead.
avoid non-descriptive variable names like file1 or jsonObject1. The variable name should be clear and tell you what it contains. When there is a number at the end, it usually means it could be named more clearly.
use plurals for list variable names - instead of title use titles
Related
My JSON File
{"data":
[
{"id":"1","user_code":"C016482","name":"CART 1","details":"[{\"Id\":15476,\"Name2\":\"AQUAFRESHIG TEETH (6+)\",\"SalesPriceVM\":250,\"DiscountPercentVM\":0,\"product_quantity\":3,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=15476\"},{\"Id\":22514,\"Name2\":\"BABE ANTI OILY DANDRUFF SHAMPOO 250 ML\",\"SalesPriceVM\":1800,\"DiscountPercentVM\":0,\"product_quantity\":1,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=22514\"},{\"Id\":19886,\"Name2\":\"ALOE VERA GEL\",\"SalesPriceVM\":1674,\"DiscountPercentVM\":0,\"product_quantity\":1,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=19886\"}]","created_at":"2020-05-20 11:52:49","updated_at":"2020-05-20 11:52:49"},
{"id":"2","user_code":"C020552","name":"CART1","details":"[{\"Id\":15480,\"Name2\":\"LISTERINE MOUTHWASH ORIGINAL 500 ML\",\"SalesPriceVM\":460,\"DiscountPercentVM\":0,\"product_quantity\":1,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=15480\"},{\"Id\":20572,\"Name2\":\"SAVLON ACTIVE HAND WASH RF 1000ML\",\"SalesPriceVM\":230,\"DiscountPercentVM\":0,\"product_quantity\":1,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=20572\"},{\"Id\":25899,\"Name2\":\"HANDISANITTIZER 200ML SOLUTION\",\"SalesPriceVM\":100,\"DiscountPercentVM\":0,\"product_quantity\":2,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=25899\"}]","created_at":"2020-05-21 01:43:10","updated_at":"2020-05-21 01:43:10"},
{"id":"3","user_code":"C020557","name":"PR 1","details":"[{\"Id\":13319,\"Name2\":\"EXIUM CAPS 20\",\"SalesPriceVM\":8.5,\"DiscountPercentVM\":0,\"product_quantity\":10,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=13319\"},{\"Id\":16432,\"Name2\":\"DOMIDON 10MG TAB\",\"SalesPriceVM\":2,\"DiscountPercentVM\":0,\"product_quantity\":10,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=16432\"},{\"Id\":19480,\"Name2\":\"ALOCAP SOFT CAP\",\"SalesPriceVM\":6,\"DiscountPercentVM\":0,\"product_quantity\":10,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=19480\"},{\"Id\":23223,\"Name2\":\"CYNTA 100MG TAB.\",\"SalesPriceVM\":25,\"DiscountPercentVM\":0,\"product_quantity\":40,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=23223\"},{\"Id\":2063,\"Name2\":\"MARLOX PLUS-200 ML-SUSPENSION\",\"SalesPriceVM\":110,\"DiscountPercentVM\":0,\"product_quantity\":1,\"Image_Url\":\"http:\\\/\\\/182.160.118.235:8099\\\/api\\\/ImageFile\\\/GetImage?enumEntityType=2&entityId=2063\"}]","created_at":"2020-05-21 07:30:29","updated_at":"2020-05-21 07:30:29"},
]
}
I am trying to read "details". In details, there are three objects I am trying to read Id,product_quantity of each object and insert them into the db table.
MY Code
public void PostSaveCartItemData()
{
var contents = File.ReadAllText("saved_carts.json");
dynamic jsonResponse = JsonConvert.DeserializeObject(contents);
int i = 0;
using (MainDataContext ctx = new MainDataContext())
{
List<SavedCartItem> list = new List<SavedCartItem>();
foreach (var item in jsonResponse.data)
{
var productId = item.details[i].Id;
var qty = item.details[i].product_quantity;
i++;
SavedCartItem entity = new SavedCartItem()
{
ProductId = productId,
Qty = qty ,
};
list.Add(entity);
}
ctx.SavedCartItems.AddRange(list);
ctx.SaveChanges();
}
}
Your kind help will be highly appreciated.
The details property is JSON encoded as a string, you need to further deserialise that value if you want to use it. Also, you should avoid using dynamic, there's no need. For example:
var jsonResponse = JObject.Parse(Json);
foreach (var item in jsonResponse["data"])
{
var detailsJson = item["details"];
var details = JArray.Parse(detailsJson.ToString());
foreach(var detail in details)
{
var productId = detail["Id"];
var qty = detail["product_quantity"];
Console.WriteLine(productId);
Console.WriteLine(qty);
}
}
I have a txt file, that has headers and then 3 columns of values (i.e)
Description=null
area = 100
1,2,3
1,2,4
2,1,5 ...
... 1,2,1//(these are the values that I need in one list)
Then another segment
Description=null
area = 10
1,2,3
1,2,4
2,1,5 ...
... 1,2,1//(these are the values that I need in one list).
In fact I just need one list per "Table" of values, the values always are in 3 columns but, there are n segments, any idea?
Thanks!
List<double> VMM40xyz = new List<double>();
foreach (var item in VMM40blocklines)
{
if (item.Contains(','))
{
VMM40xyz.AddRange(item.Split(',').Select(double.Parse).ToList());
}
}
I tried this, but it just work with the values in just one big list.
It looks like you want your data to end up in a format like this:
public class SetOfData //Feel free to name these parts better.
{
public string Description = "";
public string Area = "";
public List<double> Data = new List<double>();
}
...stored somewhere in...
List<SetOfData> finalData = new List<SetOfData>();
So, here's how I'd read that in:
public static List<SetOfData> ReadCustomFile(string Filename)
{
if (!File.Exists(Filename))
{
throw new FileNotFoundException($"{Filename} does not exist.");
}
List<SetOfData> returnData = new List<SetOfData>();
SetOfData currentDataSet = null;
using (FileStream fs = new FileStream(Filename, FileMode.Open))
{
using (StreamReader reader = new StreamReader(fs))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
//This will start a new object on every 'Description' line.
if (line.Contains("Description="))
{
//Save off the old data set if there is one.
if (currentDataSet != null)
returnData.Add(currentDataSet);
currentDataSet = new SetOfData();
//Now, to make sure there is something after "Description=" and to set the Description if there is.
//Your example data used "null" here, which this will take literally to be a string containing the letters "null". You can check the contents of parts[1] inside the if block to change this.
string[] parts = line.Split('=');
if (parts.Length > 1)
currentDataSet.Description = parts[1].Trim();
}
else if (line.Contains("area = "))
{
//Just in case your file didn't start with a "Description" line for some reason.
if (currentDataSet == null)
currentDataSet = new SetOfData();
//And then we do some string splitting like we did for Description.
string[] parts = line.Split('=');
if (parts.Length > 1)
currentDataSet.Area = parts[1].Trim();
}
else
{
//Just in case your file didn't start with a "Description" line for some reason.
if (currentDataSet == null)
currentDataSet = new SetOfData();
string[] parts = line.Split(',');
foreach (string part in parts)
{
if (double.TryParse(part, out double number))
{
currentDataSet.Data.Add(number);
}
}
}
}
//Make sure to add the last set.
returnData.Add(currentDataSet);
}
}
return returnData;
}
I have an app that checks the data on the server (json) and the database. I want when the id on json is not the same as the id on the database, it will insert all data with unequal id into the database.
Code:
var sqlpath = System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "ebookstore.db");
this.DataContextChanged += (s, e1) => { UpdateViewModel = DataContext as ViewModels.UpdateViewModel; };
string idDb = #"SELECT id FROM books where parent_folder_id = 2 and title like '%guru%'";
var IDdb = objConnUpdate.Prepare(idDb);
IDdb.Step();
iddb = IDdb[0].ToString();
IDDB = Convert.ToInt32(iddb.ToString());
ConnectionProfile connections = NetworkInformation.GetInternetConnectionProfile();
{
try
{
Downloading.IsOpen = true;
string urlPath1 = "https://.../fetch/k13G";
var httpClient1 = new HttpClient(new HttpClientHandler());
httpClient1.DefaultRequestHeaders.TryAddWithoutValidation("KIAT-API-KEY", "...*");
var values1 = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("halaman", "1"),
new KeyValuePair<string, string>("limit", "20"),
};
var response1 = await httpClient1.PostAsync(urlPath1, new FormUrlEncodedContent(values1));
response1.EnsureSuccessStatusCode();
string jsonText1 = await response1.Content.ReadAsStringAsync();
JsonObject jsonObject1 = JsonObject.Parse(jsonText1);
JsonArray jsonData1 = jsonObject1["data"].GetArray();
foreach (JsonValue groupValue in jsonData1)
{
JsonObject groupObject = groupValue.GetObject();
string ID = groupObject["id"].GetString();
BukuUpdate file1 = new BukuUpdate();
file1.ID = ID;
int intID = Convert.ToInt32(file1.ID);
if (intID != IDDB)
{
string jumlahidDb = #"SELECT COUNT(id) FROM books where parent_folder_id = 2 and id > " + IDDB + " and title like '%guru%'";
var jumlahIdDB = objConnUpdate.Prepare(jumlahidDb);
jumlahIdDB.Step();
if (jumlahiddb < jumlahbuku)
{;
if (nama == "Kelas_01_SD_")
{
DownloadBukuK2013G(url);
string K2013GUpdate = #"INSERT INTO books (id,title,folder_id,identifier,parent_folder_id) SELECT " + intID + ",'" + namaFile + ".pdf',34,'" + namaFile +
".pdf',2 WHERE not exists (select id AND title AND folder_id AND identifier AND parent_folder_id FROM books WHERE id=" + intID + " and title='" + namaFile +
".pdf' AND folder_id=34 and identifier='" + namaFile + ".pdf' and parent_folder_id=2)";
var K2013GQuery = objConnUpdate.Prepare(K2013GUpdate);
K2013GQuery.Step();
}
BukuUpdate.cs:
public class BukuUpdate
{
public string ID { get; set; }
}
Database:
I did not succeed in implementing it. How to handle it?
But if I use the code in my previous post, it only shows the first id in the database
You getting the first id by this code line iddb = IDdb[0].ToString(); for using. Actually the id list is saved in idDb variable.
I want if the id on json is not the same as the id on the database, then the data will be added to the database. If the same, then do nothing.
For this, you may just get all the ids from the database, and compare with the new income record from Json one by one. From your code snippet I'm not sure what's the Nuget package you're using for Sqlite, but the newest official tutorial using Microsoft.Data.SQLite package. I also strongly recommended you to use Microsoft.Data.SQLite package by following the official tutorial. By this way, the insert sample code may like this:
public sealed partial class MainPage : Page
{
private void btngettest_Click(object sender, RoutedEventArgs e)
{
List<String> ids = sqlhelp.GetData();
string idfromjson = "2";
foreach (string id in ids)
{
if (id != idfromjson)
{
//do insert operation
}
else
{
//do nothing
}
}
}
}
public class sqlhelp
{
public static void InitializeDatabase()
{
...
}
public static List<String> GetData()
{
List<String> entries = new List<string>();
using (SqliteConnection db =
new SqliteConnection("Filename=sqliteSample.db"))
{
db.Open();
SqliteCommand selectCommand = new SqliteCommand
("SELECT id from MyTable", db);
SqliteDataReader query = selectCommand.ExecuteReader();
while (query.Read())
{
entries.Add(query.GetString(0));
}
db.Close();
}
return entries;
}
Since your code snippet contains a lot of your own logic that I didn't give changes on your original code snippet. Please kindly reference my simple code which may be more clearly to know.
The application I am building allows a user to upload a .csv file, which will ultimately fill in fields of an existing SQL table where the Ids match. First, I am using LinqToCsv and a foreach loop to import the .csv into a temporary table. Then I have another foreach loop where I am trying to loop the rows from the temporary table into an existing table where the Ids match.
Controller Action to complete this process:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
var tc = new TemporaryCsvUpload();
foreach (var item in model)
{
tc.Id = item.Id;
tc.CreditInvoiceAmount = item.CreditInvoiceAmount;
tc.CreditInvoiceDate = item.CreditInvoiceDate;
tc.CreditInvoiceNumber = item.CreditInvoiceNumber;
tc.CreditDeniedDate = item.CreditDeniedDate;
tc.CreditDeniedReasonId = item.CreditDeniedReasonId;
tc.CreditDeniedNotes = item.CreditDeniedNotes;
entity.TemporaryCsvUploads.Add(tc);
}
var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);
foreach (var number in idMatches)
{
number.CreditInvoiceDate = tc.CreditInvoiceDate;
number.CreditInvoiceNumber = tc.CreditInvoiceNumber;
number.CreditInvoiceAmount = tc.CreditInvoiceAmount;
number.CreditDeniedDate = tc.CreditDeniedDate;
number.CreditDeniedReasonId = tc.CreditDeniedReasonId;
number.CreditDeniedNotes = tc.CreditDeniedNotes;
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
The issue in the above code is that tc is inside the first loop, but the matches are defined after the loop with var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);, so I am only getting the last item of the first loop.
So I would need to put var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id); in the first loop, but then I can't access it in the second. If I nest the second loop then it is way to slow. Is there any way I could put the above statement in the first loop and still access it. Or any other ideas to accomplish the same thing? Thanks!
Instead of using multiple loops, keep track of processed IDs as you go and then exclude any duplicates.
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
var tcIdFound = new HashSet<string>();
foreach (var item in model)
{
if (tcIdFound.Contains(item.Id))
{
continue;
}
var tc = new TemporaryCsvUpload();
tc.Id = item.Id;
tc.CreditInvoiceAmount = item.CreditInvoiceAmount;
tc.CreditInvoiceDate = item.CreditInvoiceDate;
tc.CreditInvoiceNumber = item.CreditInvoiceNumber;
tc.CreditDeniedDate = item.CreditDeniedDate;
tc.CreditDeniedReasonId = item.CreditDeniedReasonId;
tc.CreditDeniedNotes = item.CreditDeniedNotes;
entity.TemporaryCsvUploads.Add(tc);
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
If you want to make sure you get the last value for any duplicate ids, then store each TemporaryCsvUpload record in a dictionary instead of using only a HashSet. Same basic idea though.
Declare idMatches before the first loop, but don't instantiate it or set its value to null. Then you'll be able to use it inside both loops. After moving the declaration before the first loop, you'll still end up having the values from the last iteration using a simple Where. You'll need to concatenate the already existing list with results for the current iteration.
I'm hoping someone can help. A long time windows forms/aspx user, moving to WPF.
Not expecting a coded answer to this, but any pointers on a different way to approach would be greatly appreciated - I am probably approaching this in a very backward way.
So the objective is to have an ObservableCollection with sub ObservableCollection "childen" within to then bind to my WPF treeview control.
I can bind my collection to the treeview without issues, and have styled it with checkboxes images as desired, frustratingly, its the ObservableCollection with children of children of children I am having trouble generating in the first place.
I have a table in SQL with LDAP Paths, and various other information I'm storing against that LDAP path, which I read into my ObservableCollection.
Single level, no problem, the bit I'm struggling with is sorted the sub objects of sub objects by LDAP Path, so when I bind to the treeview is presented as AD OU's are structured.
EG:
TopOU
Users
Front Office Users
Helpdesk Users
Example LDAP Paths in my DB
LDAP://OU=Front Office Users,OU=Users,OU=TopOU,DC=dev,DC=local
LDAP://OU=Helpdesk Users,OU=Users,OU=TopOU,DC=dev,DC=local
LDAP://OU=OU=Users,OU=TopOU,DC=dev,DC=local
LDAP://OU=OU=TopOU,DC=dev,DC=local
private ObservableCollection<AssignmentData> OUTreeAssignmentsCollection = new ObservableCollection<AssignmentData>();
public class AssignmentData : INotifyPropertyChanged
{
public Int32 AssignmentID { get; set; }
public String AssignmentName { get; set; }
public AssignmentTypes AssignmentType { get; set; }
//other stuff....
//For TreeView all sub nodes
public ObservableCollection<AssignmentData> Children { get; set; }
}
I then start to read from my db in a rather nasty way, and this is where it all goes wrong, and I could use some pointers.
cmd = new SqlCommand("SELECT UserGroups.UserGroupID, UserGroups.Name, UserGroups.LDAPPath FROM UserGroups WHERE UserGroups.TypeID=1", DBCon);
reader = cmd.ExecuteReader();
while (reader.Read())
{
String strLDAPHierarchical = GetLDAPHierarchical(reader[2].ToString());
AssignmentData newItem = new AssignmentData()
{
AssignmentID = Convert.ToInt32(reader[0]),
AssignmentName = reader[1].ToString(),
AssignmentImage = ouIcon,
AssignmentLDAPPath = reader[2].ToString(),
AssignmentCNPath = GetCNFromLDAPPath(reader[2].ToString()),
AssignmentTooltip = GetADSLocationTooltip(reader[2].ToString()),
AssignmentType = AssignmentTypes.UserOU,
AssignmentLDAPHierarchical = strLDAPHierarchical
};
if (strLDAPHierarchical.Contains(","))
{
//Now check all the root nodes exist to continue
String strLDAPHierarchicalCheckPath = strLDAPHierarchical;
String[] SplitLDAPHierarchical = strLDAPHierarchical.Split(new Char[] { ',' });
Int32 reverseI = SplitLDAPHierarchical.Length - 1;
String prevPath = "";
for (int i = 0; i < SplitLDAPHierarchical.Length; i++)
{
String path = SplitLDAPHierarchical[reverseI];
//now check if this node is already there and if not look it up and create it
if (path != "")
{
if (i == 0) { strLDAPHierarchicalCheckPath = path; }
else { strLDAPHierarchicalCheckPath = path + "," + prevPath; }
WriteLog("CHECK:" + strLDAPHierarchicalCheckPath);
LookupItemByLDAPHierarchical(strLDAPHierarchicalCheckPath, newItem);
if (i == 0) { prevPath = path; }
else { prevPath = path + "," + prevPath; }
reverseI = reverseI - 1;
}
}
}
else
{
//is top level object, so create at the root of the collection
UserOUCollection.Add(newItem);
}
Function to add sub items :-/
internal AssignmentData LookupItemByLDAPHierarchical(String strLDAPHierarchical, AssignmentData fromItem)
{
AssignmentData currentItem = null;
foreach (AssignmentData d in UserOUCollection)
{
if (d.AssignmentLDAPHierarchical == strLDAPHierarchical) { currentItem = d; break; }
if (d.Children != null)
{
currentItem = CheckChildNodesByLDAPHierarchical(d, strLDAPHierarchical);
if (currentItem != null) { break; }
}
}
String strMessage = "null";
if (currentItem != null) { strMessage = currentItem.AssignmentLDAPPath; }
if (currentItem == null)
{
String strWhere = "LDAPPath LIKE 'LDAP://" + strLDAPHierarchical + "%'";
SqlConnection DBCon = new SqlConnection(SQLString);
DBCon.Open();
SqlCommand cmd = new SqlCommand("SELECT UserGroupID, Name, LDAPPath FROM UserGroups WHERE " + strWhere + " AND TypeID=1", DBCon);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
strLDAPHierarchical = GetLDAPHierarchical(reader[2].ToString());
AssignmentData newItem = new AssignmentData()
{
AssignmentID = Convert.ToInt32(reader[0]),
AssignmentName = reader[1].ToString(),
AssignmentImage = ouIcon,
AssignmentLDAPPath = reader[2].ToString(),
AssignmentCNPath = GetCNFromLDAPPath(reader[2].ToString()),
AssignmentTooltip = GetADSLocationTooltip(reader[2].ToString()),
AssignmentType = AssignmentTypes.UserOU,
AssignmentLDAPHierarchical = strLDAPHierarchical
};
String strLDAPHierarchicalCheckPath = strLDAPHierarchical;
foreach (String path in strLDAPHierarchical.Split(new Char[] { ',' }))
{
//now check if this node is already there and if not look it up and create it
if (path != "")
{
strLDAPHierarchicalCheckPath = strLDAPHierarchicalCheckPath.Replace(path + ",", "");
currentItem = LookupItemByLDAPHierarchical(strLDAPHierarchicalCheckPath, currentItem);
if (null == currentItem)
{
UserOUCollection.Add(newItem); //new root item
}
else
{
if (currentItem.Children == null)
{
//add new child
currentItem.Children = new ObservableCollection<AssignmentData> { newItem };
}
else
{
//add more children to exisiting
currentItem.Children.Add(newItem);
}
}
currentItem = null;
}
}
//Find a current Item to add the node to
//currentItem = LookupItemByLDAPHierarchical(strLDAPHierarchical);
}
reader.Close();
reader.Dispose();
DBCon.Close();
DBCon.Dispose();
}
return currentItem;
}
With my current solution, I get a treeview, with sub nodes of sub nodes, but they are wrong/lots of duplication etc. I have spent literally days trying to fix my probably overcomplicated attempt above - but have come to the conclusion I'm probably going about it the wrong way.
Any help greatly appreciated!
Just having a peruse ;) through your code. Think I can see why you have lots of duplications. Looks like your first SQL query get's all parent/child records. Then the second query will go and get some of those records again, if that makes sense.
One approach would be to only get the top level items in your first query. Possibly by getting SQL to count the number of commas.
SELECT UserGroups.UserGroupID, UserGroups.Name, UserGroups.LDAPPath,
LENGTH(LDAPPath) - LENGTH(REPLACE(LDAPPath, ',', '')) as CommaCount
FROM UserGroups
WHERE UserGroups.TypeID=1
AND CommaCount = 2
Since you asked for different approach id say it's not very efficient to repeatedly query the database in a loop. When I'm building a tree of parent child objects I'd normally get all parent/child records in one query. Build a flat dictionary of all the objects. Then loop through it and make the parent/child associations.
The dictionary can also be useful to lookup your objects later on either directly by key or to loop through without having to make a recursive function that crawls the tree.
So I'd suggest that you break it down into 2 blocks of code.
First block: Using your existing query that get's all of the items, create a flat Dictionary with everything in.
They key of each item should probably be the result from GetLDAPHierarchical().
Second block: Next loop through the dictionary and create the hierarchy. Add anything with no parent directly to the UserOUCollection
foreach(AssignmentData d in myDictionary.Values)
{
String parentKey = GetParentLDAPKey(d.AssignmentLDAPHierarchical);
if (myDictionary.ContainsKey(parentKey))
{
myDictionary(parentKey).children.Add(d);
}
else
{
UserOUCollection.Add(d);
}
}
GetParentLDAPKey() will need to produce the same key as it's parent by removing the first part of the LDAP Path.
Hope that points you in the right direction.
H
(SMASH)
Thanks so much to hman, who pointed me in a much more logical direction. I used LDAPPath as my dictionary key.
Dictionary<String, AssignmentData> OUDictionary = new Dictionary<String, AssignmentData>();
//Read from DB
cmd = new SqlCommand("SELECT UserGroups.UserGroupID, UserGroups.Name, UserGroups.LDAPPath FROM UserGroups WHERE UserGroups.TypeID=1", DBCon);
reader = cmd.ExecuteReader();
while (reader.Read())
{
AssignmentData newItem = new AssignmentData()
{
AssignmentID = Convert.ToInt32(reader[0]),
AssignmentName = reader[1].ToString(),
AssignmentImage = ouIcon,
AssignmentLDAPPath = reader[2].ToString(),
AssignmentCNPath = GetCNFromLDAPPath(reader[2].ToString()),
AssignmentTooltip = GetADSLocationTooltip(reader[2].ToString()),
AssignmentType = AssignmentTypes.UserOU,
};
UserOUDictionary.Add(reader[2].ToString(), newItem);
}
reader.Close();
reader.Dispose();
//Now Read OU List into TreeView Collection
foreach (AssignmentData d in UserOUDictionary.Values)
{
String parentKey = GetParentLDAPPath(d.AssignmentLDAPPath);
if (UserOUDictionary.ContainsKey(parentKey))
{
AssignmentData parentItem = UserOUDictionary[parentKey];
if (parentItem.Children == null) { parentItem.Children = new ObservableCollection<AssignmentData> { d }; } //add first child
else { parentItem.Children.Add(d); } //add more children to exisiting
}
else
{
UserOUCollection.Add(d); //add to root of control
}
}
private String GetParentLDAPKey(String strLDAPPath)
{
String retParentKey = strLDAPPath;
if (strLDAPPath.Contains(","))
{
retParentKey = retParentKey.Replace("LDAP://", "");
retParentKey = retParentKey.Remove(0, retParentKey.IndexOf(",") + 1);
retParentKey = "LDAP://" + retParentKey;
}
return retParentKey;
}