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

List<Task> - UPSERT database record using C# Entity Framework

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

Message = "Object reference not set to an instance of an object."

考虑以下 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; }
}

员工Table:

EmployeeId  FirstName   LastName
_________________________________
1           Bala        Manigandan

联系电话Table:

ContactId   EmployeeId  Type    Number
__________________________________________
1           1           Fax     9123456789
2           1           Mobile  9123456789

联系电话Table:

ContactId   EmployeeId  Type    EmailAddress
______________________________________________
1           1           Private bala@gmail.com
2           1           Public  bala@ymail.com

即将到来的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 = "Object reference not set to an instance of an object."

这里附上截图供大家参考

我的要求是并行执行所有数据库 UPSERT 进程,请帮助我如何使用 Task

毫无例外地实现这一目标

可能发生此错误的地方是 - 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; }
  1. 或者 Eager 使用 .Include
  2. 加载实体
  3. 或显式加载实体

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

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

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

据我所知,您甚至没有更改您的 Employee 对象,所以我不明白您为什么要加载它(两次)

我想你只需要
1)加载需要更新的phone并设置新的Number
2)删除不用的手机
不必加载此 record.Just 使用默认构造函数并设置 Id。
EF可以处理剩下的(当然你需要附加新创建的对象)

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

如果出于某种原因您决定执行多项任务

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

更新
我刚注意到这个:

catch (Exception ex) { throw ex;    }

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

catch (Exception ex) { throw ; }

更新 2
一些示例代码(我假设您的输入包含您想要的实体的 ID 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();
}