Запись базы данных 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 = " ссылка на объект не установлена в экземпляр объект."

здесь я прилагаю скриншот для вашей справки

enter image description here

мое требование-сделать всю базу данных UPSERT процессы в параллельном исполнении, любезно помогите мне, как достичь этого без каких-либо исключений, используя Task

2 ответов


1st) прекратите использовать контекст в разных потоках.
DbContext не является потокобезопасным, что само по себе может вызвать много странных проблем ,даже сумасшедшее исключение NullReference

теперь,вы уверены, что ваш параллельный код быстрее, чем без параллельной реализации?
Я очень сомневаюсь в этом.

из того, что я вижу, вы даже не меняете свой объект сотрудника, поэтому я не вижу, почему вы должны загрузить его (дважды)

Я думаю, все, что вам нужно is
1)Загрузите телефон, который вам нужно обновить и установить новый номер
2) удалить неиспользуемый мобильный
Не нужно загружать эту запись.Просто используйте конструктор по умолчанию и установите Id.
EF может обрабатывать остальные (конечно, вам нужно прикрепить вновь созданный объект)

3) сохраните изменения
(Сделайте 1,2,3 в 1 методе, используя тот же контекст)

если по какой-то причине вы решите пойти с несколько задач

  1. создайте новый контекст внутри каждой задачи
  2. оберните свой код в 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; }
  1. или нетерпеливый загрузить сущности, используя .Include
  2. или явная загрузка сущностей

dbContext.Entry(emp).Collection(s => s.ContactPhoneNumbers).Load(); dbContext.Entry(emp).Collection(s => s.ContactEmailAddress ).Load();