サービスオブジェクトUpdate
があります
public bool Update(object original, object modified)
{
var originalClient = (Client)original;
var modifiedClient = (Client)modified;
_context.Clients.Update(originalClient); //<-- throws the error
_context.SaveChanges();
//Variance checking and logging of changes between the modified and original
}
これが私がこのメソッドを呼び出す場所です:
public IActionResult Update(DetailViewModel vm)
{
var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
// Changing the modifiedClient here
_service.Update(originalClient, modifiedClient);
}
GetAsNotTracking
メソッドは次のとおりです。
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}
Fetch
メソッド:
public object Fetch(string id)
{
long fetchId;
long.TryParse(id, out fetchId);
return GetClientQueryableObject(fetchId).FirstOrDefault();
}
GetClientQueryableObject
:
private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.Include(x => x.Industry)
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType);
}
何か案は?
私は次の記事/議論を見ました。利用できない: ASP.NET GitHub Issue 3839
UPDATE:
GetAsNoTracking
の変更点は次のとおりです。
public Client GetAsNoTracking(long id)
{
return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}
GetClientQueryableObjectAsNoTracking
:
private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
return _context.Clients
.Where(x => x.Id == searchId)
.Include(x => x.Opportunities)
.ThenInclude(x => x.BusinessUnit)
.AsNoTracking()
.Include(x => x.Opportunities)
.ThenInclude(x => x.Probability)
.AsNoTracking()
.Include(x => x.Industry)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.User)
.AsNoTracking()
.Include(x => x.Activities)
.ThenInclude(x => x.ActivityType)
.AsNoTracking();
}
EFトラックシステムをオーバーライドせずに、「ローカル」エントリを切り離し、保存する前に更新されたエントリを添付することもできます。
//
var local = _context.Set<YourEntity>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
// check if local is not null
if (!local.IsNull()) // I'm using a extension method
{
// detach
_context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;
// save
_context.SaveChanges();
PDATE:コードの冗長性を回避するために、拡張メソッドを実行できます。
public static void DetachLocal<T>(this DbContext context, T t, string entryId)
where T : class, IIdentifier
{
var local = context.Set<T>()
.Local
.FirstOrDefault(entry => entry.Id.Equals(entryId));
if (!local.IsNull())
{
context.Entry(local).State = EntityState.Detached;
}
context.Entry(t).State = EntityState.Modified;
}
IIdentifier
インターフェイスには、Id
文字列プロパティのみがあります。
エンティティが何であれ、コンテキストでこのメソッドを使用できます。
_context.DetachLocal(tmodel, id);
_context.SaveChanges();
実際にモデルに加えられた変更を追跡するだけで、追跡されていないモデルを実際にメモリに保持するのではないように聞こえます。問題を完全に取り除く代替アプローチを提案できますか?
EFは変更を自動的に追跡します。組み込みのロジックを使用してみてはどうですか?
DbContext
のOvverride SaveChanges()
。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<Client>())
{
if (entry.State == EntityState.Modified)
{
// Get the changed values.
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
return base.SaveChanges();
}
良い例はここにあります:
MVCおよびEntity Frameworkによる監査ログ/変更履歴の実装
EDIT:Client
は簡単にインターフェースに変更できます。 ITrackableEntity
としましょう。これにより、ロジックを集中化して、特定のインターフェイスを実装するすべてのエンティティに対するすべての変更を自動的に記録できます。インターフェイス自体には、特定のプロパティはありません。
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
{
if (entry.State == EntityState.Modified)
{
// Same code as example above.
}
}
return base.SaveChanges();
}
また、実際にSaveChanges()をオーバーライドする代わりに、 eranga がサブスクライブすることをお勧めします。
私の場合、テーブルのid列はIdentity列として設定されていません。
データが一度変更されると、テーブルをトレースしてはいけないことに気づくでしょう。たとえば、tiggerを使用してテーブル更新ID([キー])をトレースすると、同じIDを取得して問題が発生します。
ああ、これは私を手に入れ、私はそれをトラブルシューティングするのに多くの時間を費やしました。問題は、私のテストがParellel(XUnitのデフォルト)で実行されていたことです。
テストを順番に実行するために、各クラスに次の属性を追加しました。
[Collection("Sequential")]
これは私がそれをどのように解決したかです:ユニットテストを(並列ではなく)連続的に実行する
GenFuでEF In Memoryコンテキストのモックアップを作成します。
private void CreateTestData(TheContext dbContext)
{
GenFu.GenFu.Configure<Employee>()
.Fill(q => q.EmployeeId, 3);
var employee = GenFu.GenFu.ListOf<Employee>(1);
var id = 1;
GenFu.GenFu.Configure<Team>()
.Fill(p => p.TeamId, () => id++).Fill(q => q.CreatedById, 3).Fill(q => q.ModifiedById, 3);
var Teams = GenFu.GenFu.ListOf<Team>(20);
dbContext.Team.AddRange(Teams);
dbContext.SaveChanges();
}
私が差し引くことができるものからテストデータを作成するとき、それは2つのスコープで生きていました(チームテストの実行中に従業員のテストで一度):
public void Team_Index_should_return_valid_model()
{
using (var context = new TheContext(CreateNewContextOptions()))
{
//Arrange
CreateTestData(context);
var controller = new TeamController(context);
//Act
var actionResult = controller.Index();
//Assert
Assert.NotNull(actionResult);
Assert.True(actionResult.Result is ViewResult);
var model = ModelFromActionResult<List<Team>>((ActionResult)actionResult.Result);
Assert.Equal(20, model.Count);
}
}
両方のテストクラスをこの順次コレクション属性でラップすると、明らかな競合が解消されました。
[Collection("Sequential")]
追加の参照:
https://github.com/aspnet/EntityFrameworkCore/issues/734
EF Core 2.1メモリ内DBでレコードが更新されない
http://www.jerriepelser.com/blog/unit-testing-aspnet5-entityframework7-inmemory-database/
http://gunnarpeipman.com/2017/04/aspnet-core-ef-inmemory/
https://github.com/aspnet/EntityFrameworkCore/issues/12459
単体テストでEF Core SqlLiteを使用する場合の追跡問題の防止