I've written an extension method to add items to an (EF) EntityCollection. I got an interesting error, saying that my collection of IEnumerable ("items") had been modified, after the first loop in the foreach. When I turn items into items.ToList() (like in the code below), it works fine.
I completely understand that doing ToList() will produce a copy of the items on which the foreach will then operate.
What I do NOT understand is what is modifying the IEnumerable when I'm doing a foreach over it.
Update: Somehow, it seems the items variable is the same as the collections variable?
Update 2: I think collection and entity may be affected by EF's entity tracking, but I still fail to understand why
Usage:
ssp.ServiceAreas.ReplaceCollection(model.ServiceAreas);
Here's my extension method:
public static void AddOrUpdate<TEntity>(this EntityCollection<TEntity> collection, IEnumerable<TEntity> items)
where TEntity : EntityObject, IProjectIdentity<int>, new()
{
foreach (var item in items.ToList())
collection.AddOrUpdate(item);
}
public static void AddOrUpdate<TEntity>(this EntityCollection<TEntity> collection, TEntity item)
where TEntity : EntityObject, IProjectIdentity<int>, new()
{
if (item.ID > 0 && collection.Any(c => c.ID == item.ID))
collection.Remove(collection.First(c => c.ID == item.ID));
// For now, the Remove NEVER gets hit
collection.Add(item);
}
collection.Remove(collection.First(c => c.ID == item.ID));
you are removing in the collection you are iterating.
I created the following sample code:
internal class Program
{
private static void Main(string[] args)
{
var one = new List<string> {"Adam", "John"};
var two = new List<string> {"Adam", "Houldsworth"};
one.AddOrUpdate(two);
Console.Read();
}
}
static class Extensions
{
public static void AddOrUpdate(this IList<string> collection, IEnumerable<string> items)
{
foreach (var item in items.ToList())
collection.AddOrUpdate2(item);
}
public static void AddOrUpdate2(this IList<string> collection, string item)
{
if (collection.Any(c => c == item))
collection.Remove(collection.First(c => c == item));
collection.Add(item);
}
}
This works as you would expect, there are no errors. So in essence, none of the lines are causing issues.
What will cause issues is if you call the list on itself:
one.AddOrUpdate(one);
So from what I can see, you must be calling this extension method with the same collection as both arguments.
If you are, then both Remove or Add will mutate the collection and cause this exception.
Perhaps EntityCollection doesn't like when someone takes over it's elements? So, when you Add to collection, item gets Removed from items.
Or it could be that items == collection
It could be that the first item is always a new item, and thus by default the ID is set to some initial value. It will then be added to the collection, letting the EntityFramework generate a new ID and assign it to the first added item.
It then might be that the EntityCollection thinks it has changed, because it uses the ID to sort or do something else internally. And thus the foreach operation (which is probably using the same list) throws the exception. That's also why the test-case proved by Adam Houldsworth does not give the issue!
EntityCollection<Customer> customers = new EntityCollection<Customer>();
Customer newCustomer = new Customer() {ID = 0};
customers.Add(newCustomer);
customers.AddOrUpdate(customers);
Related
I'm being given a collection of Lazy items. I then want to forcibly 'create' them all in one go.
void Test(IEnumerable<Lazy<MailMessage>> items){
}
Normally with a Lazy item, the contained object won't be created until one of it's member's is accessed.
Seeing as there is no ForceCreate() method (or similar), I am forced to do the following:
var createdItems = items.Where(a => a.Value != null && a.Value.ToString() != null).Select(a => a.Value);
Which is using ToString() to force created each item.
Is there a neater way to forcibly create all items?
To get the list of all the lazily initialized values:
var created = items.Select(c => c.Value).ToList();
You need two things to create all the lazy items, you need to enumerate all items (but not necessarily keep them), and you need to use the Value property to cause the item to be created.
items.All(x => x.Value != null);
The All method needs to look at all values to determine the result, so that will cause all items to be enumerated (whatever the actual type of the collection might be), and using the Value property on each item will cause it to create its object. (The != null part is just to make a value that the All method is comfortable with.)
Seeing as there is no ForceCreate() method (or similar)
You can always create a ForceCreate() extension method on Lazy<T> for this:
public static class LazyExtensions
{
public static Lazy<T> ForceCreate<T>(this Lazy<T> lazy)
{
if (lazy == null) throw new ArgumentNullException(nameof(lazy));
_ = lazy.Value;
return lazy;
}
}
...accompanied by a ForEach extension method on IEnumerable<T>:
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));
if (action == null) throw new ArgumentNullException(nameof(action));
foreach (var item in enumerable)
{
action(item);
}
}
}
By combining these two extension methods you can then forcibly create them all in one go:
items.ForEach(x => x.ForceCreate());
In one of my projects I'm trying to remove an item from a list where the id is equal to the given id.
I have a BindingList<T> called UserList.
Lists have all the method RemoveAll().
Since I have a BindingList<T>, I use it like that:
UserList.ToList().RemoveAll(x => x.id == ID )
However, my list contains the same number of items as before.
Why it's not working?
It's not working because you are working on a copy of the list which you created by calling ToList().
BindingList<T> doesn't support RemoveAll(): it's a List<T> feature only, so:
IReadOnlyList<User> usersToRemove = UserList.Where(x => (x.id == ID)).
ToList();
foreach (User user in usersToRemove)
{
UserList.Remove(user);
}
We're calling ToList() here because otherwise we'll enumerate a collection while modifying it.
You could try:
UserList = UserList.Where(x => x.id == ID).ToList();
If you use RemoveAll() inside a generic class that you intend to be used to hold a collection of any type object, like this:
public class SomeClass<T>
{
internal List<T> InternalList;
public SomeClass() { InternalList = new List<T>(); }
public void RemoveAll(T theValue)
{
// this will work
InternalList.RemoveAll(x =< x.Equals(theValue));
// the usual form of Lambda Predicate
//for RemoveAll will not compile
// error: Cannot apply operator '==' to operands of Type 'T' and 'T'
// InternalList.RemoveAll(x => x == theValue);
}
}
This content is taken from here.
If there is just one item as unique ID inside bindinglist, the below simple code can work.
UserList.Remove(UserList.First(x=>x.id==ID));
sometimes, i just feel dumb...
i have a simple class:
public class myClass
{
public long Id { get; set; }
public long ParentChannelId { get; set; }
}
and i have a list that contains the class:
List<myClass> myItems = new List<myClass>
further down the code, i feed the list with classes.
now, i want to delete an item from the list.
but, since an item can have children and grandchilds etc...
i want to delete everything related..
was thinking of something like:
(pseudo code )
var List<myClass> itemsToDelete = myItems.Where(i => i.Ancestors.Contains(myItemId));
but i dont really have the brains atm to know how to write it exactly... :\
i do have the .Ancestors function...
just need help with the lambda linq
public List<Channel> Ancestors
{
get
{
List<MyCms.Content.Channels.Channel> result = new List<MyCms.Content.Channels.Channel>();
Channel channel = this;
while (channel != null)
{
result.Add(channel);
channel = myChannels.Where(c => c.ParentChannelId == this.Id).First();
}
result.Reverse();
return result;
}
}
EDIT: guess i did not explain myself as i should...
i have all the properties like ancestors, children parent etc...
i want to select all the classes that might contain the specific class...
I've re-read your question, especially the last part where you said you already have .Ancestors, and now it makes more sense.
Do this to get your list of items to delete:
List<MyClass> itemsToDelete = myItems
.Where(i => i.Id == myItemId)
.SelectMany(i => i.Ancestors)
.Concat(myItems) // Want to delete these too, not just the ancestors
.ToList()
;
Then you can foreach through the result, and remove them from the original list.
I'd suggest keeping these in a Dictionary<int, MyClass> or a HashSet<MyClass> instead of a list, since removal will be way faster.
For a HashSet, you'll have to implement Equals and GetHashCode, or create an IEqualityComparer<MyClass> implementation to provide those methods.
Before Edit:
I wouldn't write my code this way. I'd simply create a Dictionary<int, MyClass> instead of a list. It will do a lookup way faster than anything involving ancestors/tree traversal.
But here is how to accomplish what you're trying to accomplish:
If you're using Linq to Objects (as opposed to Linq to SQL or Linq to Entities), make a property called Parent on MyClass, of the correct type, instead of trying to link them by Id.
Then you can make an Ancestors property fairly easily:
public IEnumerable<MyClass> Ancestors
{
get
{
MyClass current = this;
while(current != null)
{
current = current.Parent;
yield return current;
}
}
}
If you can't edit the class, make an extension method called GetAncestors.
Then you can use something very similar to the code you wrote in your question:
List<MyClass> itemsToDelete = myItems
.Where(i => i.Ancestors.Any(a => a.Id == myItemId))
.ToList();
Linq to Entities
If you are using Linq to Entities, create a navigation property of the type MyClass to navigate to the parent, and do the same thing. Note that this might cause re-queries. Not sure the Linq can or would get translated into a hierarchical query.
This is how I would do it using a hashset and RemoveAll method.
var itemsToDelete = new HashSet<myClass>(otherItems);
myItems.RemoveAll(i => itemsToDelete.Contains(i));
RemoveAll Method
http://msdn.microsoft.com/en-us/library/wdka673a.aspx
In a C# class I have a list and two different getters for the list:
private List<A> a;
public List<A> EveryA
{
get
{
if (a == null) a = new List<A>();
return a;
}
}
public List<A> FilteredA
{
get
{
return EveryA.FindAll(a => a.IsInFilter);
}
}
Now my question is: how about the syntax FilteredA.Add(this);?
It compiles and runs but it cannot add any item to any list.
Should a better compiler have to notify the (small) problem?
They are not the same list. This is not something the compiler can check for you, since the compiler can't really read your mind. Check the documentation for List<T>.FindAll
The result is a list, but it isn't the same list (how could it be? your original list isn't filtered!).
You should be able to add items to the list returned by FilteredA, except they won't show up in a.
I suggest you use LINQs Where instead, returning an IEnumerable<T>. That way, it is obvious that the result of FilteredA shouldn't be changed, only iterated over:
public IEnumerable<A> FilteredA
{
get { return EveryA.Where(a => a.IsInFilter); }
}
No. Why should it notify you about this? It is completely ok.
FilteredA doesn't return a but a new instance of a List<A>.
FilteredA.Add(this); adds this to this new instance.
See this code:
var filteredA = FilteredA;
int count1 = filteredA.Count;
filteredA.Add(this);
int count2 = filteredA.Count;
Assert.AreEqual(count1 + 1, count2);
This shows, that the new item IS added to the list. But to that new instance that is independent of the list inside your class.
FindAll returns a new list. You're adding the new item to the new list but not retaining a reference to the new list, I suppose. The semantics would be clearer if the filtered list came from a method rather than a property.
public List<A> FilteredA returns some output of the FindAll method, as a List<A>. This will not be the same object as EveryA so when it goes out of scope your addition will be lost.
It's not really a compiler issue - since the code is valid it will compile just fine. The problem is more on a code quality level. To catch something like this, you could use a tool like FxCop to analyze your code.
Both methods can be seen as query methods. You should not expose the result as a List, but rather an IEnumerable or A[]. If you want to add an item to the list, do so with an Add method.
private List<A> items = new List<A>();
public IEnumerable<A> EveryA
{
get { return items; }
}
public IEnumerable<A> FilteredA
{
get { return items.Where(item => item.IsInFilter); }
}
public void AddItem(A item)
{
items.Add(item);
}
I just can't figure out why the item in my filtered list is not found. I have simplified the example to show it. I have a class Item...
public class Item
{
public Item(string name)
{
Name = name;
}
public string Name
{
get; set;
}
public override string ToString()
{
return Name;
}
}
... and a class 'Items' which should filter the items and check if the first item is still in the list...
public class Items
{
private IEnumerable<Item> _items;
public Items(IEnumerable<Item> items)
{
_items = items;
}
public List<Item> Filter(string word)
{
var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));
Console.WriteLine("found: " + ret.Contains(_items.First()));
// found: false
return ret;
}
}
The executing code looks like this:
static void Main(string[] args)
{
string[] itemNames = new string[] { "a", "b", "c" };
Items list = new Items(itemNames.Select(x => new Item(x)));
list.Filter("a");
Console.ReadLine();
}
Now, if I execute the program, the Console.WriteLine outputs that the item is not found. But why?
If I change the first line in the constructor to
_items = items.ToList()
then, it can find it. If I undo that line and call ToList() later in the Filter-method, it also cannot find the item?!
public class Items
{
private IEnumerable<Item> _items;
public Items(IEnumerable<Item> items)
{
_items = items;
}
public List<Item> FilteredItems
{
get; set;
}
public List<Item> Filter(string word)
{
var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));
_items = _items.ToList();
Console.WriteLine("found: " + ret.Contains(_items.First()));
// found: false
return ret;
}
}
Why is there a difference where and when the lambda expression is executed and why isn't the item found any more? I don't get it!
The reason is deferred execution.
You intialize the _items field to
itemNames.Select(x => new Item(x));
This is a query, not the answer to that query. This query is executed every time you iterate over _items.
So in this line of your Filter method:
var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));
the source array is enumerated and a new Item(x) created for each string. These items are stored in your list ret.
When you call Contains(_items.First()) after that, First() again executes the query in _items, creating new Item instances for each source string.
Since Item's Equals method is probably not overridden and performs a simple reference equality check, the first Item returned from the second iteration is a different instance of Item than the one in your list.
Let's remove extra code to see the problem:
var itemNames = new [] { "a", "b", "c" };
var items1 = itemNames.Select(x => new Item(x));
var surprise = items1.Contains(items1.First()); // False
The collection items1 appears not to contain its initial element! (demo)
Adding ToList() fixes the problem:
var items2 = itemNames.Select(x => new Item(x)).ToList();
var noSurprise = items2.Contains(items2.First()); // True
The reason why you see different results with and without ToList() is that (1) items1 is evaluated lazily, and (2) your Item class does not implement Equals/GetHashCode. Using ToList() makes default equality work; implementing custom equality check would fix the problem for multiple enumeration.
The main lesson from this exercise is that storing IEnumerable<T> that is passed to your constructor is dangerous. This is only one of the reasons; other reasons include multiple enumeration and possible modification of the sequence after your code has validated its input. You should call ToList or ToArray on sequences passed into constructors to avoid these problems:
public Items(IEnumerable<Item> items) {
_items = items.ToList();
}
There are two problems in your code.
First problem is that you are initializing a new item every time. That is you don't store the actual items here when you write.
IEnumerable<Item> items = itemNames.Select(x => new Item(x));
The execution of Select is deferred. i.e every time you call .ToList() a new set of Items is created using itemNames as source.
Second problem is that you are comparing items by reference here.
Console.WriteLine("found: " + ret.Contains(_items.First()));
When you use ToList you store items in list and the references remains same so you will find item with reference.
When you don't use ToList the references are not same any more. because everytime a new Item is created. you cant find your item with different reference.