简体   繁体   English

清单 <Task> -使用C#实体框架的UPSERT数据库记录

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

I have an Employee object, I'm trying to update a record (ie, Update / Remove) using a multiple task (Parallel Execution) using single DB Entity Context. 我有一个Employee对象,我正在尝试使用单个DB实体上下文使用多个任务(并行执行)来更新记录(即Update / Remove)。 But I'm getting the following exception 但是我收到以下异常

Message = "Object reference not set to an instance of an object." Message =“对象引用未设置为对象的实例。”

Consider the following DTO's 考虑以下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; }
}

Employee Table : 员工表

EmployeeId  FirstName   LastName
_________________________________
1           Bala        Manigandan

ContactPhone Table : 联系人电话表

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

ContactPhone Table : 联系人电话表

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

In-Coming API Object is 现“传入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"
            }
        }
};

I'm getting an API request to update Mobile number and to remove the Fax number for a specified Employee. 我收到一个API请求,以更新手机号码并删除指定员工的传真号码

Consider the task methods: 考虑任务方法:

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;
}

I'm getting following exception: 我收到以下异常:

Message = "Object reference not set to an instance of an object." Message =“对象引用未设置为对象的实例。”

Here with I'm attaching the screenshot for your reference 在此附上屏幕截图供您参考

在此处输入图片说明

My requirement is to do all the database UPSERT processes in parallel execution, kindly assist me how to achieve this without any exception using Task 我的要求是并行执行所有数据库UPSERT流程,请协助我如何使用Task毫无例外地实现这一目标

1st)Stop using the context in different threads. 1st)在不同线程中停止使用上下文。
DbContext is NOT thread safe,that alone can cause many strange problems ,even a crazy NullReference exception DbContext不是线程安全的,仅此一个就可能导致许多奇怪的问题,甚至是一个疯狂的NullReference异常

Now,are you sure your Parallel code is faster than a non parallel implementation? 现在,您确定并行代码比非并行实现更快吗?
I very much doubt that. 我对此非常怀疑。

From what I see you are don't even changing your Employee object so I don't see why you should load it (twice) 从我的角度来看,您甚至都没有更改Employee对象,所以我看不到为什么要加载它(两次)

I think all you need is 我想你所需要的就是
1)Load the phone which you need to update and set the new Number 1)加载您需要更新的电话并设置新号码
2)Delete the unused Mobile 2)删除未使用的手机
DON'T have to load this record.Just use the default constructor and set the Id. 不必加载此记录,只需使用默认构造函数并设置ID即可。
EF can handle the rest (Of course you need to attach the newly created object) EF可以处理其余部分(当然,您需要附加新创建的对象)

3)Save your changes 3)保存您的更改
(Do 1,2,3 in 1 method using the same context) (在相同的上下文中以1、2、3方法进行操作)

If for some reason you do decide to go with multiple tasks 如果由于某种原因您决定执行多个任务

  1. Create a new context within each Task 在每个任务中创建一个新的上下文
  2. Wrap your code in a TransactionScope 将代码包装在TransactionScope中

Update 更新资料
I just noticed this: 我只是注意到了这一点:

catch (Exception ex) { throw ex;    }

This is bad (you lose the stacktrace) 这很糟糕(您丢失了堆栈跟踪)
Either remove the try/catch or use 删除try / catch或使用

catch (Exception ex) { throw ; }

Update 2 更新2
Some sample code (I assume your input contains the Ids of the entities you want to update/delete) 一些示例代码(我假设您的输入包含要更新/删除的实体的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();

If you go with the parallel approach 如果您采用并行方法

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

Possible places where this error could occur are - employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault() 可能发生此错误的地方是employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault()

The employee.ContactPhoneNumbers will be possibly null as you are not eager loading it nor you have marked the property as virtual so that it would lazy load. employee.ContactPhoneNumbers可能为null,因为您不希望加载它,也没有将属性标记为virtual以便延迟加载。

So to fix this issue: 1. Mark the navigational properties as virtual so that lazy load 因此,要解决此问题:1.将导航属性标记为virtual以便延迟加载

public virtual List<ContactPhone> ContactPhoneNumbers { get; set; }
public virtual List<ContactEmail> ContactEmailAddress { get; set; }
  1. Or Eager load the entities using .Include 或渴望使用.Include加载实体
  2. Or Explicit load the entities 或显式加载实体

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM