Запись базы данных List-UPSERT с использованием C# Entity Framework
у меня есть Employee
object, я пытаюсь обновить запись (т. е. обновить / удалить), используя несколько задач (параллельное выполнение), используя один контекст сущности DB. Но я получаю следующее исключение
Message = " ссылка на объект не установлена в экземпляр объекта."
рассмотрим следующие 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 bala@gmail.com
2 1 Public bala@ymail.com
in-Coming объект 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 = "bala@gmail.com"
},
new DTO.ContactEmail()
{
Type = "Public",
EmailAddress = "bala@ymail.com"
}
}
};
Я получаю запрос 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;
}
Я получаю следующее исключение:
Message = " ссылка на объект не установлена в экземпляр объект."
здесь я прилагаю скриншот для вашей справки
мое требование-сделать всю базу данных UPSERT
процессы в параллельном исполнении, любезно помогите мне, как достичь этого без каких-либо исключений, используя Task
2 ответов
1st) прекратите использовать контекст в разных потоках.
DbContext не является потокобезопасным, что само по себе может вызвать много странных проблем ,даже сумасшедшее исключение NullReference
теперь,вы уверены, что ваш параллельный код быстрее, чем без параллельной реализации?
Я очень сомневаюсь в этом.
из того, что я вижу, вы даже не меняете свой объект сотрудника, поэтому я не вижу, почему вы должны загрузить его (дважды)
Я думаю, все, что вам нужно is
1)Загрузите телефон, который вам нужно обновить и установить новый номер
2) удалить неиспользуемый мобильный
Не нужно загружать эту запись.Просто используйте конструктор по умолчанию и установите Id.
EF может обрабатывать остальные (конечно, вам нужно прикрепить вновь созданный объект)
3) сохраните изменения
(Сделайте 1,2,3 в 1 методе, используя тот же контекст)
если по какой-то причине вы решите пойти с несколько задач
- создайте новый контекст внутри каждой задачи
- оберните свой код в TransactionScope
обновление
Я только что заметил это:
catch (Exception ex) { throw ex; }
это плохо (вы теряете stacktrace)
Либо удалите try / catch, либо используйте
catch (Exception ex) { throw ; }
обновление 2
Некоторый пример кода (я предполагаю, что ваш ввод содержит идентификаторы объектов, которые вы хотите update / delete)
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();
}
возможные места, где эта ошибка может произойти - employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault()
на employee.ContactPhoneNumbers
будет возможно null, поскольку вы не хотите загружать его, и вы не отметили свойство как 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();