Db'de bir şey olup olmadığını kontrol etmeli ve hızlıca başarısız mı yoksa db istisnasını bekleyeyim mi?


32

İki sınıfa sahip olmak:

public class Parent 
{
    public int Id { get; set; }
    public int ChildId { get; set; }
}

public class Child { ... }

Atama yaparken önce DB'de ChildIdolup Parentolmadığını kontrol etmeli miyim yoksa DB'nin bir istisna atmasını beklemeli miyim?

Örneğin (Entity Framework Çekirdeğini kullanarak):

NOT: Bu tür kontroller resmi Microsoft'un dokümanlarında bile İNTERNET ÜZERİNDEKİ TÜMLERDİR: https://docs.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using- mvc / processing-eşzamanlılık-ile-varlık-çerçeve-bir-asp-net-mvc-uygulama # departman-denetleyicisi-değişiklik ama ek istisna işlemeSaveChanges

Ayrıca, bu kontrolün asıl amacının API mesajını kullananlara dostça bir mesaj ve bilinen HTTP durumu döndürmek ve veritabanı istisnalarını tamamen görmezden gelmemek olduğunu unutmayın. Ve atılmasına tek yer istisna içinde olduğunu SaveChangesveya SaveChangesAsyncçağrı Aradığınızda yüzden herhangi bir istisna olmayacaktır ... FindAsyncya Any. Öyleyse eğer çocuk varsa, ancak daha önce silinmişse SaveChangesAsynceşzamanlılık istisnası atılır.

Bunu, foreign key violation"{kimliği olan {parent.ChildId} çocuğu bulunamadı" ifadesini görüntülemek için istisnaların biçimlendirilmesinin daha zor olacağı gerçeği nedeniyle yaptım .

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    // is this code redundant?
   // NOTE: its probably better to use Any isntead of FindAsync because FindAsync selects *, and Any selects 1
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null)
       return NotFound($"Child with id {parent.ChildId} could not be found.");

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        

    return parent;
}

e karşı:

public async Task<ActionResult<Parent>> CreateParent(Parent parent)
{
    _db.Parents.Add(parent);
    await _db.SaveChangesAsync();  // handle exception somewhere globally when child with the specified id doesn't exist...  

    return parent;
}

Postgres'teki ikinci örnek 23503 foreign_key_violationhata verecektir : https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html

EF gibi ORM'deki istisnaların bu şekilde ele alınmasının olumsuz tarafı, yalnızca belirli bir veritabanı arka ucuyla çalışacak olmasıdır. SQL sunucusuna ya da başka bir şeye geçmek istemeniz durumunda, hata kodu değişeceğinden bu artık işe yaramayacak.

Özel durumun son kullanıcı için doğru şekilde biçimlendirilmemesi, hiç kimsenin istemediği ancak geliştiricilerin görmesini istemediğiniz bazı şeyleri ortaya çıkarabilir.

İlgili:

https://stackoverflow.com/questions/6171588/preventing-race-condition-of-if-exists-update-else-insert-in-entity-framework

https://stackoverflow.com/questions/4189954/implementing-if-not-exists-insert-using-entity-framework-without-race-conditions

https://stackoverflow.com/questions/308905/should-there-be-a-transaction-for-read-queries


2
Araştırmanızı paylaşmak herkese yardımcı olur . Bize ne denediğinizi ve neden ihtiyaçlarınızı karşılamadığını söyleyin. Bu, kendinize yardım etmek için zaman harcadığınızı, bariz cevapları tekrar etmemizi önlediğini ve hepsinden daha belirgin ve alakalı bir cevap almanıza yardımcı olduğunu gösteriyor. Ayrıca bkz. Nasıl
gnat

5
Diğerlerinin de belirttiği gibi, NotFound denetiminizle aynı anda bir kayıt eklenebilir veya silinebilir. Bu nedenle, önce kontrol kabul edilemez bir çözüm gibi görünüyor. Diğer veritabanı backends taşınabilir olmayacaktır Postgres özgü istisna durumları yazma konusunda endişeleriniz varsa, temel işlevi veritabanına özel sınıflar (SQL Postgrees, vs) tarafından uzatılabilir şekilde istisna işleyicisi yapılandırmaya çalışın
billrichards

3
Yorumlara bakarak şunu söylemeliyim: platitudelarda düşünmeyi bırak . "Hızlı başarısız", kör bir şekilde takip edilebilecek veya izlenebilecek bağlam dışı bir kural değildir. Bu bir kuraldır. Her zaman gerçekte ne elde etmeye çalıştığınızı analiz edin ve daha sonra herhangi bir tekniği bu hedefe ulaşmanıza yardımcı olup olmadığına bakarak değerlendirin. "Hızlı başarısız", istenmeyen yan etkileri önlemenize yardımcı olur. Ve ayrıca, "hızlı başarısız" ifadesi gerçekten, "bir sorun olduğunu tespit ettiğiniz anda başarısız olunması" anlamına gelir. Her iki teknik de bir sorun tespit edilir edilmez başarısız olur, bu nedenle diğer hususlara da bakmalısınız.
jpmc26

1
@Konrad istisnaların bununla ne ilgisi var? Yarış koşullarını kodunuzda yaşayan bir şey olarak düşünmeyi bırakın: bu evrenin bir özelliğidir. Her şey, bir kaynağa dokunan herhangi bir şeyin (örneğin doğrudan bellek erişimi, paylaşılan bellek, veritabanı, REST API, dosya sistemi vb.) Bir defadan fazla kontrol etmemesi ve değişmemesini beklemesi muhtemel bir yarış koşuluna sahiptir. Heck, bununla istisnalar bile olmayan C ile ilgileniyoruz . Şubelerden en az birinin söz konusu kaynağın durumu ile karışıp karışmadığını kontrol etmediğiniz bir kaynağın durumuna asla dalmayın.
Jared Smith

1
@DanielPryden Soruma, veritabanı istisnalarını ele almak istemediğimi söylemedim (istisnaların kaçınılmaz olduğunu biliyorum). Birçok insanın yanlış anladığını düşünüyorum, Web API'm için (son kullanıcıların okuması için) arkadaşça bir hata mesajı vermek istedim Child with id {parent.ChildId} could not be found.. Ve "Yabancı anahtar ihlali" biçimlendirmek bu durumda daha kötü olduğunu düşünüyorum.
Konrad

Yanıtlar:


3

Kafası karışık bir soru, ancak EVET ilk kontrol etmeli ve yalnızca bir DB istisnasını ele almamalısınız.

Öncelikle, örneğinizde SQL'i çalıştırmak için doğrudan veritabanında EF kullanarak veri katmanındasınız. Kodunuz çalışan eşdeğerdir

select * from children where id = x
//if no results, perform logic
insert into parents (blah)

Önerdiğiniz alternatif şudur:

insert into parents (blah)
//if exception, perform logic

Koşullu mantığı yürütmek için istisnayı kullanmak yavaş ve evrensel olarak kaşlarını çattı.

Bir yarış koşulunuz var ve bir işlem kullanmalısınız. Ancak bu tamamen kodda yapılabilir.

using (var transaction = new TransactionScope())
{
    var child = await _db.Children.FindAsync(parent.ChildId);
    if (child == null) 
    {
       return NotFound($"Child with id {parent.ChildId} could not be found.");
    }

    _db.Parents.Add(parent);    
    await _db.SaveChangesAsync();        
    transaction.Complete();

    return parent;
}

Anahtar şey kendinize sormaktır:

“Bu durumun gerçekleşmesini bekliyor musunuz?”

Eğer değilse, o zaman emin, uzağa yerleştirin ve bir istisna atmak. Ancak, istisnayı, oluşabilecek diğer hatalar gibi halledin.

Bunun gerçekleşmesini beklerseniz, istisnai DEĞİLDİR ve çocuğun önce var olup olmadığını kontrol etmelisiniz, eğer değilse uygun uygun mesajla cevap verin.

Düzenleme - Bu göründüğü kadar çok tartışma var. Aşağı oy vermeden önce düşünün:

A. Ya iki FK kısıtlaması varsa? Hangi nesnenin eksik olduğunu bulmak için istisna mesajının ayrıştırılmasını savunur musunuz?

B. Bir özeti varsa, yalnızca bir SQL deyimi çalıştırılır. Sadece ikinci bir sorgunun ekstra masrafına neden olan isabet.

C. Genellikle Kimliği Size bir tane biliyorum bir durum hayal etmek zor, bir yedek anahtar olacağını ve oldukça emin DB üzerinde bulunuyor değildir. Kontrol etmek tuhaf olurdu. Ancak, kullanıcının yazdığı doğal bir anahtar ise ne olur? Bu olmamak için yüksek bir şans olabilirdi


Yorumlar genişletilmiş tartışmalar için değildir; bu konuşma sohbete taşındı .
maple_shaft

1
Bu tamamen yanlış ve yanıltıcı! Her zaman karşı mücadele etmek zorunda olduğum kötü profesyoneller üreten bu gibi cevaplar. SELECT hiçbir zaman bir tabloyu kilitlemez; bu nedenle SELECT ve INSERT, UPDATE veya DELTE arasında kayıt değişebilir. Bu yüzden kötü kötü yazılım tenezzül ve üretimde bir kaza olmasını bekliyor.
Daniel Lobo

1
@DanielLobo hareketleri düzeltiyor ki
Ewan

1
bana inanmıyorsan test et
Ewan

1
@yusha Burada kod var
Ewan

111

Benzersiz olup olmadığını kontrol etmek ve sonra ayar yapmak antipattern; Kimliğin kontrol zamanı ile yazma zamanı arasında eşzamanlı olarak yerleştirilmesi her zaman olabilir. Veritabanları, bu problemi kısıtlamalar ve işlemler gibi mekanizmalar aracılığıyla ele almak için donatılmıştır; çoğu programlama dili değildir. Bu nedenle, veri tutarlılığına değer veriyorsanız, bunu uzmana bırakın (veri tabanı), yani ekleme yapın ve gerçekleşirse bir istisna yakalayın.


34
kontrol ve başarısızlık sadece "denemek" ve en iyisini ummaktan daha hızlı değildir. Eski, sisteminiz tarafından gerçekleştirilecek ve gerçekleştirilecek 2 işlemi, DB tarafından 2 işlemi gerçekleştirirken, en sonuncusu bunlardan yalnızca birini ifade eder. Kontrol DB sunucusuna verilmiştir. Aynı zamanda ağa bir daha az sıçramak ve DB'nin katılması gereken bir daha az görev anlamına gelir. DB'ye bir sorgu daha uygun olduğunu düşünebiliriz, ancak büyük düşünmeyi unuturuz. Soruşturmayı yüzlerce kez ve daha fazla tetikleyen yüksek eşzamanlılık içinde düşünün. Tüm trafiği DB'ye aktarabilirdi. Bu konuda karar vermek size kalmış.
Laiv

6
@Konrad Benim pozisyonum, varsayılan olarak doğru seçimin kendi başına başarısız olacak bir sorgu olduğu ve kendini haklı çıkarmak için ispat külfetine sahip ayrı sorgu ön avlanma yaklaşımı olması. Bunu: gelince "Sorun haline" olan işlemleri kullanarak veya aksi takdirde ToCToU hatalara karşı güvenli olmasını sağlamak , doğru mu? Bana yazdığın koddan açıkça anlaşılmıyor, ama yapmıyorsan, bir patlama bombasının gerçekte patlaması çok uzun zaman önce bir problem haline gelmesi gibi bir problem haline geldi bile.
mtraceur

4
@Konrad EF Çekirdeği hem çekinizi hem de girişi tek bir işleme yerleştirdikten hemen sonra kullanmayacaktır, açıkça talep etmeniz gerekecektir. İşlem olmadan, ilk önce kontrol etmek anlamsızdır, çünkü veritabanı durumu kontrol arasında değişebilir ve yine de ekleyebilir. Bir işlemde bile, veritabanının ayaklarınızın altında değişmesini engellemeyebilirsiniz. Birkaç yıl önce EF ile Oracle kullanarak bir sorunla karşılaştık; burada db bunu desteklese de, Varlık bir işlemdeki okunan kayıtların kilitlenmesini tetiklemiyordu ve yalnızca ekleme işlem olarak kabul edildi.
Bay

3
"Eşsizliği kontrol etmek ve sonra ayar antipattern" dir. Başka hiçbir değişikliğin olmadığını kabul edip edemeyeceğinize ve bu durumun olmadığı zaman kontrolün daha yararlı sonuçlar (sadece okuyucu için bir anlam ifade eden bir hata mesajı bile olsa) üretip üretmediğine kesinlikle bağlıdır. Veri tabanı eşzamanlı web isteklerini yerine getirirken, hayır, diğer değişikliklerin gerçekleşmeyeceğini garanti edemezsiniz, ancak bunun makul bir varsayım olduğu durumlar vardır.
jpmc26

5
İlk önce benzersizliği kontrol etmek olası arızaları ele alma ihtiyacını ortadan kaldırmaz. Öte yandan, bir eylem bunlardan herhangi genellikle daha iyi olabilir performans eylemler daha başlamadan önce başarılı olmak için tüm olasılıkları olup olmadığını kontrol etmek, çeşitli işlemleri gerçekleştirirken gerektirecektir eğer büyük olasılıkla geri çekilmesi veya toplanması gerekmektedir. İlk kontrollerin yapılması, geri alma işleminin gerekli olduğu tüm durumlardan kaçınmayabilir, ancak bu vakaların sıklığını azaltmaya yardımcı olabilir.
supercat

38

Bence "hızlı başarısız" dediğiniz şey dediğim şey aynı değil.

Veritabanına değişiklik yapmasını ve arızayı ele almasını söylemek, bu çok hızlı. Yolunuz karmaşık, yavaş ve özellikle güvenilir değil.

Bu tekniğin hızlı başarısız değil, “ön kontrol”. Bazen iyi nedenler olabilir, ancak veritabanını kullanırken.


1
Bir sınıf diğerine bağlı olduğunda 2. sorguya ihtiyaç duyduğunuz durumlar vardır, bu nedenle böyle durumlarda başka seçeneğiniz yoktur.
Konrad

4
Ama burada değil. Ve veritabanı sorguları oldukça akıllıca olabilir, bu yüzden genellikle “seçenek yok” dan şüpheleniyorum.
gnasher729

1
Bence sadece uygulamaya bağlı, sanırım sadece birkaç kullanıcı için oluşturursanız bir fark yaratmamalı ve kod 2 sorgu ile daha okunaklı olmalı.
Konrad

21
DB'nizin tutarsız veri depoladığını varsayıyorsunuz. Başka bir deyişle, DB'nize ve verilerin tutarlılığına güvenmiyorsunuz. Öyleyse, gerçekten büyük bir probleminiz var ve çözümünüz bir dolaşıma giriyor. Palyatif bir çözüm, er ya da geç ertelenmek üzere geldi. Kontrol ve yönetiminizden bir veri tabanı almak zorunda kaldığınız durumlar olabilir. Diğer uygulamalardan. Bu gibi durumlarda, böyle doğrulamalar dikkate alacağım. Her durumda, @gnasher haklıdır, sizinki hızlı bir şekilde başarısız olmaz veya hızlı bir şekilde başarısız olarak anladıklarımız değildir.
Laiv

15

Bu bir yorum olarak başladı, ancak çok büyüdü.

Hayır, diğer cevapların belirttiği gibi, bu kalıp kullanılmamalıdır. *

Eşzamansız bileşenler kullanan sistemlerle uğraşırken, her zaman veritabanının (veya dosya sistemi veya diğer eşzamansız sistemin) kontrol ile değişim arasında değişebileceği bir yarış durumu olacaktır . Bu tür bir kontrol sadece güvenilir bir yol değildir, ele almak istemediğiniz hata türünü önler.
Yeterli olmamaktan daha kötüsü, bir bakışta yinelenen kayıt hatasının yanlış bir güvenlik hissi vermesini engellemesi gerektiği izlenimini veriyor.

Yine de hatayı işlemeye ihtiyacınız var.

Yorumlarda, birden fazla kaynaktan veriye ihtiyacınız olup olmadığını sordunuz.
Hala hayır.

Kontrol etmek istediğiniz şey daha karmaşık hale gelirse, temel sorun ortadan kalkmaz.

Yine de yine de hata işlemeye ihtiyacınız var.

Bu kontrol, korumaya çalıştığınız belirli bir hatayı önlemenin güvenilir bir yolu olsa da, başka hatalar da ortaya çıkabilir. Veritabanına olan bağlantıyı kaybederseniz veya alanı biterse ne olur?

Yine de büyük olasılıkla yine de diğer veritabanı ile ilgili hata işlemlerine ihtiyacınız var . Bu özel hatanın ele alınması muhtemelen küçük bir parçası olmalıdır.

Neyin değişeceğini belirlemek için verilere ihtiyacınız varsa, onu açıkça bir yerden toplamanız gerekir. (hangi araçları kullandığınıza bağlı olarak, bunları toplamak için ayrı sorgulardan daha iyi yollar olabilir.) Topladığınız verileri incelerken, değişiklik yapmanıza gerek kalmayacağına karar verirseniz, büyük, değişiklik. Bu tespit hata işleme konusundaki endişelerden tamamen ayrıdır.

Yine de yine de hata işlemeye ihtiyacınız var.

Tekrarlı olduğumu biliyorum ama bunu açıklığa kavuşturmanın önemli olduğunu düşünüyorum. Bu pisliği daha önce temizledim.

Sonunda başarısız olur. Başarısız olduğunda, dibe ulaşmak zor ve zaman alıcı olacaktır. Yarış koşullarından kaynaklanan sorunların çözülmesi zordur. Tutarlı bir şekilde gerçekleşmezler, bu nedenle izole olarak çoğaltılması zor veya hatta imkansız olacaktır. Başlamak için doğru bir hata işleme koymadınız, bu yüzden muhtemelen devam edecek çok fazla bir şey olmayacak: Belki de son bir kullanıcının kriptik bir metinle ilgili raporu (ilk başta görmesini engellemeye çalıştığınız). Belki de, o fonksiyonu açıkça gösteren bir yığın izi, ona açıkça baktığınızda, hatanın bile mümkün olabileceğini inkar eder.

* Uygulamanın pahalı işleri çoğaltmasını önlemek gibi bu kontrolleri yapmak için geçerli ticari nedenler olabilir, ancak uygun hata yönetimi için uygun bir yedek değildir.


2

Burada not edilmesi gereken ikincil bir şey olduğunu düşünüyorum - bunun istediğiniz nedenlerden biri, kullanıcının görmesi için bir hata mesajı biçimlendirebilmenizdir.

İçtenlikle şunu tavsiye ederim:

a) son kullanıcıya, meydana gelen her hata için aynı genel hata mesajını gösterin .

b) gerçek istisnayı, yalnızca geliştiricilerin erişebileceği bir yerde (bir sunucudaysa) veya size hata bildirme araçlarıyla (istemci dağıtılmışsa) gönderilebilecek bir yerde günlüğe kaydedin

c) daha yararlı bilgiler ekleyemediğiniz sürece, kaydettiğiniz hata istisna ayrıntılarını denemeyin ve biçimlendirmeyin. Yanlışlıkla bir sorunu izlemek için kullanabileceğiniz bir yararlı bilgiyi yanlışlıkla 'biçimlendirmek' istemezsiniz.


Kısacası, istisnalar çok faydalı teknik bilgilerle doludur. Bunların hiçbiri son kullanıcı için olmamalı ve bu bilgiyi kendi sorumluluğunuzda kaybettiniz.


2
msgstr "son kullanıcıya, meydana gelen her hata için aynı genel hata mesajını göster." asıl sebep buydu, son kullanıcı istisnasını biçimlendirmek korkunç bir şey gibi görünüyor ..
Konrad

1
Herhangi bir makul veritabanı sisteminde, bir şeyin neden başarısız olduğunu programlı olarak öğrenebilmelisiniz. Bir istisna mesajının ayrıştırılması gerekli olmamalıdır. Ve daha genel olarak: kim kullanıcıya bir hata mesajı gösterilmesi gerektiğini söylüyor? İlk ekleme işlemini başaramazsınız ve başarılı olana kadar bir döngüde yeniden deneyin (veya bir miktar yeniden deneme veya süre sınırına kadar). Ve aslında, geri çekilme ve yeniden deneme, eninde sonunda uygulamak isteyeceğiniz bir şeydir.
Daniel Pryden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.