Из-за чего в Dictionary возникает исключение "Элемент с тем же ключом уже был добавлен"?
Суть такая. DataGrid заполняется данными из БД. Все работает хорошо. Исключение появляется когда повторно редактирую одну и ту же строку. Например: В таблице отделов изменяю название отдела - Enter, все хорошо, изменяю повторно - процесс падает с исключением. Трудность (так же и причина) в том, что исключение из внешнего кода как я понял EF библиотеки. Дебажить невозможно. Причину так и не смог понять, что ему не хватает. По выводу видно, что во время исполнения создается некий словарь, куда пишутся пары с редактируемыми из локального контекста объектами. И у меня они пишутся не совсем верно. Заранее спасибо.
DataGrid:
ViewModel:
public IEnumerable<Subdivision> PrimordialSubdivisions
{
get => _primordialSubdivisions;
set => SetProperty(ref _primordialSubdivisions, value);
}
....
PrimordialSubdivisions = _appContext.Subdivisions.Local.ToBindingList();
....
subdivisionsGrid.ItemsSource = PrimordialSubdivisions;
Обработчик окончания редактирования после которого следует исключение:
private void SubdivisionEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
Subdivision s = (Subdivision)e.Row.Item;
//Получаем исходный объект из бд с которым будем сравнивать
var nativeSubdivision = _appContext.GetNativeSubdivision(s);
if (_changedSubdivisions.ContainsKey(nativeSubdivision))
{
//Если при редактировании объект вернули в исходное состояние
if (_changedSubdivisions[nativeSubdivision].Equals(nativeSubdivision))
{
_changedSubdivisions.Remove(nativeSubdivision);
e.Cancel = true;
return;
}
return;
}
if (s.Equals(nativeSubdivision)) return;
_changedSubdivisions[nativeSubdivision] = s;
UndoChangesCommand.RaiseCanExecuteChanged();
}
Models:
public class Worker : ObservableViewModel
{
protected ILogWriter _innerWriter;
private string _workerName;
private string _workerLastName;
private string _workerOtchestvo;
private DateTime _workerBirthDate;
private int _id;
private Sex _workerSex;
[Key]
public int Id {
get => _id;
set => SetProperty(ref _id, value);
}
public string WorkerName
{
get => _workerName;
set => SetProperty(ref _workerName, value);
}
public string WorkerLastName
{
get => _workerLastName;
set => SetProperty(ref _workerLastName, value);
}
public string WorkerOtchestvo
{
get => _workerOtchestvo;
set => SetProperty(ref _workerOtchestvo, value);
}
public DateTime WorkerBirthDate
{
get => _workerBirthDate;
set => SetProperty(ref _workerBirthDate, value);
}
public Sex WorkerSex
{
get => _workerSex;
set => SetProperty(ref _workerSex, value);
}
[Column("WorkerSubdivision")]
public int? WorkerSubdivision { get; set; }
[ForeignKey("WorkerSubdivision")]
public virtual Subdivision Subdivision { get; set; }
public Worker() { }
public Worker(Subdivision subdivision, string name, string lastName, string otchestvo, DateTime birthDate, Sex workerSex)
{
Subdivision = GuardWithLog.GuardForNull(subdivision, "Subdivision");
WorkerName = GuardWithLog.GuardForNull(name, "name");
WorkerLastName = GuardWithLog.GuardForNull(lastName, "lastName");
WorkerOtchestvo = GuardWithLog.GuardForNull(otchestvo, "otchestvo");
WorkerBirthDate = GuardWithLog.GuardForNull(birthDate, "birthDate");
WorkerSex = GuardWithLog.GuardForNull(workerSex, "worerSex");
_innerWriter = AsyncLogWriter.CreateInstance();
_innerWriter.WriteInLog(LogLevel.Information, typeof(Worker), $"Worker {WorkerName} {WorkerLastName} was created");
}
public override bool Equals(object obj)
{
var item = obj as Worker;
if (item == null) return false;
return Id.Equals(item.Id) && WorkerBirthDate.Equals(item.WorkerBirthDate)
&& WorkerLastName.Equals(item.WorkerLastName) && WorkerName.Equals(item.WorkerName) &&
WorkerOtchestvo.Equals(item.WorkerOtchestvo)
&& WorkerSex.Equals(item.WorkerSex);
}
public override string ToString()
{
return $"{WorkerLastName} {WorkerName} {WorkerOtchestvo}";
}
}
public class Subdivision : ObservableViewModel, IEquatable<Subdivision>
{
ILogWriter _innerWriter;
private string _subdivName;
private int _id;
[Key]
public int Id { get => _id; set => SetProperty(ref _id, value); }
public string SubdivisionName
{
get => _subdivName; set => SetProperty(ref _subdivName, value);
}
[Column("HeadWorker")]
public int? HeadWorker { get; set; }
[ForeignKey("HeadWorker")]
public virtual Worker Head { get; set; }
public Subdivision() { }
private Subdivision(string subdivisionName, Worker headWorker)
{
SubdivisionName = GuardWithLog.GuardForNull(subdivisionName, "name");
Head = GuardWithLog.GuardForNull(headWorker, "headWorker");
_innerWriter = AsyncLogWriter.CreateInstance();
_innerWriter.WriteInLog(LogLevel.Information, typeof(Subdivision),
$"Subdivision {subdivisionName} was created.");
}
public override string ToString()
{
return SubdivisionName;
}
//public static Subdivision CreateInstance(string subdivisionName, Worker headWorker)
//{
// return new Subdivision(subdivisionName, headWorker);
//}
public bool Equals(Subdivision other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return _subdivName == other._subdivName && _id == other._id && HeadWorker == other.HeadWorker && Equals(Head, other.Head);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Subdivision) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (_subdivName != null ? _subdivName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ _id;
hashCode = (hashCode * 397) ^ HeadWorker.GetHashCode();
hashCode = (hashCode * 397) ^ (Head != null ? Head.GetHashCode() : 0);
return hashCode;
}
}
}
Output:
System.Transactions Critical: 0 : <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>Необработанное исключение</Description><AppDomain>FunnyWaterCarrier.exe</AppDomain><Exception><ExceptionType>System.ArgumentException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>Элемент с тем же ключом уже был добавлен.</Message><StackTrace> в System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
в System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
в System.Collections.Generic.Dictionary`2..ctor(IDictionary`2 dictionary, IEqualityComparer`1 comparer)
в System.Windows.Controls.Primitives.Selector.InternalSelectedItemsStorage..ctor(InternalSelectedItemsStorage collection, IEqualityComparer`1 equalityComparer)
Итоговый вариант метода после нахождение решения вопроса:
private void SubdivisionEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
Subdivision editableItem = (Subdivision)e.Row.Item;
//Получаем исходный объект из бд с которым будем сравнивать
var nativeSubdivision = _appContext.GetNativeSubdivision(editableItem);
if (_changedSubdivisions.ContainsKey(nativeSubdivision.Id))
{
//Если при редактировании объект вернули в исходное состояние
if (_changedSubdivisions[nativeSubdivision.Id].Equals(nativeSubdivision))
{
_changedSubdivisions.Remove(nativeSubdivision.Id);
return;
}
return;
}
if (editableItem.Equals(nativeSubdivision)) return;
_changedSubdivisions[nativeSubdivision.Id] = editableItem;
UndoChangesCommand.RaiseCanExecuteChanged();
}
Работает в совокупности с кодом из ответа.
Ответы (1 шт):
Ошибка связана с тем, что вы пытаете добавить в базу то, что там уже есть. Скорее всего проблема в формировании набора данных для записи. Словарь Dictionary думает, что вы пытаетесь в него воткнуть данные с ключом, который там уже есть.
Насколько я знаком с вашим вопросом, вы используете в качестве ключа ссылочный тип. Используйте значимый или немутабельный. Мутабельные типы нельзя использовать в качестве ключа.
Давайте уберем GetHashCode и поправим Equals и перегрузим операторы == и !=
public class Subdivision : ObservableViewModel, IEquatable<Subdivision>
{
// ...
public bool Equals(Subdivision? other)
{
return other is Subdivision division
&& (ReferenceEquals(this, division)
|| (_subdivName == division._subdivName
&& _id == division._id
&& HeadWorker == division.HeadWorker
&& Head == division.Head));
}
public static bool operator ==(Subdivision left, Subdivision right)
=> left.Equals(right);
public static bool operator !=(Subdivision left, Subdivision right)
=> !left.Equals(right);
}
// здесь вы забыли добавить IEquatable<Worker>
public class Worker : ObservableViewModel, IEquatable<Worker>
{
// ...
public bool Equals(Worker? other)
{
return other is Worker worker
&& (ReferenceEquals(this, worker)
|| (Id == worker.Id
&& WorkerBirthDate == worker.WorkerBirthDate
&& WorkerLastName == worker.WorkerLastName
&& WorkerName == worker.WorkerName
&& WorkerOtchestvo == worker.WorkerOtchestvo
&& WorkerSex == worker.WorkerSex));
}
public static bool operator ==(Worker left, Worker right)
=> left.Equals(right);
public static bool operator !=(Worker left, Worker right)
=> !left.Equals(right);
}
Всё, теперь у вас сравнение идет по содержимому, и если возникает исключение, значит вы пытаетесь добавить в словарь то, что уже там есть.
