I have a class called Estimate and it has the following field and property:
private IList<RouteInformation> _routeMatrix;
public virtual IList<RouteInformation> RouteMatrix
{
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
var routeMatrix = _routeMatrix.ToList();
routeMatrix =
routeMatrix.OrderBy(tm => tm.Level.LevelType).ThenBy(tm => tm.Level.LevelValue).ToList();
return routeMatrix;
}
else return _routeMatrix;
}
set { _routeMatrix = value; }
}
So, in the getter method, I am just sorting the _routeMatrix by Level Type and then by Level Value and returning the sorted list.
In one of my programs, I have the following code:
public void SaveApprovers(string[] approvers)
{
int i = 1;
foreach (var approver in approvers)
{
var role = Repository.Get<Role>(long.Parse(approver));
var level = new Models.Level
{
LevelType = LevelType.Approver,
LevelValue = (LevelValue)i,
Role = role
};
Repository.Save(level);
var routeInformation = new Models.RouteInformation
{
Level = level,
RouteObjectType = RouteObjectType.Estimate,
RouteObjectId = _estimate.Id
};
Repository.Save(routeInformation);
_estimate.RouteMatrix.Add(routeInformation); // <--- The problem is here
Repository.Save(_estimate);
i++;
}
}
The problem is that, if there are multiple approvers (i.e: the length of the approvers array is greater than 1, only the first routeInformation is added in the RouteMatrix. I don't know what happen to the rest of them, but the Add method doesn't give any error.
Earlier, RouteMatrix was a public field. This problem started occuring after I made it private and encapsulated it in a public property.
Your get member returns a different list, you add to that temporary list.
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
var routeMatrix = _routeMatrix.ToList(); // ToList creates a _copy_ of the list
...
return routeMatrix;
}
else return _routeMatrix;
}
.....
_estimate.RouteMatrix.Add(routeInformation); // add to the result of ToList()
I think the moral here is not to make getters too complicated. The sorting is wasted effort anyway when you just want to Add().
Also, bad things will happen when _routeMatrix == null. That may not happen but then the if (_routeMatrix != null && ...) part is misleading noise.
When you are applying ToList() then completely new list is created, which is not related to original _routeMatrix list. Well, they share same elements, but when you add or remove elements from one of lists, it does not affect second list.
From MSDN:
You can append this method to your query in order to obtain a cached
copy of the query results.
So, you have cached copy of your _routeMatrix which you are successfully modifying.
To solve this issue you can return IEnumerable instead of IList (to disable collection modifications outside of estimation class), and create AddRouteInformation method to estimation class which will add route information to _routeMatrix. Use that method to add new items:
_estimate.AddRouteInformation(routeInformation);
Repository.Save(_estimate);
The problem is that you're not actually modifying _routeMatrix, you're modifying a copy of it. Don't issue the ToList on _routeMatrix, just sort it. Change the get to this:
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
_routeMatrix =
_routeMatrix.OrderBy(tm => tm.Level.LevelType).ThenBy(tm => tm.Level.LevelValue).ToList();
return _routeMatrix;
}
else return _routeMatrix;
}
Related
I want to iterate over a custom list i.e. defined as:
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
IEnumerator<CurrentCluster> _clusIterate = _curClusters.GetEnumerator();
while (_clusIterate.MoveNext())
{
// Error_01: Cannot implicitly convert CurrentCluster to Cluster
Cluster _curClus = _clusIterate.Current; // Cluster is base class while
// CurrentCluster is derived class
// Error_02: Does not contain a definition for GetClusterSize()
if (_curClus.GetClusterSize() == 0)
{
// Error_03: Remove(char) has some invalid arguments.
_clusIterate.ToString().ToList().Remove(_curClus);
}
}
while method GetClusterSize() is defined in class Cluster.cs as:
public int GetClusterSize()
{
return _clusterObjects.Count;
// _clusterObjects is a defined in this class as:
// List<EvoObject> _clusterObjects = new List<EvoObject>();
}
If the size of specific cluster is equal to zero in that cluster list (i.e. _curClusters then to remove that cluster from the list.
How can we iterate over a custom list and remove item from list conditionally?
How about just using List RemoveAll method and doing this?
_curClusters.RemoveAll(_curClus=>_curClus.GetClusterSize() == 0);
You should be able to use a for loop - you have to work backwards because otherwise you would be moving the elements and some would get skipped.
for (int n=_curClusters.Count; n>=0; n--)
{
if (_curClusters[n].GetClusterSize()==0)
{
_curClusters.RemoveAt(n);
}
}
Removing items from a collection using iteration is both advanced and obsolete technique. Use LINQ instead:
_curClusters = _curClusters.Where(c => c.GetClusterSize() > 0).ToList();
Now curClusters contains just "sized clusters", whatever that means.
If you insist to do it through iterations this is the way:
The catch is that you MUST NOT change a collection while iterating over its items. Instead, you can iterate and determine if an item needs to be deleted and mark it somehow - for instance you can add it to another list which contains only items to be deleted. After the first iteration over the original collection, start second one and remove the items from the original, like so:
var toBeRemoved = new List<CurrentCluster>();
foreach (var suspiciousCluster in _curCluseters)
{
if(suspiciousCluster.GetClusterSize() == 0)
{
toBeRemoved.Add(suspiciousCluster);
}
}
foreach (var voidCluser in toBeRemoved)
{
_curCluster.Remove(voidCluster);
}
Again, _curClusters contains just "sized clusters", whatever this might mean.
However I highly recommend the first approach.
I did not understand why you are going with that complexity ... simply you can achieve the goal by below code
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
_curClusters.RemoveAll(i => i.GetClusterSize()== 0);
//OR
for (int i = 0; i < _curClusters.Count; )
{
//If you have some more logical checking with CurrentCluster
//before remove
if (_curClusters[i].GetClusterSize()== 0)
{
_curClusters.Remove(_curClusters[i]);
continue;
}
i++;
}
I have an Asp.Net MVC 5 website and I'm using Entity Framework code first to access its database. I have a Restaurants table and I want to let users search these with a lot of parameters. Here's what I have so far:
public void FilterModel(ref IQueryable<Restaurant> model)
{
if (!string.IsNullOrWhiteSpace(RestaurantName))
{
model = model.Where(r => r.Name.ToUpper().Contains(RestaurantName));
}
if (Recommended)
{
model = model.Where(r => r.SearchSponsor);
}
//...
}
Basically I look for each property and add another Where to the chain if it's not empty.
After that, I want to group the result based on some criteria. I'm doing this right now:
private static IQueryable<Restaurant> GroupResults(IQueryable<Restaurant> model)
{
var groups = model.GroupBy(r => r.Active);
var list = new List<IGrouping<bool, Restaurant>>();
foreach (var group in groups)
{
list.Add(group);
}
if (list.Count < 1)
{
SortModel(ref model);
return model;
}
IQueryable<Restaurant> joined, actives, inactives;
if (list[0].FirstOrDefault().Active)
{
actives = list[0].AsQueryable();
inactives = list.Count == 2 ? list[1].AsQueryable() : null;
}
else
{
actives = list.Count == 2 ? list[1].AsQueryable() : null;
inactives = list[0].AsQueryable();
}
if (actives != null)
{
//....
}
if (inactives != null)
{
SortModel(ref inactives);
}
if (actives == null || inactives == null)
{
return actives ?? inactives;
}
joined = actives.Union(inactives).AsQueryable();
return joined;
}
This works but it's got a lot of complications which I rather not talk about for the sake of keeping this question small.
I was wondering if this is the right and efficient way to do it. It seems kind of "dirty"! Lots of ifs and Wheres. Stored procedures, inverted indices, etc. This is my first "big" project and I want to learn from your experience to do this the "right" way.
Looking at the GroupResults Method I get a little confused about what you are doing. It seems the intention is to receive an arbitrary list of restuarants and return an ordered list of restaurants ordered by Active and some other criteria.
If thats true you may just do something like this and your job's done:
model.OrderBy(x => x.Active).ThenBy(x => Name);
If SortModel is somehow more sophisticated you may either add a comparer to the statement or stick with your current solution but change it to this:
if (model == null || !model.Any())
{
return model;
}
var active = model.Where(x=>x.Active);
var inactives = model.Where(x=>!x.Active);
// if (inactives == null) //not needed as where always return at least an empty list. Mabye check for inactive.Any()
SortModel(ref inactives); //You may also remove the ref as it's an reference anyway
joined = actives.Union(inactives).AsQueryable();
return joined;
Regarding the way you are handling your searching, I think it is simple, easy to read and understand, and it works. New team members will be able to look at that code and know immediately what it is doing and how it works. I think that is a pretty good indication that your approach is sound.
I have two lists. When I assign List1 to List2 and I update List1, List2 automatically updates too. List2 shouldn't update. Why does this happen?
Here is my code:
public List<TrialBalance> TBal { get; set; }
public List<TrialBalance> PrevTBal { get; private set; }
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal;
}
for (int x = 0; x < this.TBal.Count; x++)
{
this.TBal[x].Balance = this.TBal[x].Balance + adjustments;
}
You are only assigning the references, not creating a copy of either the list or the items in the list.
You should create a new list and add all the items to it.
this.PrevTBal = new List<TrialBalance>(this.TBal.Select(b => clone(b));
When you assign a List<T>, you're copying the handle to the actual list in memory, which means the same list instance is being referenced by both variables.
In order to avoid this, you'd need to clone the list itself. In this case, that likely means needing to do two things - first, make a way to clone TrialBalance, then clone the list too:
// This assumes a TrialBalance.Clone() method which returns a new TrialBalance copy
this.PrevTBal = this.TBal.Select(tb => tb.Clone()).ToList();
Replace
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal;
}
by:
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal.ToList();
}
This way you're actually creating a copy of it instead of just refering it.
I'm writing a WinForms app that contains a simple object like this:
public class MyObject : INotifyPropertyChanged // for two-way data binding
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
private int _IndexValue;
public int IndexValue
{
get { return Value; }
set
{
if (value != Value)
{
Value = value;
RaisePropertyChanged();
}
}
}
private string _StringValue;
public string StringValue
{
get { return _StringValue; }
set
{
if (value != _StringValue)
{
_StringValue = value;
_Modified = true;
RaisePropertyChanged();
}
}
}
private bool _Modified;
public bool Modified
{
get { return _Modified; }
set
{
if (value != _Modified)
{
_Modified = value;
RaisePropertyChanged();
}
}
}
public MyObject(int indexValue)
{
IndexValue = indexValue;
StringValue = string.Empty;
Modified = false;
}
}
I have a BindingList that will contain a fixed number (100,000) of my objects as well as a BindingSource. Both of those are defined like this:
BindingList<MyObject> myListOfObjects = new BindingList<MyObject>();
BindingSource bindingSourceForObjects = new BindingSource();
bindingSourceForObjects .DataSource = myListOfObjects;
Finally, I have my DataGridView control. It has single column ("STRINGVALUECOLUMN") which displays the StringValue property for my objects and it is bound to the BindingSource that I just mentioned:
dataGridViewMyObjects.DataSource = bindingSourceForObjects;
When my application starts, I add 100,000 objects to myListOfObjects. Since I only have one column in my DGV and the property that it displays is initialized to string.Empty, I basically have a DGV that contains 100,000 "blank" rows. At this point, my user can begin editing the rows to enter strings. They don't have to edit them in any order so they might put one string in the first row, the next string in row 17, the next string in row 24581, etc. Sometimes, my users will want to import strings from text file. Since I have a fixed number of objects (100,000) and there may or may not be some existing strings already entered, I have a few checks to perform during the import process before I add a new string. In the code below, I've removed those checks but they don't seem to impact the performance of my application. However, if I import tens of thousands of strings using the code below, it's very slow (like 4 or 5 minutes to import 50k lines). I have narrowed it down to something in this block of code:
// this code is inside the loop that reads each line from a file...
// does this string already exist?
int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);
if (count > 0)
{
Debug.WriteLine("String already exists!"); // don't insert strings that already exist
}
else
{
// find the first object in myListOfObjects that has a .StringValue property == string.Empty and then update it with the string read from the file
MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);
myObject.StringValue = stringFromFile;
}
It's my understanding that I need two-way binding so I can update the underlying data and have it reflect in the DGV control but I've also read that INotifyPropertyChanged can be slow sometimes. Has anyone ever run into this problem before? If so, how did you solve it?
-- UPDATE --
Just for testing purposes, I replaced:
// does this string already exist?
int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);
if (count > 0)
{
Debug.WriteLine("String already exists!"); // don't insert strings that already exist
}
else
{
// find the first object in myListOfObjects that has a .StringValue property == string.Empty and then update it with the string read from the file
MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);
myObject.StringValue = stringFromFile;
}
with a for loop containing:
myListOfObjects[counter].StringValue = "some random string";
This is extremely fast even with 100,000 objects. However, I've now lost the ability to 1) check to see if the string that I read from the file is already assigned to an object in the list before I assign it and 2) find the first available object in the list whose StringValue property == string.Empty and then update that value accordingly. So it seems that:
int count = myListOfObjects.Count(i => i.StringValue == stringFromFile);
and
MyObject myObject = myListOfObjects.FirstOrDefault(i => i.StringValue == string.Empty);
...are the source of my performance problems. Is there a faster, more efficient way to perform these two operations against my BindingList?
The thing about Linq is that its really just standard loops, optimized of course, but still regular old loops, back in the back code.
One thing that may speed your code up is this:
myListOfObjects.Any(i => i.StringValue.Equals(stringFromFile));
this returns a simple boolean, Does X exist. It early exits so it wont scan the entire collection if it doesn't have to. .Count() requires not only scanning the whole thing but also keeping a running count.
Another thing to point out, since you are using FirstOrDefault, that indicates that the result could be null. Make sure you have a null-check on myobject before trying to use it.
Finally, as suggested by Mr Saunders, check the event stack and make sure there isn't more code running than you think there is. This is a danger in operations like this. You might need to borrow some code from the initialization engine and use this.SuspendLayout() and this.ResumeLayout()
The problem may be that when you update the underlying data, events fire to cause the grid to update. Lots of data changing == lots of updates.
It's been a long time since I've done much with Windows Forms, but check out the SuspendLayout method.
I am trying to do this:
foreach (Settings sets in MySets)
{
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
sets = ss.getSettings();
}
}
return;
}
}
But since the sets spawned variable is readonly, I cant override it.
I would also like to do something like this
foreach (Settings sets in MySets)
{
if(sets.pName == someName)
sets.RemoveFromList();
}
How can I accomplish this? Lists have a very nice Add() method, but they forgot the rest :(
You can use:
MySets.RemoveAll(sets => sets.pName == someName);
to remove all the items that satisfy a specific condition.
If you want to grab all the items satisfying a condition without touching the original list, you can try:
List<Settings> selectedItems = MySets.FindAll(sets => sets.pName == someName);
foreach loops don't work here as trying to change the underlying list will cause an exception in the next iteration of the loop. Of course, you can use a for loop and manually index the list. However, you should be very careful not to miss any items in the process of removing an item from the list (since the index of all the following items will get decremented if an element is removed):
for (int i = 0; i < MySets.Count; ++i) {
var sets = MySets[i]; // simulate `foreach` current variable
// The rest of the code will be pretty much unchanged.
// Now, you can set `MySets[i]` to a new object if you wish so:
// MySets[i] = new Settings();
//
// If you need to remove the item from a list and need to continue processing
// the next item: (decrementing the index var is important here)
// MySets.RemoveAt(i--);
// continue;
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
// Assigning to `sets` is not useful. Directly modify the list:
MySets[i] = ss.getSettings();
}
}
return;
}
}
You can't do it in a 'regular' for loop?