Dataverse WebAPI’de Eşzamanlı Güncelleme Çakışmalarını Yönetmek: ETag ve If-Match
İki kullanıcı aynı kaydı aynı anda güncellerse ne olur? Son yazan kazanır. Birinci kullanıcının değişikliği, ikincisinin update’i tarafından ezilir. Herhangi bir hata veya uyarı mesajı da olmaz. Bu, özellikle entegrasyon senaryolarında (bir sistem WebAPI ile update atarken, aynı anda bir kullanıcı formdan kaydediyorsa) veri kaybına yol açabilir.
Dataverse WebAPI bu sorunu optimistic concurrency (ETag tabanlı versiyon kontrolü) ile çözüyor ama varsayılan olarak aktif değil, sen açıkça talep etmen gerekiyor.
Not: Bu yazı online Dataverse REST API davranışını temel alıyor. On-prem 9.0/9.1’de ConcurrencyBehavior desteği ve davranışı farklılık gösterebilir. Kendi ortamınızda test edebilirsiniz.
ETag Nedir?
Bir kaydı retrieveRecord ile çektiğinde, dönen response @odata.etag alanını içerir. Bu, kaydın o andaki versiyonunun bir “parmak izi”dir. Kaydı güncellerken bu ETag’i If-Match header’ında geri gönderirsen, Dataverse şunu kontrol eder: “Ben bu kaydı okuduğumdan beri değişti mi?”
- Değişmediyse: update normal şekilde uygulanır.
- Değiştiyse: 412 Precondition Failed hatası döner, update reddedilir.
Neden Xrm.WebApi Yetmiyor
Xrm.WebApi.updateRecord fonksiyonu custom header eklemene izin vermiyor. Bu yüzden ETag kontrolü yapmak istiyorsan raw fetch ile kendi isteğini kurman gerekiyor:
async function updateWithConcurrencyCheck(entitySetName, recordId, etag, payload) {
const clientUrl = Xrm.Utility.getGlobalContext().getClientUrl();
const url = `${clientUrl}/api/data/v9.2/${entitySetName}(${recordId})`;
const response = await fetch(url, {
method: "PATCH",
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Content-Type": "application/json",
"If-Match": etag
},
body: JSON.stringify(payload)
});
if (response.status === 412) {
throw new Error("Kayıt siz okuduğunuzdan beri değişmiş. Sayfayı yenileyip tekrar deneyin.");
}
if (!response.ok) {
throw new Error(`Update başarısız: ${response.status}`);
}
}
ETag’i almak için önce kaydı çekerken @odata.etag alanını da alman gerekiyor:
const record = await Xrm.WebApi.retrieveRecord("account", accountId, "?$select=name");
const etag = record["@odata.etag"];
Ne Zaman Gerekli
- Aynı kaydı hem kullanıcıların formdan hem de bir entegrasyonun (Power Automate, harici sistem, batch job) güncellediği senaryolar
- Kritik alanlarda (fiyat, stok, onay durumu) sessiz veri kaybını önlemek istediğin durumlar
- Uzun süre açık kalan formlarda (kullanıcı 20 dakika form açık tutup kaydediyor, o sırada başkası da güncellemiş olabilir)
Esenlikler.