我有以下代码:
public void someMethod(){
...
var accounts = myRepo.GetAccounts(accountId)?.ToList();
...
foreach (var account in accounts)
{
account.Status="INACTIVE";
var updatedAccount = myRepo.AddOrUpdateAccounts(account);
}
}
public Account AddOrUpdateAccounts(Account account){
//I want to compare account in the Db and what is passed in. So get the account from DB
var accountFromDb = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
//here accountFromDb.Status is returned as INACTIVE, but in the database the column value is ACTIVE
...
...
}
public IEnumerable<Account> GetAccounts(int id){
return id <= 0 ? null : m_Context.Accounts.Where(x => x.Id == id);
}
在这里,我在someMethod()
内打电话给GetAccounts()
,它从Accounts表返回数据。
然后我要更改帐户的Status
,然后调用AddOrUpdateAccounts()
。
在AddOrUpdateAccounts()
内部,我想比较传入的帐户和数据库中的内容。当我调用GetAccounts()
时,它返回带有STATUS="INACTIVE"
的记录。我还没有完成SaveChanges()
。为什么GetAccounts()
没有从数据库返回数据?在Db中,状态仍然为“有效”
存储库方法应返回IQueryable<Account>
而不是IEnumerable<Account>
,因为这将使使用者可以继续完善任何条件或控制在对数据库执行任何查询之前应该如何使用帐户:
我会考虑:
public IQueryable<Account> GetAccountsById(int id){
return m_Context.Accounts.Where(x => x.Id == id);
}
不返回#null,仅返回查询。消费者可以决定如果数据不可用该怎么办。
从那里开始,调用代码如下:
var accounts = myRepo.GetAccounts(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
您的addOrUpdate无法使用:
public Account AddOrUpdateAccounts(Account account){
...
var account = myRepo.GetAccounts(account.Id); //this doesn't return whats in the database.
您以“帐户”身份传递帐户,然后尝试声明一个称为“帐户”的本地变量。如果删除var
关键字,则会将DbContext的记录加载到修改后的帐户上方,并且所做的更改将丢失。只要该帐户仍与DbContext关联,就不需要将其加载到另一个变量中。
编辑:将var account = ...
语句更改为如下形式:
public Account AddOrUpdateAccounts(Account account){
...
var accountToUpdate = myRepo.GetAccounts(account.Id); //this doesn't return whats
accountToUpdate
将显示修改后的状态,而不是数据库中的状态,因为DbContext仍在跟踪对您修改的实体的引用。 (account
)例如,如果我这样做:
var account1st = context.Accounts.Single(x => x.AccountId == 1);
var account2nd = context.Accounts.Single(x => x.AccountId == 1);
Console.WriteLine(account1st.Status); // I get "ACTIVE"
Console.WriteLine(account2nd.Status); // I get "ACTIVE"
account1st.Status = "INACTIVE";
Console.WriteLine(account2nd.Status); // I get "INACTIVE"
两个引用都指向同一实例。只要我第二次尝试读取帐户,只要它来自相同的DbContext且上下文正在跟踪实例,就没有关系。如果您通过不同的DbContext读取行,或者将AsNoTracking()
与所有读取一起使用,则可以从数据库中重新读取帐户。您可以重新加载实体,但是如果这些变量指向相同的引用,它将覆盖您的更改并将实体设置回Unmodified
。观看SQL事件探查器输出时,这可能会有些混乱,因为在某些情况下,您会看到EF对实体运行SELECT
查询,但返回的实体具有与数据库中不同的修改后的值。即使从跟踪缓存中加载,EF在某些情况下仍然可以对数据库执行查询,但是它会返回跟踪的实体引用。
/编辑
关于保存更改,实际上只是归结为在与该帐户关联的DbContext上调用SaveChanges。 “棘手的”部分是对DbContext进行范围界定,以便可以做到这一点。推荐的模式是工作单元。有几种不同的方法,我推荐给EF的方法是Mehdime的DbContextScope,但是您可以实现更简单的方法,这些方法可能更易于理解和遵循。从本质上讲,一个工作单元封装了DbContext,以便您可以定义一个存储库可以访问相同DbContext的范围,然后在工作结束时提交这些更改。
最基本的级别:
public interface IUnitOfWork<TDbContext> : IDisposable where TDbContext : DbContext
{
TDbContext Context { get; }
int SaveChanges();
}
public class UnitOfWork : IUnitOfWork<YourDbContext>
{
private YourDbContext _context = null;
TDbContext IUnitOfWork<YourDbContext>.Context
{
get { return _context ?? (_context = new YourDbContext("YourConnectionString"); }
}
int IUnitOfWork<YourDbContext>.SaveChanges()
{
if(_context == null)
return 0;
return _context.SaveChanges();
}
public void Dispose()
{
try
{
if (_context != null)
_context.Dispose();
}
catch (ObjectDisposedException)
{ }
}
}
[使用此类,并通过IoC容器(Autofac,Unity或MVC Core)使用依赖项注入,您将工作单元注册为每个请求的实例,以便当控制器和存储库类在其构造函数中请求一个时,它们会收到同一实例。
控制器/服务:
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
private readonly IYourRepository _repository = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork, IYourRepository repository)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
_repository = repository ?? throw new ArgumentNullException("repository");
}
存储库
private readonly IUnitOfWork<YourDbContext> _unitOfWork = null;
public YourService(IUnitOfWork<YourDbContext> unitOfWork)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException("unitOfWork");
}
private YourDbContext Context { get { return _unitOfWork.Context; } }
大免责声明:这是一个非常粗糙的初始实现,粗略地解释了工作单元的工作方式,这绝对不是生产合适的代码的方法。它有局限性,特别是在处理DbContext方面,但应作为一个示范。绝对希望实现已经存在的库并解决这些问题。这些实现正确管理DbContext处理,并将管理超出上下文的范围,例如TransactionScope,以便即使调用unitOfWork.Context.SaveChanges()也需要它们的SaveChanges。
有了控制器/服务和存储库可用的工作单元,用于使用存储库和更新更改的代码将成为:
var accounts = myRepo.GetAccountsById(accountId).ToList();
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
UnitOfWork.SaveChanges();
有了适当的工作单元,它将看起来更像:
using (var unitOfWork = UnitOfWorkFactory.Create())
{
var accounts = myRepo.GetAccountsById(accountId).ToList(); // Where myRepo can resolve the unit of work via locator.
foreach (var account in accounts)
{
account.Status="INACTIVE";
}
unitOfWork.SaveChanges();
}
这样,如果您要调用不同的存储库来获取数据,执行许多不同的更新,则更改将在最后一次调用中全部提交,并在任何数据出现问题时回滚。