Список <Задачa> - запись базы данных UPSERT с использованием С# Entity Framework
У меня есть объект Employee
, я пытаюсь обновить запись (т.е. Update/Remove) с помощью нескольких задач (Parallel Execution) с использованием одного контекста Entity Entity. Но я получаю следующее исключение.
Сообщение = "Ссылка на объект не установлена в экземпляр объекта."
Рассмотрим следующие DTO
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<ContactPhone> ContactPhoneNumbers { get; set; }
public List<ContactEmail> ContactEmailAddress { get; set; }
}
public class ContactPhone
{
public int ContactId { get; set; }
public string Type { get; set; }
public string Number { get; set; }
}
public class ContactEmail
{
public int ContactId { get; set; }
public string Type { get; set; }
public string Number { get; set; }
}
Таблица сотрудников:
EmployeeId FirstName LastName
_________________________________
1 Bala Manigandan
Таблица ContactPhone:
ContactId EmployeeId Type Number
__________________________________________
1 1 Fax 9123456789
2 1 Mobile 9123456789
Таблица ContactPhone:
ContactId EmployeeId Type EmailAddress
______________________________________________
1 1 Private [email protected]
2 1 Public [email protected]
Входящий API-объект
DTO.Employee emp = new DTO.Employee()
{
EmployeeId = 1,
FirstName = "Bala",
LastName = "Manigandan",
ContactPhoneNumbers = new List<DTO.ContactPhone>
{
new DTO.ContactPhone()
{
Type = "Mobile",
Number = "9000012345"
}
},
ContactEmailAddress = new List<DTO.ContactEmail>()
{
new DTO.ContactEmail()
{
Type = "Private",
EmailAddress = "[email protected]"
},
new DTO.ContactEmail()
{
Type = "Public",
EmailAddress = "[email protected]"
}
}
};
Я получаю запрос API для обновления мобильного номера и удаления номера факса для указанного сотрудника.
Рассмотрим методы задачи:
public void ProcessEmployee(DTO.Employee employee)
{
if(employee != null)
{
DevDBEntities dbContext = new DevDBEntities();
DbContextTransaction dbTransaction = dbContext.Database.BeginTransaction();
List<Task> taskList = new List<Task>();
List<bool> transactionStatus = new List<bool>();
try
{
Employee emp = dbContext.Employees.FirstOrDefault(m => m.EmployeeId == employee.EmployeeId);
if (emp != null)
{
Task task1 = Task.Factory.StartNew(() =>
{
bool flag = UpdateContactPhone(emp.EmployeeId, employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault().Number, dbContext).Result;
transactionStatus.Add(flag);
});
taskList.Add(task1);
Task task2 = Task.Factory.StartNew(() =>
{
bool flag = RemoveContactPhone(emp.EmployeeId, "Fax", dbContext).Result;
transactionStatus.Add(flag);
});
taskList.Add(task2);
}
if(taskList.Any())
{
Task.WaitAll(taskList.ToArray());
}
}
catch
{
dbTransaction.Rollback();
}
finally
{
if(transactionStatus.Any(m => !m))
{
dbTransaction.Rollback();
}
else
{
dbTransaction.Commit();
}
dbTransaction.Dispose();
dbContext.Dispose();
}
}
}
public async Task<bool> UpdateContactPhone(int empId, string type, string newPhone, DevDBEntities dbContext)
{
bool flag = false;
try
{
var empPhone = dbContext.ContactPhones.FirstOrDefault(m => (m.EmployeeId == empId) && (m.Type == type));
if (empPhone != null)
{
empPhone.Number = newPhone;
await dbContext.SaveChangesAsync();
flag = true;
}
}
catch (Exception ex)
{
throw ex;
}
return flag;
}
public async Task<bool> RemoveContactPhone(int empId, string type, DevDBEntities dbContext)
{
bool flag = false;
try
{
var empPhone = dbContext.ContactPhones.FirstOrDefault(m => (m.EmployeeId == empId) && (m.Type == type));
if (empPhone != null)
{
dbContext.ContactPhones.Remove(empPhone);
await dbContext.SaveChangesAsync();
flag = true;
}
}
catch (Exception ex)
{
throw ex;
}
return flag;
}
Я получаю следующее исключение:
Сообщение = "Ссылка на объект не установлена в экземпляр объекта."
Здесь я прикрепляю скриншот к вашей ссылке
![введите описание изображения здесь]()
Мое требование состоит в том, чтобы выполнять все процессы базы данных UPSERT
при параллельном выполнении, любезно помогите мне, как добиться этого без каких-либо исключений, используя Task
Ответы
Ответ 1
1st) Прекратите использование контекста в разных потоках.
DbContext не является потокобезопасным, это само по себе может вызвать множество странных проблем, даже сумасшедшее исключение NullReference
Теперь вы уверены, что ваш параллельный код быстрее, чем не параллельная реализация?
Я очень сомневаюсь в этом.
Из того, что я вижу, вы даже не меняете свой объект Employee, поэтому я не понимаю, почему вы должны его загрузить (дважды)
Я думаю, что все, что вам нужно, - это
1) Загрузите телефон, который необходимо обновить, и установите новый номер
2) Удалите неиспользуемый мобильный
НЕ нужно загружать эту запись. Просто используйте конструктор по умолчанию и установите Id.
EF может справиться с остальными (конечно, вам нужно прикрепить вновь созданный объект)
3) Сохраните изменения
(Используйте метод 1,2,3 в 1, используя тот же контекст)
Если по какой-то причине вы решите пойти с несколькими задачами
Обновление
Я просто заметил это:
catch (Exception ex) { throw ex; }
Это плохо (вы потеряете stacktrace)
Либо удалите try/catch, либо используйте
catch (Exception ex) { throw ; }
Обновление 2
Некоторые примеры кода (я предполагаю, что ваш ввод содержит идентификаторы объектов, которые вы хотите обновить/удалить)
var toUpdate= ctx.ContactPhones.Find(YourIdToUpdate);
toUpdate.Number = newPhone;
var toDelete= new ContactPhone{ Id = 1 };
ctx.ContactPhones.Attach(toDelete);
ctx.ContactPhones.Remove(toDelete);
ctx.SaveChanges();
Если вы идете с параллельным подходом
using(TransactionScope tran = new TransactionScope()) {
//Create and Wait both Tasks(Each task should create it own context)
tran.Complete();
}
Ответ 2
Возможные места, где может возникать эта ошибка, - employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault()
employee.ContactPhoneNumbers
будет возможно нулевым, поскольку вы не хотите его загружать, или вы не отметили его как virtual
, чтобы он ленился.
Итак, чтобы исправить эту проблему:
1. Отметьте навигационные свойства как virtual
, чтобы ленивая загрузка
public virtual List<ContactPhone> ContactPhoneNumbers { get; set; }
public virtual List<ContactEmail> ContactEmailAddress { get; set; }
- Или загружать объекты с помощью
.Include
- Или Явная загрузка объектов
dbContext.Entry(emp).Collection(s => s.ContactPhoneNumbers).Load();
dbContext.Entry(emp).Collection(s => s.ContactEmailAddress ).Load();