代码之家  ›  专栏  ›  技术社区  ›  klas mack

为什么我不能用EF core保存相关数据的更新?[副本]

  •  0
  • klas mack  · 技术社区  · 6 年前

    我无法弄清这个错误的根源,因为附加调试器时,似乎不会发生这种情况。下面是代码。

    当Windows窗体客户端订阅时,订阅者ID将添加到订阅者词典中,当客户端取消订阅时,它将从词典中删除。当(或在)客户机取消订阅时发生错误。似乎下次调用NotifySubscribers()方法时,foreach()循环失败,主题行出错。该方法将错误写入应用程序日志,如下面的代码所示。当附加调试器并且客户端取消订阅时,代码执行正常。

    你觉得这个代码有问题吗?我需要使字典安全吗?

    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    public class SubscriptionServer : ISubscriptionServer
    {
        private static IDictionary<Guid, Subscriber> subscribers;
    
        public SubscriptionServer()
        {            
            subscribers = new Dictionary<Guid, Subscriber>();
        }
    
        public void NotifySubscribers(DataRecord sr)
        {
            foreach(Subscriber s in subscribers.Values)
            {
                try
                {
                    s.Callback.SignalData(sr);
                }
                catch (Exception e)
                {
                    DCS.WriteToApplicationLog(e.Message, 
                      System.Diagnostics.EventLogEntryType.Error);
    
                    UnsubscribeEvent(s.ClientId);
                }
            }
        }
    
    
        public Guid SubscribeEvent(string clientDescription)
        {
            Subscriber subscriber = new Subscriber();
            subscriber.Callback = OperationContext.Current.
                    GetCallbackChannel<IDCSCallback>();
    
            subscribers.Add(subscriber.ClientId, subscriber);
    
            return subscriber.ClientId;
        }
    
    
        public void UnsubscribeEvent(Guid clientId)
        {
            try
            {
                subscribers.Remove(clientId);
            }
            catch(Exception e)
            {
                System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                        e.Message);
            }
        }
    }
    
    0 回复  |  直到 11 年前
        1
  •  1616
  •   Drewness    5 年前

    foreach(Subscriber s in subscribers.Values)
    

    foreach(Subscriber s in subscribers.Values.ToList())
    

    打电话 subscribers.Values.ToList() 复制的值 subscribers.Values foreach

        2
  •  113
  •   Mitch Wheat    7 年前

    有几种方法可以解决这个问题,其中一种方法是将for循环更改为使用显式 .ToList() :

    public void NotifySubscribers(DataRecord sr)  
    {
        foreach(Subscriber s in subscribers.Values.ToList())
        {
                                                  ^^^^^^^^^  
            ...
    
        3
  •  64
  •   Soner Gönül to StackOverflow    11 年前

    在我看来,一个更有效的方法是有另一个清单,你声明你把任何“要删除”的东西都放进去。然后在完成主循环(不带.to list())之后,在“要删除”列表上执行另一个循环,在发生时删除每个条目。所以在你的课上你要加上:

    private List<Guid> toBeRemoved = new List<Guid>();
    

    public void NotifySubscribers(DataRecord sr)
    {
        toBeRemoved.Clear();
    
        ...your unchanged code skipped...
    
       foreach ( Guid clientId in toBeRemoved )
       {
            try
            {
                subscribers.Remove(clientId);
            }
            catch(Exception e)
            {
                System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + 
                    e.Message);
            }
       }
    }
    
    ...your unchanged code skipped...
    
    public void UnsubscribeEvent(Guid clientId)
    {
        toBeRemoved.Add( clientId );
    }
    

    这不仅可以解决你的问题,还可以防止你不断地从字典中创建一个列表,如果有很多订阅者的话,这个列表会很昂贵。假设在任何给定的迭代中要删除的订阅服务器列表低于列表中的总数,则速度应该更快。当然,如果您对您的具体使用情况有任何疑问,请随时对其进行描述以确保其正确性。

        4
  •  42
  •   Mohammad Sepahvand    13 年前

    您还可以锁定订阅者词典,以防止在循环时对其进行修改:

     lock (subscribers)
     {
             foreach (var subscriber in subscribers)
             {
                   //do something
             }
     }
    
        5
  •  28
  •   Language Lassi    6 年前

    为什么会出现这个错误?

    通常.Net集合不支持同时枚举和修改。如果在枚举期间尝试修改集合列表,则会引发异常。因此,这个错误背后的问题是,我们不能在遍历列表/字典时对其进行修改。

    解决方案之一

    如果我们使用dictionary的键列表来迭代dictionary,那么我们可以并行地修改dictionary对象,因为我们正在迭代key集合,并且 不是字典(并迭代它的密钥集合)。

    例子

    //get key collection from dictionary into a list to loop through
    List<int> keys = new List<int>(Dictionary.Keys);
    
    // iterating key collection using a simple for-each loop
    foreach (int key in keys)
    {
      // Now we can perform any modification with values of the dictionary.
      Dictionary[key] = Dictionary[key] - 1;
    }
    

    这是一个 blog post

    Why this error occurs?

        6
  •  5
  •   luc.rg.roy    13 年前

    实际上,在我看来,问题是您正在从列表中删除元素,并希望继续读取列表,就像什么都没发生过一样。

        7
  •  4
  •   nich vivek    8 年前

    无效操作异常- 发生InvalidOperationException。它报告foreach循环中的“collection was modified”

    删除对象后,使用break语句。

    ArrayList list = new ArrayList(); 
    
    foreach (var item in list)
    {
        if(condition)
        {
            list.remove(item);
            break;
        }
    }
    
        8
  •  3
  •   Nisarg Shah    7 年前

    我有同样的问题,当我使用 for 循环而不是 foreach

    // foreach (var item in itemsToBeLast)
    for (int i = 0; i < itemsToBeLast.Count; i++)
    {
        var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);
    
       if (matchingItem != null)
       {
          itemsToBeLast.Remove(matchingItem);
          continue;
       }
       allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
    }
    
        9
  •  3
  •   Mark Aven    7 年前

    好吧,所以帮助我的是反向迭代。我试图从列表中删除一个条目,但向上迭代,结果它弄糟了循环,因为该条目已不存在:

    for (int x = myList.Count - 1; x > -1; x--)
                            {
    
                                myList.RemoveAt(x);
    
                            }
    
        10
  •  2
  •   Mike    7 年前

    我见过很多选择,但对我来说这是最好的。

    ListItemCollection collection = new ListItemCollection();
            foreach (ListItem item in ListBox1.Items)
            {
                if (item.Selected)
                    collection.Add(item);
            }
    

    请注意,ListItemCollection可以包含重复项。默认情况下,没有任何内容阻止将重复项添加到集合中。要避免重复,可以执行以下操作:

    ListItemCollection collection = new ListItemCollection();
                foreach (ListItem item in ListBox1.Items)
                {
                    if (item.Selected && !collection.Contains(item))
                        collection.Add(item);
                }
    
        11
  •  1
  •   joe    5 年前

    接受的答案在最坏的情况下是不精确和不正确的。 ToList() ,仍然可能会出现错误。另外 lock immutable types .

    通常,不可变类型意味着一旦创建就不能更改其状态。 所以你的代码应该是:

    public class SubscriptionServer : ISubscriptionServer
    {
        private static ImmutableDictionary<Guid, Subscriber> subscribers = ImmutableDictionary<Guid, Subscriber>.Empty;
        public void SubscribeEvent(string id)
        {
            subscribers = subscribers.Add(Guid.NewGuid(), new Subscriber());
        }
        public void NotifyEvent()
        {
            foreach(var sub in subscribers.Values)
            {
                //.....This is always safe
            }
        }
        //.........
    }
    

    如果您有一个公共成员,这可能特别有用。其他类总是可以 foreach 在不可变类型上,不必担心集合被修改。

        12
  •  0
  •   Rezoan    12 年前

    可以将订阅服务器字典对象复制到同一类型的临时字典对象,然后使用foreach循环迭代临时字典对象。

        13
  •  0
  •   ford prefect    11 年前

    因此,解决这个问题的另一种方法是创建一个新字典,只添加不想删除的元素,然后用新字典替换原来的字典。我不认为这是一个太多的效率问题,因为它不会增加您在结构上迭代的次数。

        14
  •  0
  •   user8851697    6 年前

    给定的解决方案是可以的,然后像帖子一样,其他人可以尝试这些解决方案。

    供您参考原始链接:- https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/

    当我们使用.Net序列化类序列化其定义包含可枚举类型的对象时,即。 集合,您将很容易得到invalidooperationexception,说“集合被修改了; 最根本的原因是序列化类将通过枚举器在集合中迭代,因此,

    第一个解决方案,我们可以简单地使用锁作为同步解决方案来确保 对列表对象的操作一次只能从一个线程执行。 如果要序列化该对象的集合,则将对每个对象应用锁。

    好吧,.Net 4.0这使得处理多线程场景变得非常方便。 对于这个序列化集合字段的问题,我发现我们可以从ConcurrentQueue(Check MSDN)类中获益, 这是一个线程安全和FIFO集合,使代码无锁。

    使用这个类,简单地说,代码需要修改的内容是用它替换集合类型, 使用Enqueue将元素添加到ConcurrentQueue的末尾,删除这些锁代码。 或者,如果您正在处理的场景确实需要像List这样的集合,那么您将需要更多的代码来将ConcurrentQueue调整到您的字段中。

    顺便说一句,ConcurrentQueue没有Clear方法,因为底层算法不允许原子清除集合。 所以你必须自己做,最快的方法是重新创建一个新的空ConcurrentQueue来替换它。

        15
  •  0
  •   Theodor Zoulias    5 年前

    下面是一个需要专门方法的特定场景:

    1. 这个 Dictionary 经常枚举。
    2. 这个 字典 很少修改。

    Dictionary.Values )在每一个枚举之前都可能是非常昂贵的。我解决这个问题的想法是在多个枚举中重用同一个缓存副本,并观察 IEnumerator 字典 例外情况。枚举器将与复制的数据一起缓存,并在启动新枚举之前进行查询。如果出现异常,缓存的副本将被丢弃,并创建一个新副本。以下是我对这个想法的实现:

    public class SafeEnumerable<T> : IEnumerable<T>, IDisposable
    {
        private IEnumerable<T> _source;
        private IEnumerator<T> _enumerator;
        private T[] _cached;
    
        public SafeEnumerable(IEnumerable<T> source)
        {
            _source = source ?? throw new ArgumentNullException(nameof(source));
        }
    
        public IEnumerator<T> GetEnumerator()
        {
            if (_source == null) throw new ObjectDisposedException(this.GetType().Name);
            if (_enumerator == null)
            {
                _enumerator = _source.GetEnumerator();
                _cached = _source.ToArray();
            }
            else
            {
                var modified = false;
                if (_source is ICollection collection) // C# 7 syntax
                {
                    modified = _cached.Length != collection.Count;
                }
                if (!modified)
                {
                    try
                    {
                        _enumerator.MoveNext();
                    }
                    catch (InvalidOperationException)
                    {
                        modified = true;
                    }
                }
                if (modified)
                {
                    _enumerator.Dispose();
                    _enumerator = _source.GetEnumerator();
                    _cached = _source.ToArray();
                }
            }
            foreach (var item in _cached)
            {
                yield return item;
            }
        }
    
        public void Dispose()
        {
            _enumerator?.Dispose();
            _enumerator = null;
            _cached = null;
            _source = null;
        }
    
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
    
    public static SafeEnumerable<T> AsSafeEnumerable<T>(this IEnumerable<T> source)
        => new SafeEnumerable<T>(source);
    

    private static IDictionary<Guid, Subscriber> _subscribers;
    private static SafeEnumerable<Subscriber> _safeSubscribers;
    
    //...(in the constructor)
    _subscribers = new Dictionary<Guid, Subscriber>();
    _safeSubscribers = _subscribers.Values.AsSafeEnumerable();
    
    // ...(elsewere)
    foreach (var subscriber in _safeSubscribers)
    {
        //...
    }
    

    字典 this class does not throw a Collection was modified exception Remove Clear 被调用。我检查过的所有其他容器的行为都是一致的。我系统地检查了这些课程: List<T> , Collection<T> ObservableCollection<T> , HashSet<T> , SortedSet<T> , Dictionary<T,V> SortedDictionary<T,V> . 只有上述两种方法 字典


    更新: SafeEnumerable 的构造函数,其标识不会被(例如)如下投影隐藏: dictionary.Select(e => e).AsSafeEnumerable()

        16
  •  -1
  •   Michael Sefranek    5 年前

    我个人最近在一个不熟悉的代码中遇到了这个问题,它位于一个带有Linq.Remove(item)的开放dbcontext中。我花了很长时间查找错误调试,因为第一次迭代时项被删除,如果我试图后退一步并重新跳过,则集合为空,因为我处于同一上下文中!