這讓我難過了幾個小時。我正在重寫 Winforms 桌面應用程式以支持 ASP.NET Core 網站。該應用程式將一些表本地存盤在 LiteDB 快取中,并呼叫“使用”DBContext 來獲取資料。
桌面應用程式使用一個TaxAccount抽象類,它由Householdand繼承Business。
在客戶端搜索時,應用呼叫 GetAccount() 以顯示單個用戶帳戶。由于資料庫可能很慢,因此快取會在后臺更新。這是方法。
/// <summary>
/// Retrieve a single account from cache. Later, replace the account object with object from server.
/// </summary>
/// <param name="accountID"></param>
/// <returns></returns>
public TaxAccount GetAccount(int accountID)
{
var accounts = Cache.GetCollection<TaxAccount>();
var account = accounts.FindById(accountID);
if (GetSingleAccountTask == null || GetSingleAccountTask.IsCompleted)
{
GetSingleAccountTask = Task.Run(() => UpdateAccount(account));
}
return account;
void UpdateAccount(TaxAccount account)
{
using (var serverContext = new ApplicationDbContext(ServerOptions))
{
var found = serverContext.Accounts
.Include(X => X.Users)
.FirstOrDefault(X => X.Id == account.Id);
account = found;
if (found != null)
{
accounts.Update(found);
}
else
{
accounts.Delete(account.Id);
}
}
}
}
我想更新TaxAccount物體的單個屬性。為此,我使用Attach(taxAccount),理想情況下應該只更新我想要的屬性。
public void UpdatePrivateLink(TaxAccount taxAccount, string link)
{
// Retrieve collection from cache.
var accounts = Cache.GetCollection<TaxAccount>();
using (var serverContext = new ApplicationDbContext(ServerOptions))
{
// Attach taxAccount to server context.
serverContext.Attach(taxAccount);
taxAccount.PrivateFolderLink = link;
// Update server.
serverContext.SaveChanges();
// Update cache.
accounts.Update(taxAccount);
}
}
這行不通。它創建一個System.InvalidOperationException: The instance of entity type 'Household' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked。但我找不到物體。
這是我嘗試過的事情的清單:
- 將 Get() 查詢更改為 .AsNoTracking() 什么也不做。
- serverContext.ChangeTracker.Clear() 什么都不做。
- serverContext.Entry(taxAccount) 回傳 EntityState.Detached 的狀態
- serverContext.ChangeTracker.ToDebugString() 中沒有元資料
- serverContext.Find(taxAccount.Id) 使資料庫命中
- 使用 accounts.FindbyId(taxAccount.Id) 直接從 LiteDB 快取中檢索會產生相同的錯誤。
更糟糕的是,如果我創建一個new Household()具有相同 id 的,那么它突然就起作用了!
var account = new Household() { Id = taxAccount.Id };
serverContext.Attach(account);
account.PrivateFolderLink = link;
serverContext.SaveChanges();
// Then we have to save in cache.
taxAccount.PrivateFolderLink = link;
accounts.Update(taxAccount);
這種解決方法對我來說毫無意義。為什么 EF 認為taxAccount在全新的 DbContext 上進行跟蹤?為什么我不能在不創建新物件的情況下擺脫這種跟蹤?
不勝感激。
編輯:
- serverContext.Accounts.Local 不包含任何元素。
編輯:這個測驗是仍然失敗的最簡單的實作。
public void AttachTest(int accountID, string link)
{
var accounts = Cache.GetCollection<TaxAccount>();
var acct = accounts.FindById(accountID);
using (var serverContext = new ApplicationDbContext(ServerOptions))
{
serverContext.Attach(acct);
acct.PrivateFolderLink = link;
serverContext.SaveChanges();
}
}
For full debugging: I'm testing on .NET 5.0 console app, the EF version is 5.0.13 hosted on a .NET Standard 2.1 library.
Here's the TaxAccount model I'm using.
public abstract class TaxAccount
{
[Key]
public int Id { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public bool Archived { get; set; } = false;
public string PrivateFolderLink { get; set; }
public List<AppUser> Users { get; set; }
}
public class Household : TaxAccount
{
}
public class Business : TaxAccount
{
[EmailAddress, MaxLength(500)]
public string Email { get; set; }
[MaxLength(500)]
public string Phone { get; set; }
[MaxLength(1000)]
public string Address { get; set; }
}
In my ApplicationDbContext, the only fluent logic is to mark the discriminator.
// Tax Account abstract class.
builder.Entity<TaxAccount>().HasDiscriminator()
.HasValue<Household>(nameof(Household))
.HasValue<Business>(nameof(Business))
.IsComplete(true);
builder.Entity<TaxAccount>()
.Property("Discriminator")
.HasMaxLength(50);
uj5u.com熱心網友回復:
經過今天早上的一些實驗,我想通了!幸運的是,它與快取或其他 DbContext 無關!
該類TaxAccount有一個List<AppUser>,它有一個屬性Accounts,它是一個List<TaxAccount>。這種多對多關系在Attach()EF Core 處理不好的方法中創建了一個回圈。為了證明這一點,我寫了兩個測驗,這兩個都有效!
public void AttachTest(int accountID, string link)
{
var accounts = Cache.GetCollection<TaxAccount>();
var acct = accounts.FindById(accountID);
// We set the Users relation to be null.
acct.Users = null;
using (var serverContext = new ApplicationDbContext(ServerOptions))
{
serverContext.Attach(acct);
acct.PrivateFolderLink = link;
serverContext.SaveChanges();
}
}
public void AttachTestNullUsers(int accountID, string link)
{
var accounts = Cache.GetCollection<TaxAccount>();
var acct = accounts.FindById(accountID);
// For each user, the Accounts is null, this also breaks the relationship.
acct.Users.ForEach(X => X.Accounts = null);
using (var serverContext ...
}
現在,有兩個后續問題:
- 這比創建一個新實體
TaxAccount并附加它更好嗎? - 這對于多對多的關系是如何處理
INSERT和UPDATE操作的?
可能不是。設定
acct.Users = null是一個意想不到的結果,一旦命令完成,很容易忘記恢復這種關系。OTOH,初始化 anew TaxAccount(taxAccount.Id)是一個輕量級的操作,對基礎物件沒有影響。糟糕的是,經過一些測驗,
Attach()如果要添加或洗掉多對多物件,這不是一個好主意。查找然后更新是您最好的選擇。
uj5u.com熱心網友回復:
來自微軟維基:
默認情況下,使用 >Modified 狀態開始跟蹤給定物體和可從給定物體訪問的條目,但請參閱下文了解將使用不同狀態的情況。
通常,在呼叫 SaveChanges() 之前不會執行資料庫互動。
我的猜測是,在保存更改之前會跟蹤帳戶。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/408613.html
標籤:
