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; }
- 或者 Eager 使用
.Include
加载实体
- 或显式加载实体
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)
如果出于某种原因您决定执行多项任务
- 在每个任务中创建一个新上下文
- 将您的代码包装在 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();
}
我有一个 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; }
- 或者 Eager 使用
.Include
加载实体
- 或显式加载实体
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)
如果出于某种原因您决定执行多项任务
- 在每个任务中创建一个新上下文
- 将您的代码包装在 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();
}