List <Task> - 使用C#Entity Framework的UPSERT数据库记录

B.B*_*dan 10 c# entity-framework task object-reference task-parallel-library

我有一个Employee对象,我正在尝试使用单个数据库实体上下文使用多任务(并行执行)更新记录(即更新/删除).但我得到以下例外

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; }
}
Run Code Online (Sandbox Code Playgroud)

员工表:

EmployeeId  FirstName   LastName
_________________________________
1           Bala        Manigandan
Run Code Online (Sandbox Code Playgroud)

联系电话表:

ContactId   EmployeeId  Type    Number
__________________________________________
1           1           Fax     9123456789
2           1           Mobile  9123456789
Run Code Online (Sandbox Code Playgroud)

联系电话表:

ContactId   EmployeeId  Type    EmailAddress
______________________________________________
1           1           Private bala@gmail.com
2           1           Public  bala@ymail.com
Run Code Online (Sandbox Code Playgroud)

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"
            }
        }
};
Run Code Online (Sandbox Code Playgroud)

我收到一个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;
}
Run Code Online (Sandbox Code Playgroud)

我得到以下异常:

Message ="对象引用未设置为对象的实例."

在这里,我附上截图供您参考

在此输入图像描述

我的要求是UPSERT在并行执行中完成所有数据库进程,请使用Task帮助我如何实现这一点,没有任何异常

Geo*_*vos 6

1)停止在不同的线程中使用上下文.
DbContext不是线程安全的,单独就会导致许多奇怪的问题,甚至是疯狂的NullReference异常

现在,您确定您的并行代码比非并行实现更快吗?
我非常怀疑.

从我看到你甚至不更改你的Employee对象所以我不明白为什么你应该加载它(两次)

我认为您需要的只是
1)加载您需要更新的手机并设置新的号码
2)删除未使用的Mobile
不必加载此记录.只需使用默认构造函数并设置Id.
EF可以处理其余的(当然你需要附加新创建的对象)

3)保存您的更改
(使用相同的上下文在1,2方法中执行1,2,3)

如果由于某种原因你决定去做多个任务

  1. 在每个任务中创建新的上下文
  2. 将您的代码包装在TransactionScope中

更新
我刚注意到这一点:

catch (Exception ex) { throw ex;    }
Run Code Online (Sandbox Code Playgroud)

这很糟糕(你丢失了堆栈跟踪)
删除try/catch或者使用

catch (Exception ex) { throw ; }
Run Code Online (Sandbox Code Playgroud)

更新2
一些示例代码(我假设您的输入包含您要更新/删除的实体的ID)

 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();
Run Code Online (Sandbox Code Playgroud)

如果你采用并行方法

using(TransactionScope tran = new TransactionScope()) {
    //Create and Wait both Tasks(Each task should create it own context)
    tran.Complete();
}
Run Code Online (Sandbox Code Playgroud)