Best way to resolve OutOfMemoryException? (C#) - c#

I'm using C#, .Net 4.6.x, and the Telerik controls for WinForms in Visual Studio 2017 RC. The application consists of a "main" window that uses a RadRibbon and a RadPageView. The RadPageView is dynamically populated with pages based on a search form which is the first page, or by a user requesting a new blank form. The other pages are inherited from RadPageViewPage and have an additional property called "TQC". TQC refers to a custom control that loads on the page.
The TQC has several dropdownlists and text areas in RadPageViewPages that are all contained in a RadPageView control within the TQC object. It doesn't bind any data to its controls until its RPVP (the class inheriting RadPageViewPage) is Selected. One of the dropdownlists contains 200 or so entries when it's populated (a list of accounts).
The problem I've run into is that when the outer RadPageView Remove()s a page, the memory taken by that page isn't freeing up, to the order of a couple hundred megabytes. This is problematic, as the target machines have between 4GB and 8GB of RAM. I tried setting the data objects that populate the controls to null as part of the closing event, but nothing changed. I also tried explicitly calling the Dispose() method on all descendants of the RadPageViewPage as below:
private void rpvQtabs_PageRemoved(object sender, RadPageViewEventArgs e)
{
foreach (TQC c in e.Page.Controls)
{
foreach (Control ca in c.Controls)
{
foreach (Control cac in ca.Controls)
{
cac.Dispose();
}
ca.Dispose();
}
c.Dispose();
}
e.Page.Dispose();
}
I'm still getting a crazy huge memory leak, and if more than 5 tabs are viewed by the user (even if they close the page), an OutOfMemoryException is soon to follow. I tried attaching the Performance Profiler, but it crashes on the attempt. VS 2015 isn't an option at the moment. How can I ensure that the pages are disposed properly, or reduce the memory footprint on the extremely large dropdownlists? This is our first foray into using Telerik.
In response to questions in the comments:
The object that throws the error is usually relatively random and depends on which accounts are loaded. It's not anything infinitely recursive. This is how the control's is originally loaded (using a special connection class):
public static List<Account> List(bool includeDefaults = true)
{
//search
var rs = new List<Account>();
string q = "select distinct r.ID, r.name from db.addressbook r";
DataTable dt = new DataTable();
using (var cmd = new CustomConnectionClass())
{
cmd.Safety.Off();
dt = cmd.ExecuteDirectQuery(q);
}
foreach (DataRow r in dt.Rows)
{
var a = new Account();
a.ID = long.Parse(r[0].ToString());
a.Name = r[1].ToString();
rs.Add(a);
}
rs = rs.OrderBy(t => t.ID).ToList();
var n = new Account();
n.ID = 0;
n.Name = "Generic Account";
var o = new Account();
o.ID = 999999;
o.Name = n.Name;
rs.InsertRange(0, new Account[] { n, o });
return rs;
}

After further investigation, I was able to sort out that this involves how Telerik loads its themes and controls. Making all datasources for the dropdownlists static reduced the footprint a bit, but the thememanager and themes for the controls within each tab were loaded as new each time. By design, they aren't necessarily disposed when the control is closed and disposed. The design of the UI will have to be re-worked to prevent users from running out of memory. The problem goes away if the same design is incorporated using standard WinForms.

Related

Using large number of UserControl in WindowsForms

I'm used to working with HTML apps in which there is no problem of creating a list of components (like phonebook contact).
Example of my app
Now I have used this method in Windows Forms. One panel is populated with large number of custom UserControls (UC). Only 30 UserControls takes more than 5s to render. While the list of data from database is returnd in <1s.
This UC has only labels, PictureBox and Click event. It's called like this. This data is used to populate child controls.
new UserControl(MyModel data);
Is there a beter way of doing this? I would like to have user friendly GUI and not using Grid layout. It's not user friendly and very limited in terms of how data can be showed to user.
foreach (var data in myDbResult)
{
var uc = new MyUserControl(data);
uc.Dock = DockStyle.Top;
resultFlowPanel.Controls.Add(uc);
}
...
public MyUserControl(MyModel data)
{
this.data = data;
InitializeComponent();
label1.Text = data.name;
label2.Text = data.address;
// get some more data from database
using (IDbConnection db = new SqlConnection(ConfigurationManager.ConnectionStrings["AlarmDbCon"].ConnectionString))
{
payer = db.Query<Models.g_UserPayer>("SELECT TOP(1) * FROM g_UserPayer WHERE ID_User=#UserID", new
{
UserID = guard.ID_User
}).FirstOrDefault();
}
label3.Text = payer.email;
PictureBox.Image = payer.image;
}
If there are your own UI controls then you should consider using in your Load method the this.SuspendLayout(); and this.ResumeLayout(); methods to postpone the expensive UI layout calculation of the UI control.
Another aspect you can use is to load your expensive data on a Background Thread. To be able to do this you should use delegates to be able to update controls created on the UI Thread. More info on this on this SO answer.

TestStack White performance slow and speed up

I am using TestStack White to automate a test scenario in a Telerik Winforms application.
The application has plenty of elements and if I directly search for an element, the run would just stale and never end.
So I did the manual hierarchy search to dig into element levels to save the performance and make it work.
Then the code looks in a not neat way as I heavily use foreach to do the loop myself.
Is this the proper way to cope with the performance or we have better ways to
have both neat code and good performance when using TestStack White?
Cheers:
[TestMethod]
public void TestMethod1()
{
CoreAppXmlConfiguration.Instance.RawElementBasedSearch = true;
CoreAppXmlConfiguration.Instance.MaxElementSearchDepth = 2;
var applicationDirectory = "D:\\Software\\ABC Handling System";
var applicationPath = Path.Combine(applicationDirectory, "ABCClient.GUI.exe");
Application application = Application.Launch(applicationPath);
Thread.Sleep(5000);
Window window = application.GetWindow("[AB2] - ABC Handling System 2 - [Entity Search]", InitializeOption.NoCache);
ListBox listbox = window.Get<ListBox>();
ListItem dispatchButton = listbox.Items.Find(i=>i.Name.Equals("Display"));
dispatchButton.Click();
Thread.Sleep(5000);
SearchCriteria sc = SearchCriteria.ByControlType(ControlType.Pane).AndIndex(3);
UIItemContainer groupbox = (UIItemContainer)window.MdiChild(sc);
UIItemContainer pane1 = null;
foreach (IUIItem automationElement in groupbox.Items)
{
if (automationElement.Name == "radSplitContainer1")
{
pane1 = (UIItemContainer)automationElement;
break;
}
}
UIItemContainer pane2 = null;
foreach (IUIItem automationElement in pane1.Items)
{
if (automationElement.Name == "splitPanel1")
{
pane2 = (UIItemContainer)automationElement;
break;
}
}
Table table = null;
foreach (IUIItem automationElement in pane2.Items)
{
if (automationElement.Name == "Telerik.WinControls.UI.RadGridView ; 247;14")
{
table = (Table)automationElement;
break;
}
}
TableRow row = table.Rows[9];
string s = row.ToString();
}
Update on 05/11/2019:
It turned out TestStack White is not the best solution for my multi-row grid app regarding its performance.
Actually we managed to have developed something from the grid side by developers, and we hook those functions up. So we were using AutoIt + Customized Application side hooks. Some really good controls there.
Yes, we succeeded in this approach.
Yeah instead of doing the looping yourself you would just use the Get on each of the retrieved elements. Oddly when I dig down through the code it is using AutomationElement.FindFromPoint which I am not sure how it traverses the tree to find it's elements. I would give the follow code a try and see if it is more or less performant in your application.
CoreAppXmlConfiguration.Instance.RawElementBasedSearch = true;
CoreAppXmlConfiguration.Instance.MaxElementSearchDepth = 2;
var applicationDirectory = "D:\\Software\\ABC Handling System";
var applicationPath = Path.Combine(applicationDirectory, "ABCClient.GUI.exe");
Application application = Application.Launch(applicationPath);
Thread.Sleep(5000);
Window window = application.GetWindow("[AB2] - ABC Handling System 2 - [Entity Search]", InitializeOption.NoCache);
ListBox listbox = window.Get<ListBox>();
ListItem dispatchButton = listbox.Get<ListItem>(SearchCriteria.ByText("Display"));
dispatchButton.Click();
Thread.Sleep(5000);
UIItemContainer groupbox = window.MdiChild(SearchCriteria.ByControlType(ControlType.Pane).AndIndex(3));
UIItemContainer pane1 = groupbox.Get<UIItemContainer>(SearchCriteria.ByText("radSplitContainer1"));
UIItemContainer pane2 = pane1.Get<UIItemContainer>(SearchCriteria.ByText("splitPanel1"));
Table table = pane2.Get<Table>(SearchCriteria.ByText("Telerik.WinControls.UI.RadGridView ; 247;14")); ;
TableRow row = table.Rows[9];
string s = row.ToString();
I am getting everything from the previous element in an attempt to limit the portion of the tree it is scanning. From the looks of White's code this may have no affect other than reducing the amount of boiler plate on the front end depending on the performance of AutomationElement.FindFromPoint.
Also the ByText search criteria maps to the name property on a AutomationElement by calling CreateForName which is why I replaced all the checks against name with ByText.

Entity framework object context saves new entities that were not added

I have been working with Entity Framework (VS2010 Framework 4.0) in my proyect. I had some trouble with using a different object context per form. What I did then, was to create a object context in the Main Menu Form (stays opened) and everytime I create and show one form, I pass that object context to this new form. Example:
public partial class frm_Menu : Base
{
public Sistema_financiero_Entities db = new Sistema_financiero_Entities();
private void cancelarCuotaToolStripMenuItem_Click(object sender, EventArgs e)
{
frm_Cancelacion_Cuota Form1 = new frm_Cancelacion_Cuota();
Form1.db = db;
Form1.Show();
}
}
Ok, that solution worked fine until now because I needed to use and pass objects throw the differents forms sometimes, and if the objects contexts were different, I got an error.
Now, I have detected a huge issue using it this way. I have a form, where I can pay for the different installments of a loan. I´ll attach an image so then you can see what I´m talking about.
There, you select the different installments you want to pay for. Then, you introduce the value you will finally pay in "Total cobrado". Here is the important thing: When the checkbox image is checked (the blue one - already checked in the image), I create a "payment" entity per installment. Every "payment" object is stored in a list. If I uncheck it, I can change the value and the same thing is done. Obviously, I´m clearing the list before doing a list.Clear();. Then, one the checkbox checked, I can press "Aceptar" (accept). There I add to the database every "payment"(PAGO) in the list. After that, I save all changes.
foreach (Pago p in Lista_nuevos_pagos)
{
db.AddToPago(p);
}
try
{
db.SaveChanges();
this.Close();
}
My problem, is that it´s not only adding those "payments" in the list but the other "payments" entities that were in the list before clearing it. I reach the conclusion that when I clear the list, the objects remains in the object context. I thought that if the entity is not in the database, I have to Add it to the entity in the object context as I did with pago (db.AddToPago(p);).
I wanted to ask you guys how can I solve this issues. I solved it now doing this:
private void cancelarCuotaToolStripMenuItem_Click(object sender, EventArgs e)
{
Sistema_financiero_Entities db = new Sistema_financiero_Entities();
frm_Cancelacion_Cuota Form1 = new frm_Cancelacion_Cuota();
Form1.db = db;
Form1.Show();
}
Instead of creating just one global db for all forms, I create one in the Main Menu for every form. Then, in that form closed event, I dispose that object context.
Then, when i check the checkbox image, before creating the "payments", I delete every "Pago" entity from the object context:
foreach (Pago p in Lista_nuevos_pagos)
{
db.DeleteObject(p);
}
Lista_nuevos_pagos.Clear();
Doing this works correctly, but I´m still having trouble with some other created entities (Installments) that are not deleted when I clear a list. I think I´m doing it wrongly, thats why I need some direction to use EF correctly. I really need to get this done really soon, I don´t have too much time to read EF tutorials.
Just in case, this is how I create every "Pago" (payment)
Pago p = new Pago();
p.desc_aumento_intereses = nudwb1.Value;
p.desc_aumento_punitorios = nudwb2.Value;
p.desc_aumento_gastos = nudwb3.Value;
p.desc_aumento_comision = nudwb4.Value;
p.cotizacion = ntxt_Cotizacion.Value;
p.fecha_hora = fecha_hora;
Cuota c = new Cuota();
string name = tbx.Name.Substring(tbx.Name.IndexOf("-") + 1);
int nro_cuota = Convert.ToInt32(name);
c = Lista_cuotas_cobrar.Where(x => x.num_cuota == nro_cuota).First();
p.Cuota.Add(c);
Thank you for reading, I know this is a lot of info. Hope some guide soon..
I guess that you have references to those object in your Lista_nuevos_pagos list. This is why they will be duplicated.

InvalidOperationException when enumerating through a list of results to provide multiple textbox's with text

I have a data entry form with many textbox's and some dropdowns for the user to input data. When the user selects a "Location" from the dropdownlist, they can click a button on top of the form to view a popup with more details according to that location. The data successfully automates when the popup loads but when the user tries to close the popup and continue with the main form, an unhandled exception occurs for the system.InvalidOperationException. The error specifically occurs because "The collection I'm Enumerating through has been changed". Although I'm not changing anything I guess something behind the scenes is happening, here is my code to retreive the data:
string postalCode;
string phone1;
string phone2;
string supervisor;
var ObjectContext = new ObjectContext();
var qry = (from i in ObjectContext.TableLocation
where i.LocationName == LocationValue
select i).ToList();
foreach (var data in qry)
{
postalCode = data.postalCode;
phone1 = data.phoneNumber1;
phone2 = data.phoneNumber2;
supervisor = data.supervisor
}
txtPostalCode.Text = postalCode;
txtPhone1.Text = Phone1;
txtPhone2.Text = Phone2;
txtSupervisor.Text = supervisor;
The LocationValue is linked to a Public variable that the parent form fills with whatever is selected in the location dropdownlist:
public string CountyValue
{
get { return txtCountyName.Text; }
set { txtCountyName.Text = value; }
}
Is there a better way to enumerate through this list of values and supply them to textbox.text? I have tried everything to fix this error.
EDIT
Also all my database columns are Varchars so there was no need to convert data types.
And I only get this error when I deploy my app via ClickOnce to clients PC.
From what I see, that Location is a single value, and there is no need to create a List. So that means you can avoid iterating over a list, and do this instead:
var ObjectContext = new ObjectContext();
var details = ObjectContext.TableLocation
.First(x => x.LocationName == LocationValue)
.Select(x =>
new {
PostalCode = x.postalCode,
Phone1 = x.phoneNumber1,
Phone2 = x.phoneNumber2,
Supervisor = x.supervisor
});
txtPostalCode.Text = details.PostalCode;
txtPhone1.Text = details.Phone1;
txtPhone2.Text = details.Phone2;
txtSupervisor.Text = details.Supervisor;
ADDED:
Also check this MSDN Reference, according to it, there are several scenarios where ShowDialog() could throw an InvalidOperationException, that are unrelated to LINQ-to-SQL or EF.
ADDED: From that MSDN article it says this:
When a form is displayed as a modal dialog box, clicking the Close
button (the button with an X at the upper-right corner of the form)
causes the form to be hidden and the DialogResult property to be set
to DialogResult.Cancel. Unlike non-modal forms, the Close method is
not called by the .NET Framework when the user clicks the close form
button of a dialog box or sets the value of the DialogResult property.
Instead the form is hidden and can be shown again without creating a
new instance of the dialog box. Because a form displayed as a dialog
box is hidden instead of closed, you must call the Dispose method of
the form when the form is no longer needed by your application.
memoize items(.ToArray()) before collection iterations
var qry = (from i in ObjectContext.TableLocation.ToArray()
where i.LocationName == LocationValue
select i).ToList();

Open row in DataGridView to Edit, DGV has SQL Backend in C#

Ok, so I've got a DataGridView for some Project Management Software, I want to be able to double click a row it opens a form and fills in the fields and combo boxes with the relvant data, so that I can change what ever needs to be changed. In this case the most important thing is changing the Status. As in the Project Management Software I have bugs and tasks, that have a status and that needs to be changed at some point obviously when the Bug has been resolved or the task completed.
So I will show how the DGV is getting data for the bugs section:
public void ViewAllBugs()
{
DataClasses1DataContext dc = new DataClasses1DataContext(sConnectionString);
var Bugs =
(from data in dc.GetTable<Bug>()
from user in dc.GetTable<User>()
from projects in dc.GetTable<Project>()
from priority in dc.GetTable<Priority>()
from status in dc.GetTable<Status>()
from category in dc.GetTable<Category>()
where data.UserID == user.UserID && data.ProjectID == projects.ProjectID && data.PriorityID == priority.PriorityID && status.StatusID == data.StatusID && category.CategoryID == data.CategoryID
select new
{
Bug = data.Name,
Project = projects.Name,
Priority = priority.Priority1,
Status = status.Name,
Category = category.Category1,
Assigned_To = user.Username,
Start = data.Opened_Date,
End = data.Closed_Date,
Description = data.Description,
});
dgvBugs.DataSource = Bugs;
}
As you can see my approach is fairly simplistic. I will also show the code for creating a new Bug. The ComboBoxes have been DataBound using the GUI option as opposed to me actually hardcoding it in.
public void CreateBug()
{
string sBugName = txtName.Text;
string sDescription = txtDescription.Text;
int iProject = Convert.ToInt32(cbProject.SelectedValue);
int iPriority = Convert.ToInt32(cbPriority.SelectedValue);
int iStatus = Convert.ToInt32(cbStatus.SelectedValue);
int iCategory = Convert.ToInt32(cbCategory.SelectedValue);
int iUser = Convert.ToInt32(cbAssigned.SelectedValue);
DateTime dtOpen = dateOpened.Value;
DateTime dtClosed = dateClosed.Value;
DataClasses1DataContext dc = new DataClasses1DataContext(sConnectionString);
Table<Bug> tBug = dc.GetTable<Bug>();
Bug nBug = new Bug();
nBug.Name = sBugName;
nBug.ProjectID = iProject;
nBug.PriorityID = iPriority;
nBug.StatusID = iStatus;
nBug.CategoryID = iCategory;
nBug.UserID = iUser;
nBug.Opened_Date = dtOpen;
nBug.Closed_Date = dtClosed;
nBug.Description = sDescription;
tBug.InsertOnSubmit(nBug);
dc.SubmitChanges();
this.Close();
}
Now I know there a few bits of sloppy code in there, but i am new to C# and i personally think im not doing too badly. But if you can see anything that should be improved please feel free to mentiond that as well.
My DataClasses1DataContext is organised so that there are Associations between the UserID, PriorityID, CategoryID, UserID, ProjectID and StatusID.
So basically i want to be able to click on Bug1 (an example bug) in the DGV and that needs to open up a form (that i havnt made yet) and i need to be able to edit every piece of data in that Bug.
If you require anymore information I'll be more than happy to supply it!
Thanks,
Brendan
The DataGridView will have a CellDoubleClick event. You can use this to respond to the UI, however don't forget you need to ignore invalid row or column indexes (-1s). I believe these crop in if people click on the row/column headers.
You the event args to get the row index and then the row. From the row you can access the DataBoundItem property. This should be directly cast-able to Bug.
You can then pass this reference to your new form, bind it to the controls and make your edits. Because its a reference, it will update the data on the other form too - though you may have to refresh your grid.
You can then decide whether to update the database on the details form or the master form (I'd personally make saving part of the grid form).

Categories

Resources