İyimser Eşzamanlılık Uygulama (VB)
tarafından Scott Mitchell
Birden çok kullanıcının verileri düzenlemesine izin veren bir web uygulaması için, iki kullanıcının aynı verileri aynı anda düzenleme riski vardır. Bu öğreticide bu riski işlemek için iyimser eşzamanlılık denetimi uygulayacağız.
Giriş
Kullanıcıların yalnızca verileri görüntülemesine izin veren web uygulamaları veya yalnızca verileri değiştirebilen tek bir kullanıcı içerenler için, iki eşzamanlı kullanıcının yanlışlıkla bir başkasının değişikliklerinin üzerine yazması tehdidi yoktur. Ancak, birden çok kullanıcının verileri güncelleştirmesine veya silmesine olanak sağlayan web uygulamaları için, bir kullanıcının değişikliklerinin başka bir eşzamanlı kullanıcınınkiyle çakışma olasılığı vardır. Herhangi bir eşzamanlılık ilkesi olmadığında, iki kullanıcı aynı anda tek bir kaydı düzenlerken, son değişikliklerini yapan kullanıcı ilk kayıt tarafından yapılan değişiklikleri geçersiz kılar.
Örneğin, Jisun ve Sam adlı iki kullanıcının uygulamamızda ziyaretçilerin gridview denetimi aracılığıyla ürünleri güncelleştirmesine ve silmesine izin veren bir sayfayı ziyaret ettiğini düşünün. Her ikisi de GridView'da Düzenle düğmesine aynı anda tıklar. Jisun, ürün adını "Chai Tea" olarak değiştirir ve Güncelleştir düğmesine tıklar. Net sonuç, veritabanına gönderilen ve ürünün güncelleştirilebilir alanlarının tümünü ayarlayan bir deyimdir (Jisun yalnızca bir UPDATE
alanı güncelleştirmiş olsa bile). ProductName
Bu noktada, veritabanı bu belirli bir ürün için "Chai Tea" değerlerine, İçecekler kategorisine, egzotik sıvılar kategorisine vb. sahiptir. Ancak Sam'in ekranındaki GridView yine de düzenlenebilir GridView satırında ürün adını "Chai" olarak gösterir. Jisun'un değişiklikleri işlendikten birkaç saniye sonra Sam kategoriyi Condiments olarak güncelleştirir ve Güncelleştir'e tıklar. Bu, veritabanına gönderilen ve ürün adını "Chai" CategoryID
olarak, karşılık gelen İçecek kategorisi kimliğine ayarlayan bir UPDATE
deyimle sonuçlanabilir. Jisun'un ürün adında yapılan değişikliklerin üzerine yazıldı. Şekil 1'de bu olay serisi grafik olarak gösterilmektedir.
Şekil 1: İki Kullanıcı Bir Kaydı Aynı Anda Güncelleştirdiğinde, Bir Kullanıcının Diğer Kullanıcıların Üzerine Yazma Değişikliklerini Görme Olasılığı Vardır (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Benzer şekilde, iki kullanıcı bir sayfayı ziyaret ettiğinde, bir kullanıcı başka bir kullanıcı tarafından silindiğinde kaydı güncelleştirmenin ortasında olabilir. Veya bir kullanıcı bir sayfayı yüklediğinde ve Sil düğmesine tıkladığında, başka bir kullanıcı bu kaydın içeriğini değiştirmiş olabilir.
Kullanılabilir üç eşzamanlılık denetimi stratejisi vardır:
- Hiçbir Şey Yapma - eşzamanlı kullanıcılar aynı kaydı değiştiriyorsa, son işlemenin kazanmasına izin verin (varsayılan davranış)
- İyimser Eşzamanlılık - her zaman eşzamanlılık çakışmaları olsa da, bu tür çakışmaların büyük çoğunluğunun ortaya çıkmayacağını varsayalım; bu nedenle, bir çakışma oluşursa, başka bir kullanıcı aynı verileri değiştirdiği için değişikliklerinin kaydedilemediğini kullanıcıya bildirmeniz yeterlidir
- Kötümser Eşzamanlılık - eşzamanlılık çakışmalarının yaygın olduğunu ve kullanıcıların değişikliklerinin başka bir kullanıcının eşzamanlı etkinliği nedeniyle kaydedilmediğinin söylenmesine izin vermeyeceklerini varsayalım; bu nedenle, bir kullanıcı bir kaydı güncelleştirmeye başladığında, kaydı kilitleyerek kullanıcı değişikliklerini yürütene kadar diğer kullanıcıların bu kaydı düzenlemesini veya silmesini engelleyin
Şimdiye kadarki tüm öğreticilerimiz varsayılan eşzamanlılık çözümleme stratejisini kullandı. Yani son yazma işleminin kazanmasına izin verdik. Bu öğreticide iyimser eşzamanlılık denetiminin nasıl uygulandığını inceleyeceğiz.
Not
Bu öğretici serisinde kötümser eşzamanlılık örneklerine bakmayacağız. Kötümser eşzamanlılık nadiren kullanılır çünkü bu tür kilitler, düzgün şekilde iptal edilmediği takdirde diğer kullanıcıların verileri güncelleştirmesini engelleyebilir. Örneğin, bir kullanıcı bir kaydı düzenlemek üzere kilitler ve kilidi açmadan bir gün önce ayrılırsa, özgün kullanıcı geri dönüp güncelleştirmesini tamamlayana kadar başka hiçbir kullanıcı bu kaydı güncelleştiremez. Bu nedenle, kötümser eşzamanlılığın kullanıldığı durumlarda, genellikle ulaşılırsa kilidi iptal eden bir zaman aşımı olur. Kullanıcı sipariş işlemini tamamlarken belirli bir oturma konumunu kısa bir süre kilitleyen bilet satış web siteleri kötümser eşzamanlılık denetimi örneğidir.
1. Adım: İyimser Eşzamanlılığın Nasıl Uygulandığına Bakma
İyimser eşzamanlılık denetimi, güncelleştirilen veya silinen kaydın, güncelleştirme veya silme işlemi başladığındakiyle aynı değerlere sahip olduğundan emin olarak çalışır. Örneğin, düzenlenebilir bir GridView'da Düzenle düğmesine tıklandığında, kaydın değerleri veritabanından okunur ve TextBoxes ve diğer Web denetimlerinde görüntülenir. Bu özgün değerler GridView tarafından kaydedilir. Daha sonra, kullanıcı değişikliklerini yaptıktan ve Güncelleştir düğmesine tıkladıktan sonra, özgün değerler ve yeni değerler İş Mantığı Katmanı'na ve ardından Veri Erişim Katmanı'na gönderilir. Veri Erişim Katmanı, yalnızca kullanıcının düzenlemeye başladığı özgün değerler veritabanındaki değerlerle aynıysa kaydı güncelleştirecek bir SQL deyimi vermelidir. Şekil 2'de bu olay dizisi gösterilmektedir.
Şekil 2: Güncelleştirme veya Silme işleminin Başarılı Olması için Özgün Değerlerin Geçerli Veritabanı Değerlerine Eşit Olması Gerekir (Tam boyutlu görüntüyü görüntülemek için tıklayın)
İyimser eşzamanlılık uygulamaya yönelik çeşitli yaklaşımlar vardır (çeşitli seçeneklere kısa bir bakış için bkz . Peter A. Bromberg'inİyimser Eşzamanlılık Güncelleştirme Mantığı ). ADO.NET Yazılan Veri Kümesi, yalnızca onay kutusunun işaretiyle yapılandırılabilir bir uygulama sağlar. Typed DataSet içindeki bir TableAdapter için iyimser eşzamanlılık etkinleştirildiğinde TableAdapter'ın UPDATE
ve DELETE
deyimleri, yan tümcesindeki WHERE
tüm özgün değerlerin karşılaştırmasını içerecek şekilde genişletildi. Örneğin aşağıdaki UPDATE
deyim, yalnızca geçerli veritabanı değerleri GridView'da kaydı güncelleştirirken alınan değerlere eşitse ürünün adını ve fiyatını güncelleştirir. @ProductName
ve @UnitPrice
parametreleri kullanıcı tarafından girilen yeni değerleri içerirken @original_ProductName
ve @original_UnitPrice
Düzenle düğmesine tıklandığında GridView'a yüklenen değerleri içerir:
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Not
Bu UPDATE
deyim okunabilirlik için basitleştirilmiştir. Pratikte yan UnitPrice
tümcesinde WHERE
denetim daha fazla yer alabilir çünkü UnitPrice
s içerebilir NULL
ve her zaman False döndürüyorsa NULL = NULL
denetim (bunun yerine kullanmanız IS NULL
gerekir).
Farklı bir temel alınan UPDATE
deyimi kullanmanın yanı sıra, bir TableAdapter'ın iyimser eşzamanlılık kullanacak şekilde yapılandırılması, veritabanı doğrudan yöntemlerinin imzasını da değiştirir. İlk öğreticimiz olan Veri Erişim Katmanı Oluşturma bölümünde db doğrudan yöntemlerinin giriş parametreleri olarak skaler değerlerin listesini kabul eden yöntemler olduğunu hatırlayın (kesin olarak türü belirlenmiş bir DataRow veya DataTable örneği yerine). İyimser eşzamanlılık kullanılırken, veritabanı doğrudan Update()
ve Delete()
yöntemleri özgün değerlerin giriş parametrelerini de içerir. Ayrıca, toplu güncelleştirme desenini kullanmaya yönelik BLL kodu ( Update()
skaler değerler yerine DataRows ve DataTable'ları kabul eden yöntem aşırı yüklemeleri) de değiştirilmelidir.
Mevcut DAL'nin TableAdapter'larını iyimser eşzamanlılığı kullanacak şekilde genişletmek yerine (BLL'nin uyum sağlaması için değiştirilmesini zorunlu tutar), bunun yerine iyimser eşzamanlılık kullanan bir TableAdapter ekleyeceğimiz adlı NorthwindOptimisticConcurrency
yeni bir Products
Typed DataSet oluşturalım. Bundan sonra, iyimser eşzamanlılık DAL'sini desteklemek için uygun değişikliklere sahip bir ProductsOptimisticConcurrencyBLL
İş Mantığı Katmanı sınıfı oluşturacağız. Bu temel çalışma hazır olduktan sonra ASP.NET sayfasını oluşturmaya hazır olacağız.
2. Adım: İyimser Eşzamanlılığı Destekleyen Bir Veri Erişim Katmanı Oluşturma
Yeni bir Yazılan Veri Kümesi oluşturmak için, klasörün içindeki App_Code
klasöre sağ tıklayın DAL
ve adlı NorthwindOptimisticConcurrency
yeni bir DataSet ekleyin. İlk öğreticide gördüğümüz gibi, bunu yaptığınızda Typed DataSet'e yeni bir TableAdapter eklenir ve TableAdapter Yapılandırma Sihirbazı otomatik olarak başlatılır. İlk ekranda, bağlantısı oluşturulacak veritabanını belirtmemiz ve içinden ayarını Web.config
kullanarak NORTHWNDConnectionString
aynı Northwind veritabanına bağlanmamız istenir.
Şekil 3: Aynı Northwind Veritabanına Bağlanma (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Ardından, geçici bir SQL deyimi, yeni bir saklı yordam veya mevcut bir saklı yordam aracılığıyla verileri nasıl sorgulayacağımız sorulur. Özgün DAL'mizde geçici SQL sorguları kullandığımızdan bu seçeneği burada da kullanabilirsiniz.
Şekil 4: Geçici SQL Deyimi Kullanarak Alınacak Verileri Belirtme (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Aşağıdaki ekranda, ürün bilgilerini almak için kullanılacak SQL sorgusunu girin. Şimdi özgün DAL'den TableAdapter için Products
kullanılan SQL sorgusunun Product
aynısını kullanalım. Bu sorgu, ürünün sağlayıcı ve kategori adlarıyla birlikte tüm sütunları döndürür:
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID)
as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID)
as SupplierName
FROM Products
Şekil 5: Özgün DAL'deki TableAdapter'dan Products
Aynı SQL Sorgusunu Kullanma (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Sonraki ekrana geçmeden önce Gelişmiş Seçenekler düğmesine tıklayın. Bu TableAdapter'ın iyimser eşzamanlılık denetimi kullanmasını sağlamak için "İyimser eşzamanlılık kullan" onay kutusunu işaretlemeniz yeterlidir.
Şekil 6: "İyimser eşzamanlılık kullan" Onay Kutusunu Denetleyerek İyimser Eşzamanlılık Denetimini Etkinleştirme (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Son olarak, TableAdapter'ın hem DataTable'ı dolduran hem de DataTable döndüren veri erişim desenlerini kullanması gerektiğini belirtin; ayrıca doğrudan veritabanı yöntemlerinin oluşturulması gerektiğini de belirtir. Özgün DAL'mizde kullandığımız adlandırma kurallarını yansıtmak için GetData'dan GetProducts'a DataTable Döndür deseninin yöntem adını değiştirin.
Şekil 7: TableAdapter'ın Tüm Veri Erişim Desenlerini Kullanmasını Sağlayın (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Sihirbazı tamamladıktan sonra DataSet Tasarım Aracı kesin olarak yazılmış Products
bir DataTable ve TableAdapter içerir. DataTable'ın Products
başlık çubuğuna ProductsOptimisticConcurrency
sağ tıklayıp bağlam menüsünden Yeniden Adlandır'ı seçerek yapabileceğiniz DataTable'ı olarak yeniden adlandırmak için biraz zaman ayırın.
Şekil 8: Yazılan Veri Kümesine DataTable ve TableAdapter Eklendi (Tam boyutlu görüntüyü görüntülemek için tıklayın)
TableAdapter (iyimser eşzamanlılık kullanan) ile Products TableAdapter (olmayan) arasındaki ve DELETE
sorguları arasındaki UPDATE
ProductsOptimisticConcurrency
farkları görmek için TableAdapter'a tıklayın ve Özellikler penceresi gidin. DeleteCommand
DAL'nin CommandText
güncelleştirme veya silmeyle ilgili yöntemleri çağrıldığında ve UpdateCommand
özelliklerinin alt özelliklerinde veritabanına gönderilen gerçek SQL söz dizimini görebilirsiniz. ProductsOptimisticConcurrency
TableAdapter DELETE
için kullanılan deyim şu şekildedir:
DELETE FROM [Products]
WHERE (([ProductID] = @Original_ProductID)
AND ([ProductName] = @Original_ProductName)
AND ((@IsNull_SupplierID = 1 AND [SupplierID] IS NULL)
OR ([SupplierID] = @Original_SupplierID))
AND ((@IsNull_CategoryID = 1 AND [CategoryID] IS NULL)
OR ([CategoryID] = @Original_CategoryID))
AND ((@IsNull_QuantityPerUnit = 1 AND [QuantityPerUnit] IS NULL)
OR ([QuantityPerUnit] = @Original_QuantityPerUnit))
AND ((@IsNull_UnitPrice = 1 AND [UnitPrice] IS NULL)
OR ([UnitPrice] = @Original_UnitPrice))
AND ((@IsNull_UnitsInStock = 1 AND [UnitsInStock] IS NULL)
OR ([UnitsInStock] = @Original_UnitsInStock))
AND ((@IsNull_UnitsOnOrder = 1 AND [UnitsOnOrder] IS NULL)
OR ([UnitsOnOrder] = @Original_UnitsOnOrder))
AND ((@IsNull_ReorderLevel = 1 AND [ReorderLevel] IS NULL)
OR ([ReorderLevel] = @Original_ReorderLevel))
AND ([Discontinued] = @Original_Discontinued))
DELETE
Ancak özgün DAL'mizdeki Product TableAdapter deyimi çok daha basittir:
DELETE FROM [Products] WHERE (([ProductID] = @Original_ProductID))
Gördüğünüz gibi, WHERE
TableAdapter deyiminde DELETE
iyimser eşzamanlılık kullanan yan tümcesi, tablonun mevcut sütun değerlerinin her biri Product
ile GridView'un (veya DetailsView veya FormView) son doldurulma zamanındaki özgün değerlerin karşılaştırmasını içerir. , ProductName
ve Discontinued
dışındaki tüm alanlar değerlere sahip NULL
olabileceğinden ProductID
yan tümcesindeki WHERE
değerleri doğru karşılaştırmak NULL
için ek parametreler ve denetimler eklenir.
bu öğretici için iyimser eşzamanlılık özellikli DataSet'e ek DataTable eklemeyeceğiz çünkü ASP.NET sayfamız yalnızca ürün bilgilerini güncelleştirme ve silme bilgilerini sağlayacaktır. Ancak yine de yöntemini TableAdapter'a ProductsOptimisticConcurrency
eklememiz GetProductByProductID(productID)
gerekir.
Bunu yapmak için TableAdapter'ın başlık çubuğuna (ve GetProducts
yöntemi adlarının hemen üstündeki Fill
alan) sağ tıklayın ve bağlam menüsünden Sorgu Ekle'yi seçin. Bu işlem TableAdapter Sorgu Yapılandırma Sihirbazı'nı başlatır. TableAdapter'ın ilk yapılandırmasında GetProductByProductID(productID)
olduğu gibi geçici bir SQL deyimi kullanarak yöntemini oluşturmayı tercih edin (bkz. Şekil 4). GetProductByProductID(productID)
yöntemi belirli bir ürünle ilgili bilgileri döndürdüğünden, bu sorgunun satır döndüren bir SELECT
sorgu türü olduğunu belirtin.
Şekil 9: Sorgu Türünü " satır döndüren"SELECT
olarak işaretleyin (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Sonraki ekranda TableAdapter'ın varsayılan sorgusu önceden yüklenmiş olarak KULLANıLACAK SQL sorgusu istenir. Şekil 10'da gösterildiği gibi, var olan sorguyu yan tümcesini WHERE ProductID = @ProductID
içerecek şekilde genişletin.
Şekil 10: Belirli bir WHERE
Ürün Kaydını Döndürmek için Önceden Yüklenmiş Sorguya Yan Tümce Ekleme (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Son olarak, oluşturulan yöntem adlarını ve GetProductByProductID
olarak FillByProductID
değiştirin.
Şekil 11: Yöntemleri FillByProductID
ve GetProductByProductID
olarak yeniden adlandırın (tam boyutlu görüntüyü görüntülemek için tıklayın)
Bu sihirbaz tamamlandığında TableAdapter artık verileri almak için iki yöntem içerir: GetProducts()
tüm ürünleri döndüren ve GetProductByProductID(productID)
belirtilen ürünü döndüren .
3. Adım: İyimser Concurrency-Enabled DAL için İş Mantığı Katmanı Oluşturma
Mevcut ProductsBLL
sınıfımızda hem toplu güncelleştirme hem de veritabanı doğrudan desenlerini kullanma örnekleri vardır. Hem AddProduct
yöntemi hem UpdateProduct
de aşırı yüklemeleri toplu güncelleştirme desenini kullanır ve bir ProductRow
örneği TableAdapter'ın Update yöntemine geçirir. DeleteProduct
Diğer yandan yöntemi, TableAdapter'ın Delete(productID)
yöntemini çağırarak veritabanı doğrudan desenini kullanır.
Yeni ProductsOptimisticConcurrency
TableAdapter ile db doğrudan yöntemleri artık özgün değerlerin de geçirilmesini gerektirir. Örneğin, Delete
yöntemi artık on giriş parametresi bekler: özgün ProductID
, ProductName
, SupplierID
, CategoryID
, , QuantityPerUnit
, UnitPrice
, , UnitsInStock
, UnitsOnOrder
, ReorderLevel
ve Discontinued
. Veritabanına gönderilen deyiminin yan tümcesinde WHERE
bu ek giriş parametrelerinin DELETE
değerlerini kullanır, yalnızca veritabanının geçerli değerleri özgün kayıtlarla eşlerse belirtilen kaydı siler.
Toplu güncelleştirme deseninde kullanılan TableAdapter Update
yönteminin yöntem imzası değişmemiş olsa da, özgün ve yeni değerleri kaydetmek için gereken kodda vardır. Bu nedenle, mevcut ProductsBLL
sınıfımızla iyimser eşzamanlılık özellikli DAL'yi kullanmaya çalışmak yerine yeni DAL'mizle çalışmak için yeni bir İş Mantığı Katmanı sınıfı oluşturalım.
adlı bir sınıfı ProductsOptimisticConcurrencyBLL
BLL
klasörün içindeki App_Code
klasöre ekleyin.
Şekil 12: Sınıfı BLL Klasörüne Ekleme ProductsOptimisticConcurrencyBLL
Ardından sınıfına aşağıdaki kodu ProductsOptimisticConcurrencyBLL
ekleyin:
Imports NorthwindOptimisticConcurrencyTableAdapters
<System.ComponentModel.DataObject()> _
Public Class ProductsOptimisticConcurrencyBLL
Private _productsAdapter As ProductsOptimisticConcurrencyTableAdapter = Nothing
Protected ReadOnly Property Adapter() As ProductsOptimisticConcurrencyTableAdapter
Get
If _productsAdapter Is Nothing Then
_productsAdapter = New ProductsOptimisticConcurrencyTableAdapter()
End If
Return _productsAdapter
End Get
End Property
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Select, True)> _
Public Function GetProducts() As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable
Return Adapter.GetProducts()
End Function
End Class
Sınıf bildiriminin başlangıcının üzerindeki using NorthwindOptimisticConcurrencyTableAdapters
deyimine dikkat edin. NorthwindOptimisticConcurrencyTableAdapters
ad alanı, DAL'nin yöntemlerini sağlayan sınıfını içerirProductsOptimisticConcurrencyTableAdapter
. Ayrıca sınıf bildiriminden önce, Visual Studio'ya bu sınıfı ObjectDataSource sihirbazının açılan listesine eklemesini emreden özniteliğini bulacaksınız System.ComponentModel.DataObject
.
özelliği sınıfın ProductsOptimisticConcurrencyBLL
bir örneğine ProductsOptimisticConcurrencyTableAdapter
hızlı erişim sağlar ve özgün BLL sınıflarımızda (ProductsBLL
, CategoriesBLL
vb.) kullanılan deseni Adapter
izler. Son olarak, yöntemi dal'ın GetProducts()
GetProducts()
yöntemine çağrı yaparak veritabanındaki her ürün kaydı için bir örnekle doldurulmuş bir ProductsOptimisticConcurrencyRow
nesne döndürürProductsOptimisticConcurrencyDataTable
.
İyimser Eşzamanlılık ile Db Doğrudan Desenini Kullanarak Bir Ürünü Silme
İyimser eşzamanlılık kullanan bir DAL'ye karşı veritabanı doğrudan deseni kullanılırken yöntemlere yeni ve özgün değerler geçirilmelidir. Silme için yeni değer olmadığından yalnızca özgün değerlerin geçirilmesi gerekir. BLL'mizde tüm özgün parametreleri giriş parametreleri olarak kabul etmeliyiz. sınıfındaki yönteminin DeleteProduct
ProductsOptimisticConcurrencyBLL
DB doğrudan yöntemini kullanmasını sağlayın. Bu, aşağıdaki kodda gösterildiği gibi bu yöntemin on ürün veri alanının tamamını giriş parametresi olarak alması ve dal'a geçirmesi gerektiği anlamına gelir:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteProduct( _
ByVal original_productID As Integer, ByVal original_productName As String, _
ByVal original_supplierID As Nullable(Of Integer), _
ByVal original_categoryID As Nullable(Of Integer), _
ByVal original_quantityPerUnit As String, _
ByVal original_unitPrice As Nullable(Of Decimal), _
ByVal original_unitsInStock As Nullable(Of Short), _
ByVal original_unitsOnOrder As Nullable(Of Short), _
ByVal original_reorderLevel As Nullable(Of Short), _
ByVal original_discontinued As Boolean) _
As Boolean
Dim rowsAffected As Integer = Adapter.Delete(
original_productID, _
original_productName, _
original_supplierID, _
original_categoryID, _
original_quantityPerUnit, _
original_unitPrice, _
original_unitsInStock, _
original_unitsOnOrder, _
original_reorderLevel, _
original_discontinued)
' Return true if precisely one row was deleted, otherwise false
Return rowsAffected = 1
End Function
Kullanıcı Sil düğmesine WHERE
tıkladığında özgün değerler (GridView'a (veya DetailsView veya FormView) en son yüklenen değerler) veritabanındaki değerlerden farklıysa yan tümcesi hiçbir veritabanı kaydıyla eşleşmez ve hiçbir kayıt etkilenmez. Bu nedenle TableAdapter'ın Delete
yöntemi döndürülecek 0
ve BLL'nin DeleteProduct
yöntemi döndürülecektir false
.
İyimser Eşzamanlılık ile Toplu Güncelleştirme Desenini Kullanarak Bir Ürünü Güncelleştirme
Daha önce belirtildiği gibi, TableAdapter'ın Update
toplu güncelleştirme düzeni için yöntemi, iyimser eşzamanlılığın çalışıp çalışmadığına bakılmaksızın aynı yöntem imzasını kullanır. Yani yöntemi bir DataRow, Update
datarows dizisi, DataTable veya Typed DataSet bekler. Özgün değerleri belirtmek için ek giriş parametresi yoktur. DataTable, DataRow'ları için özgün ve değiştirilmiş değerleri takip ettiğinden bu mümkündür. DAL deyimini UPDATE
@original_ColumnName
verdiği zaman, parametreler DataRow'un özgün değerleriyle doldurulurken @ColumnName
, parametreler DataRow'un değiştirilmiş değerleriyle doldurulur.
ProductsBLL
sınıfında (özgün, iyimser olmayan eşzamanlılık DAL'mizi kullanır) ürün bilgilerini güncelleştirmek için toplu güncelleştirme desenini kullanırken kodumuz aşağıdaki olay dizisini gerçekleştirir:
- TableAdapter'ın
GetProductByProductID(productID)
yöntemini kullanarak geçerli veritabanı ürün bilgilerini birProductRow
örneğe okuma - 1. Adım'dan örneğe
ProductRow
yeni değerler atama - TableAdapter'ın
Update
yöntemini çağırın ve örneğiniProductRow
geçirin
Ancak bu adım dizisi, 1. Adımda doldurulan değerlerin doğrudan veritabanından doldurulması nedeniyle iyimser eşzamanlılığı ProductRow
doğru şekilde desteklemez; başka bir deyişle DataRow tarafından kullanılan özgün değerler, düzenleme işleminin başlangıcında GridView'a bağlı olanlar değil, şu anda veritabanında bulunan değerlerdir. Bunun yerine, iyimser eşzamanlılık özellikli bir DAL kullanırken aşağıdaki adımları kullanmak için yöntem aşırı yüklemelerini değiştirmemiz UpdateProduct
gerekir:
- TableAdapter'ın
GetProductByProductID(productID)
yöntemini kullanarak geçerli veritabanı ürün bilgilerini birProductsOptimisticConcurrencyRow
örneğe okuma - 1. Adım'dan örneğe özgün değerleri
ProductsOptimisticConcurrencyRow
atama ProductsOptimisticConcurrencyRow
DataRow'aAcceptChanges()
geçerli değerlerinin "özgün" değerler olduğunu belirten örneğin yöntemini çağırın- Yeni değerleri örneğe
ProductsOptimisticConcurrencyRow
atama - TableAdapter'ın
Update
yöntemini çağırın ve örneğiniProductsOptimisticConcurrencyRow
geçirin
1. adım, belirtilen ürün kaydı için tüm geçerli veritabanı değerlerini okur. Bu adım, tüm ürün sütunlarını güncelleştiren aşırı yüklemede UpdateProduct
gereksizdir (2. Adımda bu değerlerin üzerine yazıldığı gibi), ancak sütun değerlerinin yalnızca bir alt kümesinin giriş parametresi olarak geçirildiği aşırı yüklemeler için önemlidir. Özgün değerler örneğe ProductsOptimisticConcurrencyRow
atandıktan sonra, AcceptChanges()
geçerli DataRow değerlerini deyimdeki parametrelerde @original_ColumnName
kullanılacak özgün değerler olarak işaretleyen yöntemi çağrılır UPDATE
. Ardından yeni parametre değerleri ve öğesine atanır ProductsOptimisticConcurrencyRow
ve son olarak Update
datarow'a geçirilerek yöntemi çağrılır.
Aşağıdaki kod, UpdateProduct
tüm ürün veri alanlarını giriş parametreleri olarak kabul eden aşırı yüklemeyi gösterir. Burada gösterilmese de, ProductsOptimisticConcurrencyBLL
bu öğreticinin indirmesine dahil edilen sınıf, giriş parametreleri olarak yalnızca ürünün adını ve fiyatını kabul eden bir UpdateProduct
aşırı yükleme içerir.
Protected Sub AssignAllProductValues( _
ByVal product As NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow, _
ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
ByVal discontinued As Boolean)
product.ProductName = productName
If Not supplierID.HasValue Then
product.SetSupplierIDNull()
Else
product.SupplierID = supplierID.Value
End If
If Not categoryID.HasValue Then
product.SetCategoryIDNull()
Else
product.CategoryID = categoryID.Value
End If
If quantityPerUnit Is Nothing Then
product.SetQuantityPerUnitNull()
Else
product.QuantityPerUnit = quantityPerUnit
End If
If Not unitPrice.HasValue Then
product.SetUnitPriceNull()
Else
product.UnitPrice = unitPrice.Value
End If
If Not unitsInStock.HasValue Then
product.SetUnitsInStockNull()
Else
product.UnitsInStock = unitsInStock.Value
End If
If Not unitsOnOrder.HasValue Then
product.SetUnitsOnOrderNull()
Else
product.UnitsOnOrder = unitsOnOrder.Value
End If
If Not reorderLevel.HasValue Then
product.SetReorderLevelNull()
Else
product.ReorderLevel = reorderLevel.Value
End If
product.Discontinued = discontinued
End Sub
<System.ComponentModel.DataObjectMethodAttribute( _
System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct(
ByVal productName As String, ByVal supplierID As Nullable(Of Integer), _
ByVal categoryID As Nullable(Of Integer), ByVal quantityPerUnit As String, _
ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
ByVal unitsOnOrder As Nullable(Of Short), ByVal reorderLevel As Nullable(Of Short), _
ByVal discontinued As Boolean, ByVal productID As Integer, _
_
ByVal original_productName As String, _
ByVal original_supplierID As Nullable(Of Integer), _
ByVal original_categoryID As Nullable(Of Integer), _
ByVal original_quantityPerUnit As String, _
ByVal original_unitPrice As Nullable(Of Decimal), _
ByVal original_unitsInStock As Nullable(Of Short), _
ByVal original_unitsOnOrder As Nullable(Of Short), _
ByVal original_reorderLevel As Nullable(Of Short), _
ByVal original_discontinued As Boolean, _
ByVal original_productID As Integer) _
As Boolean
'STEP 1: Read in the current database product information
Dim products As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable = _
Adapter.GetProductByProductID(original_productID)
If products.Count = 0 Then
' no matching record found, return false
Return False
End If
Dim product As _
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow = products(0)
'STEP 2: Assign the original values to the product instance
AssignAllProductValues( _
product, original_productName, original_supplierID, _
original_categoryID, original_quantityPerUnit, original_unitPrice, _
original_unitsInStock, original_unitsOnOrder, original_reorderLevel, _
original_discontinued)
'STEP 3: Accept the changes
product.AcceptChanges()
'STEP 4: Assign the new values to the product instance
AssignAllProductValues( _
product, productName, supplierID, categoryID, quantityPerUnit, unitPrice, _
unitsInStock, unitsOnOrder, reorderLevel, discontinued)
'STEP 5: Update the product record
Dim rowsAffected As Integer = Adapter.Update(product)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
4. Adım: ASP.NET Sayfasından Özgün ve Yeni Değerleri BLL Yöntemlerine Geçirme
DAL ve BLL tamamlandıktan sonra geriye kalan tek şey, sistemde yerleşik olarak bulunan iyimser eşzamanlılık mantığını kullanabilen bir ASP.NET sayfası oluşturmaktır. Özellikle, veri Web denetiminin (GridView, DetailsView veya FormView) özgün değerlerini hatırlaması ve ObjectDataSource'un her iki değer kümesini de İş Mantığı Katmanı'na geçirmesi gerekir. Ayrıca, ASP.NET sayfası eşzamanlılık ihlallerini düzgün bir şekilde işleyecek şekilde yapılandırılmalıdır.
İlk olarak klasördeki EditInsertDelete
sayfayı OptimisticConcurrency.aspx
açın ve Tasarım Aracı bir GridView ekleyerek özelliğini olarak ProductsGrid
ayarlayınID
. GridView'un akıllı etiketinden adlı ProductsOptimisticConcurrencyDataSource
yeni bir ObjectDataSource oluşturmayı tercih edin. Bu ObjectDataSource'un iyimser eşzamanlılığı destekleyen DAL'yi kullanmasını istediğimizden, nesnesini kullanacak ProductsOptimisticConcurrencyBLL
şekilde yapılandırın.
Şekil 13: ObjectDataSource'un Nesneyi Kullanmasını ProductsOptimisticConcurrencyBLL
Sağlayın (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Sihirbazdaki GetProducts
açılan listelerden , UpdateProduct
ve DeleteProduct
yöntemlerini seçin. UpdateProduct yöntemi için ürünün tüm veri alanlarını kabul eden aşırı yüklemeyi kullanın.
ObjectDataSource Denetiminin Özelliklerini Yapılandırma
Sihirbazı tamamladıktan sonra ObjectDataSource'un bildirim temelli işaretlemesi aşağıdaki gibi görünmelidir:
<asp:ObjectDataSource ID="ProductsOptimisticConcurrencyDataSource" runat="server"
DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsOptimisticConcurrencyBLL"
UpdateMethod="UpdateProduct">
<DeleteParameters>
<asp:Parameter Name="original_productID" Type="Int32" />
<asp:Parameter Name="original_productName" Type="String" />
<asp:Parameter Name="original_supplierID" Type="Int32" />
<asp:Parameter Name="original_categoryID" Type="Int32" />
<asp:Parameter Name="original_quantityPerUnit" Type="String" />
<asp:Parameter Name="original_unitPrice" Type="Decimal" />
<asp:Parameter Name="original_unitsInStock" Type="Int16" />
<asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
<asp:Parameter Name="original_reorderLevel" Type="Int16" />
<asp:Parameter Name="original_discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="supplierID" Type="Int32" />
<asp:Parameter Name="categoryID" Type="Int32" />
<asp:Parameter Name="quantityPerUnit" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="unitsInStock" Type="Int16" />
<asp:Parameter Name="unitsOnOrder" Type="Int16" />
<asp:Parameter Name="reorderLevel" Type="Int16" />
<asp:Parameter Name="discontinued" Type="Boolean" />
<asp:Parameter Name="productID" Type="Int32" />
<asp:Parameter Name="original_productName" Type="String" />
<asp:Parameter Name="original_supplierID" Type="Int32" />
<asp:Parameter Name="original_categoryID" Type="Int32" />
<asp:Parameter Name="original_quantityPerUnit" Type="String" />
<asp:Parameter Name="original_unitPrice" Type="Decimal" />
<asp:Parameter Name="original_unitsInStock" Type="Int16" />
<asp:Parameter Name="original_unitsOnOrder" Type="Int16" />
<asp:Parameter Name="original_reorderLevel" Type="Int16" />
<asp:Parameter Name="original_discontinued" Type="Boolean" />
<asp:Parameter Name="original_productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
Gördüğünüz gibi koleksiyon, sınıfın DeleteParameters
DeleteProduct
yöntemindeki on giriş parametresinin ProductsOptimisticConcurrencyBLL
her biri için bir Parameter
örnek içerir. Benzer şekilde, UpdateParameters
koleksiyonu içindeki UpdateProduct
giriş parametrelerinin her biri için bir Parameter
örnek içerir.
Bu özellik, BLL yönteminin hem eski (veya özgün) değerlerin hem de yeni değerlerin geçirilmesini beklediğini gösterdiğinden, veri değişikliğini içeren önceki öğreticilerde ObjectDataSource OldValuesParameterFormatString
özelliğini bu noktada kaldıracağız. Ayrıca, bu özellik değeri özgün değerler için giriş parametresi adlarını gösterir. Özgün değerleri BLL'ye aktardığımız için bu özelliği kaldırmayın .
Not
özelliğinin OldValuesParameterFormatString
değeri, BLL'deki özgün değerleri bekleyen giriş parametresi adlarıyla eşlenmelidir. Bu parametreleri original_productName
, vboriginal_supplierID
. adlandırdığımız için özellik değerini olarak original_{0}
bırakabilirsinizOldValuesParameterFormatString
. Ancak, BLL yöntemlerinin giriş parametrelerinde , gibi old_productName
adlar varsa, özelliğini old_{0}
olarak güncelleştirmeniz OldValuesParameterFormatString
old_supplierID
gerekir.
ObjectDataSource'un özgün değerleri BLL yöntemlerine doğru şekilde geçirmesi için yapılması gereken son bir özellik ayarı vardır. ObjectDataSource, iki değerden birine atanabilen bir ConflictDetection özelliğine sahiptir:
OverwriteChanges
- varsayılan değer; özgün değerleri BLL yöntemlerinin özgün giriş parametrelerine göndermezCompareAllValues
- özgün değerleri BLL yöntemlerine gönderir; İyimser eşzamanlılık kullanırken bu seçeneği belirleyin
özelliğini CompareAllValues
olarak ayarlamak ConflictDetection
için biraz zaman ayırın.
GridView Özelliklerini ve Alanlarını Yapılandırma
ObjectDataSource'un özellikleri düzgün yapılandırıldığında, GridView'u ayarlamaya dikkat edelim. İlk olarak, GridView'un düzenleme ve silmeyi desteklemesini istediğimizden, GridView'un akıllı etiketinden Düzenlemeyi Etkinleştir ve Silmeyi Etkinleştir onay kutularına tıklayın. Bu, ve ShowDeleteButton
her ikisi de olarak true
ayarlanmış bir CommandField ShowEditButton
ekler.
ObjectDataSource'a ProductsOptimisticConcurrencyDataSource
bağlıyken GridView, ürünün veri alanlarının her biri için bir alan içerir. Böyle bir GridView düzenlenebilir ancak kullanıcı deneyimi kabul edilebilir bir deneyimdir. CategoryID
ve SupplierID
BoundFields, kullanıcının uygun kategoriyi ve sağlayıcıyı kimlik numaraları olarak girmesini gerektiren TextBoxes olarak işlenir. Sayısal alanlar için biçimlendirme ve ürünün adının sağlandığından ve birim fiyatının, stoktaki birimlerin, siparişteki birimlerin ve yeniden sıralama düzeyi değerlerinin hem uygun sayısal değerler olduğundan hem de sıfırdan büyük veya sıfıra eşit olduğundan emin olmak için hiçbir doğrulama denetimi olmayacaktır.
Düzenleme ve Ekleme Arabirimlerine Doğrulama Denetimleri Ekleme veVeri Değiştirme Arabirimini Özelleştirme öğreticilerinde ele aldığımız gibi, kullanıcı arabirimi BoundFields yerine TemplateFields ile özelleştirilebilir. Bu GridView'ı ve düzenleme arabirimini aşağıdaki yollarla değiştirdim:
ProductID
,SupplierName
, veCategoryName
BoundFields kaldırıldıProductName
BoundField bir TemplateField'e dönüştürüldü ve RequiredFieldValidation denetimi eklendi.CategoryID
veSupplierID
BoundField'leri TemplateFields'e dönüştürdü ve düzenleme arabirimini TextBoxes yerine DropDownLists kullanacak şekilde ayarladı. Bu TemplateField'lerdeItemTemplates
CategoryName
veSupplierName
veri alanları görüntülenir.UnitPrice
, ,UnitsInStock
UnitsOnOrder
veReorderLevel
BoundFields şablonalanlarına dönüştürüldü ve CompareValidator denetimleri eklendi.
Bu görevleri önceki öğreticilerde nasıl yerine getirebileceğimizi incelediğimizden, son bildirim temelli söz dizimini burada listeleyip uygulamayı uygulama olarak bırakacağım.
<asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsOptimisticConcurrencyDataSource"
OnRowUpdated="ProductsGrid_RowUpdated">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:TemplateField HeaderText="Product" SortExpression="ProductName">
<EditItemTemplate>
<asp:TextBox ID="EditProductName" runat="server"
Text='<%# Bind("ProductName") %>'></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
ControlToValidate="EditProductName"
ErrorMessage="You must enter a product name."
runat="server">*</asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("ProductName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
<EditItemTemplate>
<asp:DropDownList ID="EditCategoryID" runat="server"
DataSourceID="CategoriesDataSource" AppendDataBoundItems="true"
DataTextField="CategoryName" DataValueField="CategoryID"
SelectedValue='<%# Bind("CategoryID") %>'>
<asp:ListItem Value=">(None)</asp:ListItem>
</asp:DropDownList><asp:ObjectDataSource ID="CategoriesDataSource"
runat="server" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("CategoryName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
<EditItemTemplate>
<asp:DropDownList ID="EditSuppliersID" runat="server"
DataSourceID="SuppliersDataSource" AppendDataBoundItems="true"
DataTextField="CompanyName" DataValueField="SupplierID"
SelectedValue='<%# Bind("SupplierID") %>'>
<asp:ListItem Value=">(None)</asp:ListItem>
</asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource"
runat="server" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
</asp:ObjectDataSource>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label3" runat="server"
Text='<%# Bind("SupplierName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
SortExpression="QuantityPerUnit" />
<asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
<EditItemTemplate>
<asp:TextBox ID="EditUnitPrice" runat="server"
Text='<%# Bind("UnitPrice", "{0:N2}") %>' Columns="8" />
<asp:CompareValidator ID="CompareValidator1" runat="server"
ControlToValidate="EditUnitPrice"
ErrorMessage="Unit price must be a valid currency value without the
currency symbol and must have a value greater than or equal to zero."
Operator="GreaterThanEqual" Type="Currency"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label4" runat="server"
Text='<%# Bind("UnitPrice", "{0:C}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Units In Stock" SortExpression="UnitsInStock">
<EditItemTemplate>
<asp:TextBox ID="EditUnitsInStock" runat="server"
Text='<%# Bind("UnitsInStock") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator2" runat="server"
ControlToValidate="EditUnitsInStock"
ErrorMessage="Units in stock must be a valid number
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label5" runat="server"
Text='<%# Bind("UnitsInStock", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
<EditItemTemplate>
<asp:TextBox ID="EditUnitsOnOrder" runat="server"
Text='<%# Bind("UnitsOnOrder") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator3" runat="server"
ControlToValidate="EditUnitsOnOrder"
ErrorMessage="Units on order must be a valid numeric value
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label6" runat="server"
Text='<%# Bind("UnitsOnOrder", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
<EditItemTemplate>
<asp:TextBox ID="EditReorderLevel" runat="server"
Text='<%# Bind("ReorderLevel") %>' Columns="6"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator4" runat="server"
ControlToValidate="EditReorderLevel"
ErrorMessage="Reorder level must be a valid numeric value
greater than or equal to zero."
Operator="GreaterThanEqual" Type="Integer"
ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label7" runat="server"
Text='<%# Bind("ReorderLevel", "{0:N0}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
Tam olarak çalışan bir örnekle çok yakınız. Ancak, sürünecek ve bize sorunlara neden olacak birkaç incelik vardır. Buna ek olarak, eşzamanlılık ihlali oluştuğunda kullanıcıyı uyaran bir arabirime ihtiyacımız var.
Not
Veri Web denetiminin özgün değerleri ObjectDataSource'a doğru bir şekilde geçirmesi için (daha sonra BLL'ye geçirilir), GridView özelliğinin EnableViewState
(varsayılan) olarak ayarlanması true
çok önemlidir. Görünüm durumunu devre dışı bırakırsanız, özgün değerler geri göndermede kaybolur.
ObjectDataSource'a Doğru Özgün Değerleri Geçirme
GridView'un yapılandırılma şekliyle ilgili birkaç sorun vardır. ObjectDataSource'un ConflictDetection
özelliği (bizim olduğu gibi) olarak ayarlanırsa CompareAllValues
, ObjectDataSource'un Update()
veya Delete()
yöntemleri GridView (veya DetailsView veya FormView) tarafından çağrıldığında, ObjectDataSource GridView'un özgün değerlerini uygun Parameter
örneklerine kopyalamayı dener. Bu işlemin grafik gösterimi için Şekil 2'ye geri bakın.
Özellikle, GridView'un özgün değerlerine, veriler GridView'a her bağlandığında iki yönlü veri bağlama deyimlerindeki değerler atanır. Bu nedenle, gerekli özgün değerlerin tümünün iki yönlü veri bağlama yoluyla yakalanması ve dönüştürülebilir bir biçimde sağlanması önemlidir.
Bunun neden önemli olduğunu görmek için bir dakika ayırarak sayfamızı bir tarayıcıda ziyaret edin. Beklendiği gibi GridView, her ürünü en soldaki sütunda düzenle ve sil düğmesiyle listeler.
Şekil 14: Ürünler GridView'da Listelenir (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Herhangi bir ürün için Sil düğmesine tıklarsanız, bir FormatException
oluşturulur.
Şekil 15: Bir içindeki Herhangi Bir FormatException
Ürün Sonucunu Silmeye Çalışma (Tam boyutlu görüntüyü görüntülemek için tıklayın)
FormatException
, ObjectDataSource özgün UnitPrice
değerde okumayı denediğinde oluşturulur. ItemTemplate
para birimi ()<%# Bind("UnitPrice", "{0:C}") %>
olarak biçimlendirildiğindenUnitPrice
, 19,95 ABD doları gibi bir para birimi simgesi içerir. , FormatException
ObjectDataSource bu dizeyi öğesine decimal
dönüştürmeye çalıştığında gerçekleşir. Bu sorunu aşmak için birkaç seçeneğimiz vardır:
- para birimi biçimlendirmesini
ItemTemplate
içinden kaldırın. Diğer bir ifadeyle kullanmak yerine<%# Bind("UnitPrice", "{0:C}") %>
yalnızca kullanın<%# Bind("UnitPrice") %>
. Bunun dezavantajı, fiyatın artık biçimlendirilmemiş olmasıdır. UnitPrice
Biçimlendirilmiş para birimini içindeItemTemplate
bir para birimi olarak görüntüleyin, ancak bunu gerçekleştirmek için anahtar sözcüğünüEval
kullanın. Bunun tek yönlü veri bağlama işlemi gerçekleştirdiğiniEval
hatırlayın. Özgün değerlerin değerini sağlamamız gerektiğindenUnitPrice
, içinde yine de iki yönlü bir veri bağlama deyimineItemTemplate
ihtiyacımız olacaktır, ancak bu özellik olarak ayarlanmış bir Etiket Web denetimineVisible
false
yerleştirilebilir. ItemTemplate içinde aşağıdaki işaretlemeyi kullanabiliriz:
<ItemTemplate>
<asp:Label ID="DummyUnitPrice" runat="server"
Text='<%# Bind("UnitPrice") %>' Visible="false"></asp:Label>
<asp:Label ID="Label4" runat="server"
Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
</ItemTemplate>
- kullanarak
<%# Bind("UnitPrice") %>
para birimi biçimlendirmesiniItemTemplate
içinden kaldırın. GridView'unRowDataBound
olay işleyicisinde, değerin görüntülendiği Label Web denetimineUnitPrice
program aracılığıyla erişin ve özelliğini biçimlendirilmiş sürüme ayarlayınText
. UnitPrice
Para birimi olarak biçimlendirilmiş olarak bırakın. GridView'unRowDeleting
olay işleyicisinde, kullanarakDecimal.Parse
mevcut özgünUnitPrice
değeri ($19,95) gerçek ondalık değerle değiştirin. bir ASP.NET Sayfasındaki BLL- ve DAL-Level Özel Durumlarını İşleme öğreticisindeki olay işleyicisinde benzerRowUpdating
bir şeyin nasıl gerçekleştirildiğini gördük.
Örneğim için, özelliği biçimlendirilmemiş UnitPrice
değere bağlı iki yönlü veriler olan Text
gizli bir Label Web denetimi ekleyerek ikinci yaklaşımı tercih ettim.
Bu sorunu çözdükten sonra herhangi bir ürün için Sil düğmesine tıklamayı yeniden deneyin. Bu kez, ObjectDataSource BLL'nin UpdateProduct
yöntemini çağırmaya çalıştığında bir InvalidOperationException
alırsınız.
Şekil 16: ObjectDataSource Göndermek istediği Giriş Parametrelerine Sahip Bir Yöntemi Bulamıyor (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Özel durumun iletisine baktığımızda, ObjectDataSource'un ve original_SupplierName
giriş parametrelerini içeren original_CategoryName
bir BLL DeleteProduct
yöntemini çağırmak istediği açıktır. Bunun nedeni, ve SupplierID
TemplateFields için CategoryID
s şu anda ve SupplierName
veri alanlarıyla iki yönlü Bind deyimleri içermesidirCategoryName
.ItemTemplate
Bunun yerine ve SupplierID
veri alanlarına deyimleri CategoryID
eklememiz Bind
gerekir. Bunu yapmak için, var olan Bind deyimlerini deyimleriyle Eval
değiştirin ve ardından aşağıda gösterildiği gibi iki yönlü veri bağlama kullanarak ve SupplierID
veri alanlarına bağlı CategoryID
olan gizli Etiket denetimleri Text
ekleyin:
<asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
<EditItemTemplate>
...
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="DummyCategoryID" runat="server"
Text='<%# Bind("CategoryID") %>' Visible="False"></asp:Label>
<asp:Label ID="Label2" runat="server"
Text='<%# Eval("CategoryName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
<EditItemTemplate>
...
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="DummySupplierID" runat="server"
Text='<%# Bind("SupplierID") %>' Visible="False"></asp:Label>
<asp:Label ID="Label3" runat="server"
Text='<%# Eval("SupplierName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
Bu değişikliklerle artık ürün bilgilerini başarıyla silip düzenleyebiliyoruz! 5. Adım'da eşzamanlılık ihlallerinin algılandığını doğrulamayı inceleyeceğiz. Ancak şimdilik, tek bir kullanıcı için güncelleştirme ve silmenin beklendiği gibi çalıştığından emin olmak için birkaç kaydı güncelleştirmeyi ve silmeyi denemeniz birkaç dakika sürer.
5. Adım: İyimser Eşzamanlılık Desteğini Test Etme
Eşzamanlılık ihlallerinin algılandığını doğrulamak için (verilerin üzerine körü körüne yazılmasına neden olmak yerine) bu sayfaya iki tarayıcı penceresi açmamız gerekir. Her iki tarayıcı örneğinde de Chai için Düzenle düğmesine tıklayın. Ardından, tarayıcılardan yalnızca birinde adı "Chai Tea" olarak değiştirin ve Güncelleştir'e tıklayın. Güncelleştirme başarılı olmalı ve GridView'u yeni ürün adı olarak "Chai Tea" ile ön düzenleme durumuna döndürmelidir.
Öte yandan diğer tarayıcı penceresi örneğinde, TextBox ürün adı hala "Chai" olarak gösterilir. Bu ikinci tarayıcı penceresinde öğesini olarak 25.00
güncelleştirinUnitPrice
. İyimser eşzamanlılık desteği olmadan, ikinci tarayıcı örneğinde güncelleştirme seçeneğine tıklanması ürün adını "Chai" olarak değiştirir ve böylece ilk tarayıcı örneği tarafından yapılan değişikliklerin üzerine yazılır. İyimser eşzamanlılık kullanılırken, ikinci tarayıcı örneğinde Güncelleştir düğmesine tıklanması DBConcurrencyException ile sonuçlanmıştır.
Şekil 17: Eşzamanlılık İhlali Algılandığında, bir DBConcurrencyException
Oluşturulur (Tam boyutlu görüntüyü görüntülemek için tıklayın)
DBConcurrencyException
yalnızca DAL'nin toplu güncelleştirme deseni kullanıldığında oluşturulur. Veritabanı doğrudan deseni bir özel durum oluşturmaz, yalnızca hiçbir satırın etkilenmediğini gösterir. Bunu göstermek için her iki tarayıcı örneğinin GridView değerini de düzenleme öncesi durumlarına döndürin. Ardından, ilk tarayıcı örneğinde Düzenle düğmesine tıklayın ve ürün adını "Chai Tea" yerine "Chai" olarak değiştirin ve Güncelleştir'e tıklayın. İkinci tarayıcı penceresinde Chai için Sil düğmesine tıklayın.
Sil'e tıklanması üzerine sayfa geri gönderir, GridView ObjectDataSource'un Delete()
yöntemini çağırır ve ObjectDataSource sınıfın ProductsOptimisticConcurrencyBLL
DeleteProduct
yöntemine çağrı yaparak özgün değerleri geçirir. İkinci tarayıcı örneğinin özgün ProductName
değeri ,veritabanındaki geçerli ProductName
değerle eşleşmeyen "Chai Tea" değeridir. Bu nedenle DELETE
, veritabanında yan tümcenin karşılediği kayıt olmadığından veritabanına WHERE
verilen deyim sıfır satırı etkiler. DeleteProduct
yöntemi döndürür false
ve ObjectDataSource'un verileri GridView'a yeniden eklenir.
Son kullanıcının perspektifinden, ikinci tarayıcı penceresinde Chai Tea için Sil düğmesine tıklanması ekranın yanıp sönmesine neden oldu ve geri döndükten sonra ürün hala orada, ancak artık "Chai" olarak listeleniyor (ilk tarayıcı örneği tarafından yapılan ürün adı değişikliği). Kullanıcı Sil düğmesine yeniden tıklarsa, GridView'un özgün ProductName
değeri ("Chai") artık veritabanındaki değerle eşleştiğinden Sil başarılı olur.
Bu iki durumda da kullanıcı deneyimi idealden çok uzaktır. Toplu güncelleştirme desenini kullanırken kullanıcıya özel durumun nitty-gritty ayrıntılarını DBConcurrencyException
göstermek istemediğimiz açıktır. Kullanıcılar komutu başarısız olduğundan veritabanı doğrudan desenini kullanma davranışı biraz kafa karıştırıcı olsa da bunun nedeninin kesin bir göstergesi yoktu.
Bu iki sorunu gidermek için, sayfada bir güncelleştirmenin veya silmenin neden başarısız olduğuna ilişkin bir açıklama sağlayan Etiket Web denetimleri oluşturabiliriz. Toplu güncelleştirme düzeni için GridView'un son düzey olay işleyicisinde uyarı etiketini gerektiği gibi görüntüleyen bir DBConcurrencyException
özel durumun oluşup oluşmadığını belirleyebiliriz. Db doğrudan yöntemi için BLL yönteminin dönüş değerini inceleyebilir ( true
bir satır etkilendiyse, false
aksi takdirde) ve gerektiğinde bilgilendirsel bir ileti görüntüleyebiliriz.
6. Adım: Bilgilendirici İletiler Ekleme ve Eşzamanlılık İhlali Karşısında Görüntüleme
Eşzamanlılık ihlali oluştuğunda, sergilenen davranış DAL'ın toplu güncelleştirmesinin mi yoksa VERITABANı doğrudan deseninin mi kullanıldığına bağlıdır. Öğreticimizde, güncelleştirme için toplu güncelleştirme deseni ve silme için kullanılan veritabanı doğrudan deseni ile birlikte her iki desen de kullanılmaktadır. Başlamak için sayfamıza verileri silmeye veya güncelleştirmeye çalışırken eşzamanlılık ihlalinin oluştuğuna ilişkin iki Label Web denetimi ekleyelim. Etiket denetiminin Visible
ve EnableViewState
özelliklerini olarak false
ayarlayın; bu, özelliklerinin program aracılığıyla olarak ayarlandığı belirli sayfa ziyaretleri dışında her sayfa ziyaretinde gizlenmelerine Visible
true
neden olur.
<asp:Label ID="DeleteConflictMessage" runat="server" Visible="False"
EnableViewState="False" CssClass="Warning"
Text="The record you attempted to delete has been modified by another user
since you last visited this page. Your delete was cancelled to allow
you to review the other user's changes and determine if you want to
continue deleting this record." />
<asp:Label ID="UpdateConflictMessage" runat="server" Visible="False"
EnableViewState="False" CssClass="Warning"
Text="The record you attempted to update has been modified by another user
since you started the update process. Your changes have been replaced
with the current values. Please review the existing values and make
any needed changes." />
, ve Text
EnabledViewState
özelliklerini ayarlamaya Visible
ek olarak, özelliğini Warning
olarak da ayarladımCssClass
. Bu özellik, Etiketin büyük, kırmızı, italik ve kalın yazı tipinde görüntülenmesine neden oluyor. Bu CSS Warning
sınıfı tanımlandı ve ekleme, güncelleştirme ve silme ile ilişkili olayları inceleme öğreticisinde Styles.css geri eklendi.
Bu Etiketleri ekledikten sonra Visual Studio'daki Tasarım Aracı Şekil 18'e benzer görünmelidir.
Şekil 18: Sayfaya İki Etiket Denetimi Eklendi (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Bu Etiket Web denetimleri uygulandığında, bir eşzamanlılık ihlalinin ne zaman oluştuğunun nasıl belirleneceğini incelemeye hazırız. Bu noktada, bilgilendirici iletiyi görüntüleyen uygun Label'ın Visible
özelliği olarak true
ayarlanabilir.
Güncelleştirme Sırasında Eşzamanlılık İhlallerini İşleme
Şimdi ilk olarak toplu güncelleştirme desenini kullanırken eşzamanlılık ihlallerini işlemeye bakalım. Toplu güncelleştirme düzeniyle ilgili bu tür ihlaller özel durum DBConcurrencyException
oluşturmasına neden olduğundan, güncelleştirme işlemi sırasında bir DBConcurrencyException
özel durumun oluşup oluşmadığını belirlemek için ASP.NET sayfamıza kod eklememiz gerekir. Bu durumda, kullanıcıya, başka bir kullanıcı kaydı düzenlemeye başladığı ve Güncelleştir düğmesine tıklaması arasında aynı verileri değiştirdiği için değişikliklerinin kaydedilmediğini açıklayan bir ileti görüntülememiz gerekir.
bir ASP.NET Sayfasındaki BLL- ve DAL-Level Özel Durumlarını İşleme öğreticisinde gördüğümüz gibi, bu tür özel durumlar veri Web denetiminin son düzey olay işleyicilerinde algılanabilir ve gizlenebilir. Bu nedenle, GridView'un RowUpdated
olayı için özel durumun oluşturulup oluşturulmadığını denetleen bir DBConcurrencyException
olay işleyicisi oluşturmamız gerekir. Bu olay işleyicisine, aşağıdaki olay işleyici kodunda gösterildiği gibi güncelleştirme işlemi sırasında oluşturulan herhangi bir özel duruma başvuru geçirilir:
Protected Sub ProductsGrid_RowUpdated _
(ByVal sender As Object, ByVal e As GridViewUpdatedEventArgs) _
Handles ProductsGrid.RowUpdated
If e.Exception IsNot Nothing AndAlso e.Exception.InnerException IsNot Nothing Then
If TypeOf e.Exception.InnerException Is System.Data.DBConcurrencyException Then
' Display the warning message and note that the exception has
' been handled...
UpdateConflictMessage.Visible = True
e.ExceptionHandled = True
End If
End If
End Sub
Bir DBConcurrencyException
özel durum karşısında, bu olay işleyicisi Etiket denetimini görüntüler UpdateConflictMessage
ve özel durumun işlendiğini gösterir. Bu kod uygulandığında, bir kayıt güncelleştirilirken eşzamanlılık ihlali oluştuğunda kullanıcının değişiklikleri kaybolur çünkü aynı anda başka bir kullanıcının değişikliklerinin üzerine yazılırdı. Özellikle GridView, düzenleme öncesi durumuna döndürülür ve geçerli veritabanı verilerine bağlanır. Bu, GridView satırını daha önce görünür olmayan diğer kullanıcının değişiklikleriyle güncelleştirir. Ayrıca, UpdateConflictMessage
Etiket denetimi kullanıcıya az önce ne olduğunu açıklar. Bu olay dizisi Şekil 19'da ayrıntılı olarak anlatılır.
Şekil 19: Eşzamanlılık İhlaliNin Karşısında Kullanıcının Güncelleştirmeler Kayboldu (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Not
Alternatif olarak, GridView'u düzenleme öncesi durumuna döndürmek yerine, geçirilen nesnenin özelliğini true olarak ayarlayarak KeepInEditMode
GridView'u GridViewUpdatedEventArgs
düzenleme durumunda bırakabiliriz. Ancak bu yaklaşımı benimsediğinizde, diğer kullanıcının değerlerinin düzenleme arabirimine yüklenmesi için verileri GridView'a yeniden bağlamayı (yöntemini çağırarak DataBind()
) emin olun. Bu öğreticiyle indirilebilen kod, olay işleyicisinde RowUpdated
şu iki kod satırına açıklama satırı eklenmiştir; GridView'un eşzamanlılık ihlalinden sonra düzenleme modunda kalması için bu kod satırlarını açıklamadan çıkarmanız yeterlidir.
Silerken Eşzamanlılık İhlallerine Yanıt Verme
Veritabanı doğrudan deseni ile eşzamanlılık ihlaline karşı oluşturulan bir özel durum yoktur. Bunun yerine, WHERE yan tümcesi herhangi bir kayıtla eşleşmediğinden veritabanı deyimi hiçbir kaydı etkilemez. BLL'de oluşturulan tüm veri değiştirme yöntemleri, tam olarak bir kaydı etkileyip etkilemediklerini belirten bir Boole değeri döndürecekler şekilde tasarlanmıştır. Bu nedenle, bir kayıt silinirken eşzamanlılık ihlalinin oluşup oluşmadığını belirlemek için BLL'nin DeleteProduct
yönteminin dönüş değerini inceleyebiliriz.
Bir BLL yönteminin dönüş değeri, olay işleyicisine geçirilen nesnenin ObjectDataSourceStatusEventArgs
özelliği aracılığıyla ReturnValue
ObjectDataSource'un son düzey olay işleyicilerinde incelenebilir. yönteminden DeleteProduct
dönüş değerini belirlemekle ilgilendiğimiz için ObjectDataSource'un Deleted
olayı için bir olay işleyicisi oluşturmamız gerekir. ReturnValue
özelliği türündedir object
ve bir özel durum oluşturulduysa ve yöntem bir değer döndürmeden önce kesildiyse olabilirnull
. Bu nedenle, önce özelliğin ReturnValue
bir Boole değeri olmadığından null
ve olmadığından emin olmalıyız. Bu denetimin başarılı olduğunu varsayarsak, etiketi ReturnValue
false
ise Etiket denetimini gösteririzDeleteConflictMessage
. Bu, aşağıdaki kod kullanılarak gerçekleştirilebilir:
Protected Sub ProductsOptimisticConcurrencyDataSource_Deleted _
(ByVal sender As Object, ByVal e As ObjectDataSourceStatusEventArgs) _
Handles ProductsOptimisticConcurrencyDataSource.Deleted
If e.ReturnValue IsNot Nothing AndAlso TypeOf e.ReturnValue Is Boolean Then
Dim deleteReturnValue As Boolean = CType(e.ReturnValue, Boolean)
If deleteReturnValue = False Then
' No row was deleted, display the warning message
DeleteConflictMessage.Visible = True
End If
End If
End Sub
Eşzamanlılık ihlali durumunda kullanıcının silme isteği iptal edilir. GridView yenilenir ve bu kayıt için kullanıcının sayfayı yüklediği zaman ile Sil düğmesine tıklaması arasında gerçekleşen değişiklikler gösterilir. Böyle bir ihlal gerçekleştiğinde DeleteConflictMessage
, az önce ne olduğunu açıklayan Etiket gösterilir (bkz. Şekil 20).
Şekil 20: Eşzamanlılık İhlaliNin Karşısında Kullanıcı Silme İptal Edildi (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Özet
Eşzamanlılık ihlalleri için fırsatlar, birden çok eşzamanlı kullanıcının verileri güncelleştirmesine veya silmesine olanak tanıyan her uygulamada mevcuttur. Bu tür ihlaller dikkate alınmazsa, iki kullanıcı aynı anda aynı verileri güncelleştirdiğinde, son yazmada "kazanır" yazan diğer kullanıcının değişikliklerinin üzerine yazılır. Alternatif olarak geliştiriciler iyimser veya kötümser eşzamanlılık denetimi uygulayabilir. İyimser eşzamanlılık denetimi, eşzamanlılık ihlallerinin seyrek olduğunu varsayar ve yalnızca eşzamanlılık ihlali oluşturacak bir güncelleştirme veya silme komutuna izin vermemektedir. Kötümser eşzamanlılık denetimi, eşzamanlılık ihlallerinin sık sık olduğunu varsayar ve yalnızca bir kullanıcının güncelleştirme veya silme komutunu reddetmenin kabul edilemez olduğunu varsayar. Kötümser eşzamanlılık denetimiyle bir kaydın güncelleştirilmesi, kaydın kilitlenmesini ve böylece diğer kullanıcıların kilitliyken kaydı değiştirmesini veya silmesini önler.
.NET'te Yazılan Veri Kümesi, iyimser eşzamanlılık denetimini desteklemek için işlevsellik sağlar. Özellikle, UPDATE
veritabanına verilen ve DELETE
deyimleri tablonun tüm sütunlarını içerir, böylece güncelleştirme veya silme işleminin yalnızca kaydın geçerli verileri kullanıcının güncelleştirme veya silme işlemini gerçekleştirirken sahip olduğu özgün verilerle eşleşmesi durumunda gerçekleşmesini sağlar. DAL iyimser eşzamanlılığı destekleyecek şekilde yapılandırıldıktan sonra BLL yöntemlerinin güncelleştirilmiş olması gerekir. Buna ek olarak, BLL'ye çağrıda bulunan ASP.NET sayfası, ObjectDataSource'un özgün değerleri veri Web denetiminden alıp BLL'ye iletecek şekilde yapılandırılması gerekir.
Bu öğreticide gördüğümüz gibi, ASP.NET bir web uygulamasında iyimser eşzamanlılık denetimi uygulamak için DAL ve BLL'nin güncelleştirilmesi ve ASP.NET sayfasına destek eklenmesi gerekir. Bu eklenen çalışmanın zamanınızın ve çabanızın akıllıca bir yatırımı olup olmadığı uygulamanıza bağlıdır. Verileri güncelleştiren eş zamanlı kullanıcılarınız nadiren varsa veya güncelleştirdikleri veriler birbirinden farklıysa, eşzamanlılık denetimi önemli bir sorun değildir. Ancak, sitenizde aynı verilerle düzenli olarak birden çok kullanıcı çalışıyorsa eşzamanlılık denetimi, bir kullanıcının güncelleştirmelerinin veya silmelerinin farkında olmadan başka birinin üzerine yazılmasını önlemeye yardımcı olabilir.
Mutlu Programlama!
Yazar hakkında
Yedi ASP/ASP.NET kitabının yazarı ve 4GuysFromRolla.com kurucusu Scott Mitchell, 1998'den beri Microsoft Web teknolojileriyle çalışmaktadır. Scott bağımsız bir danışman, eğitmen ve yazar olarak çalışmaktadır. Son kitabı Sams Teach Yourself ASP.NET 24 Hours 2.0'dır. Adresine adresinden veya adresinden ulaşabileceğiniz http://ScottOnWriting.NETblogu aracılığıyla ulaşabilirsinizmitchell@4GuysFromRolla.com.
Geri Bildirim
https://aka.ms/ContentUserFeedback.
Çok yakında: 2024 boyunca, içerik için geri bildirim mekanizması olarak GitHub Sorunları’nı kullanımdan kaldıracak ve yeni bir geri bildirim sistemiyle değiştireceğiz. Daha fazla bilgi için bkz.Gönderin ve geri bildirimi görüntüleyin