Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
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 ele almak için iyimser eşzamanlılık denetimi uygulayacağız.
Giriş
Yalnızca kullanıcıların verileri görüntülemesine izin veren web uygulamaları veya yalnızca verileri değiştirebilen tek bir kullanıcı içeren web uygulamaları için, iki eş zamanlı kullanıcının yanlışlıkla başka bir kullanıcının değişikliklerinin üzerine yazma 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, değişikliklerini en son işleyen 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ıklayın. 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 UPDATE
(Jisun yalnızca bir alanı güncelleştirmiş olsa bile) ayarlayan bir ProductName
deyimdir. Bu noktada veritabanı, bu ürün için "Chai Tea", beverages kategorisi, tedarikçi Exotic Liquids vb. değerlerine 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" olarak ayarlayan bir UPDATE
ifadeyi, ilgili İçecek kategori kimliğine CategoryID
olarak ayarlayan ve diğer ayarlamaları gerçekleştiren bir sonuç verir. Jisun'un ürün adındaki değişiklikler geçersiz kılındı. Şekil 1'de bu olay serisi grafik olarak gösterilmektedir.
Şekil 1: İki Kullanıcı Aynı Anda Bir Kaydı Güncelleştirdiğinde, Bir Kullanıcının DiğerInin Üzerine YazmaSına Yönelik Değişiklikler Olabilir (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Benzer şekilde, iki kullanıcı bir sayfayı ziyaret ederken, bir kullanıcı kaydı güncelleştiriyor olabilir ve bu sırada başka bir kullanıcı kaydı silebilir. 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.
Üç eşzamanlılık denetimi stratejisi vardır:
- Hiçbir Şey Yapma -if 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 seferinde eşzamanlılık çakışmaları olsa da, bu tür çakışmaların çoğu zaman 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 başka bir kullanıcının eşzamanlı etkinliği nedeniyle değişikliklerinin 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 önleyin
Şimdiye kadar tüm öğreticilerimiz varsayılan eşzamanlılık çözümleme stratejisini kullandı. Yani son yazma işleminin kazanmasını sağladık. Bu öğreticide iyimser eşzamanlılık denetimini nasıl uygulayacağımızı inceleyeceğiz.
Uyarı
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 bir şekilde iptal edilmediği takdirde diğer kullanıcıların verileri güncelleştirmesini engelleyebilir. Örneğin, bir kullanıcı kaydı düzenlemek üzere kilitleyip kilidini 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, eğer bu süre aşılırsa kilidi iptal eden bir zaman aşımı vardır. Kullanıcı sipariş işlemini tamamlarken belirli bir oturma konumunu kısa bir süre için kilitleyen bilet satış web siteleri, kötümser eşzamanlılık denetimine bir örnektir.
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ığındaki değerlerle 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 uygulamak için çeşitli yaklaşımlar vardır (birkaç seçeneğe 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ığı etkinleştirmek, TableAdapter'ın UPDATE
ve DELETE
deyimlerini, WHERE
yan tümcesinde tüm özgün değerlerin karşılaştırılmasını içerecek şekilde genişletir. Ö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 başlangıçta 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
Uyarı
Bu UPDATE
deyim okunabilirlik için basitleştirilmiştir. Uygulamada, UnitPrice
yan tümcesinde WHERE
denetimi daha kapsamlı olur çünkü UnitPrice
'nin NULL
içerebilmesi ve NULL = NULL
'ün her zaman False döndürüldüğünü kontrol etmek gerekir (bunun yerine IS NULL
kullanmanız gerekir).
Farklı bir temel alınan UPDATE
deyimi kullanmanın yanı sıra, bir TableAdapter'ı iyimser eşzamanlılık kullanacak şekilde yapılandırmak, veritabanı doğrudan erişim yöntemlerinin tanımını da değiştirir. İlk öğreticimiz olan Veri Erişim Katmanı Oluşturma bölümünden hatırlayacağınız üzere veritabanı doğrudan yöntemleri, skaler değerlerin listesini giriş parametreleri olarak kabul eden yöntemlerdi (kesin olarak türü belirlenmiş 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'ın TableAdapter'larını iyimser eşzamanlılığı kullanacak şekilde genişletmek yerine (uyum sağlamak için BLL'nin değiştirilmesini gerektirecektir), 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ırlandıktan 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 Türlenmiş Veri Kümesi oluşturmak için, klasörün içindeki DAL
klasöre sağ tıklayın App_Code
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, aynı Northwind veritabanına bağlanmak için NORTHWNDConnectionString
ayarını kullanarak Web.config
ile hangi veritabanına bağlanılacağını belirtmemiz 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 kullanın.
Ş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. Orijinal DAL'den Products
TableAdapter için kullanılan aynı SQL sorgusunu kullanalım, bu sorgu tüm Product
sütunlarının yanı sıra ürünün tedarikçi ve kategori adlarını da 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'de 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 veritabanı doğrudan yöntemlerinin oluşturulması gerektiğini de belirtin. DAL'ımızdaki özgün adlandırma kurallarını yansıtmak amacıyla "Return a DataTable" deseninin yöntem adını GetData'dan GetProducts'a 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ımcısı güçlü bir şekilde yazılmış Products
DataTable ve TableAdapter'ı içerir. Biraz zaman ayırarak, Products
olan DataTable'ı, başlık çubuğuna sağ tıklayıp bağlam menüsünden Yeniden Adlandır'ı seçerek ProductsOptimisticConcurrency
olarak yeniden adlandı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)
Optimist eşzamanlılık kullanan UPDATE
TableAdapter ile kullanmayan Products TableAdapter arasındaki farklılıkları ve bu TableAdapter'lar arasındaki DELETE
ve ProductsOptimisticConcurrency
sorgularını görmek için TableAdapter'a tıklayın ve Özellikler penceresine gidin.
DeleteCommand
ve UpdateCommand
özelliklerinin CommandText
alt özelliklerinde, DAL'ın güncelleme veya silme ile ilgili yöntemleri çağrıldığında, veritabanına gönderilen gerçek SQL söz dizimini görebilirsiniz.
ProductsOptimisticConcurrency
TableAdapter DELETE
için kullanılan deyim şöyledir:
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, iyimser eşzamanlılık kullanan WHERE
TableAdapter deyimindeki DELETE
yan tümcesi, Product
tablonun mevcut sütun değerlerinin her biri ile GridView'un (veya DetailsView veya FormView) son doldurulma zamanındaki orijinal değerler arasında bir karşılaştırma içerir.
ProductID
, ProductName
ve Discontinued
dışındaki tüm alanlar NULL
değerlere sahip olabileceğinden, yan tümcedeki NULL
değerlerini doğru bir şekilde karşılaştırmak için ek parametreler ve denetimler WHERE
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üncelleme ve silme işlemlerine olanak tanıyacaktır. Ancak yine de GetProductByProductID(productID)
yöntemini ProductsOptimisticConcurrency
TableAdapter'a eklememiz gerekir.
Bunu yapmak için TableAdapter'ın başlık çubuğuna (ve Fill
yöntemi adlarının hemen üstündeki GetProducts
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, doğaçlama bir SQL ifadesi kullanarak metodunu oluşturmayı tercih edin (bkz. Şekil 4).
GetProductByProductID(productID)
yöntemi belirli bir ürün hakkındaki bilgileri döndürdüğünden, bu sorgunun satırları döndüren bir SELECT
sorgu türü olduğunu belirtin.
Şekil 9: Sorgu Türünü "satırları döndüren"SELECT
olarak işaretleme (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ıcıdan 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 ürün kaydını döndürmek için önceden yüklenmiş sorguya bir madde ekleyin (WHERE
)
Son olarak, oluşturulan yöntem adlarını FillByProductID
ve GetProductByProductID
olarak 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.
AddProduct
yöntemi ve UpdateProduct
aşırı yüklemeleri her ikisi de toplu güncelleştirme desenini kullanır ve bir ProductRow
örneğini TableAdapter'ın Update yöntemine geçirir. Öte yandan, DeleteProduct
yöntemi, TableAdapter'ın Delete(productID)
yöntemini çağırarak veritabanına doğrudan erişim desenini kullanır.
Yeni ProductsOptimisticConcurrency
TableAdapter ile veritabanı 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 deyimin yan tümcesi içinde WHERE
bu ek giriş parametrelerinin DELETE
değerlerini kullanır, ancak yalnızca veritabanının geçerli değerleri orijinal değerlerle eşleşirse belirtilen kaydı siler.
Toplu güncelleştirme düzeninde 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 ile çalışmak için yeni bir İş Mantığı Katmanı sınıfı oluşturalım.
klasöründeki ProductsOptimisticConcurrencyBLL
klasöre BLL
adlı App_Code
bir sınıf ekleyin.
Şekil 12: Sınıfı BLL Klasörüne Ekleme ProductsOptimisticConcurrencyBLL
Ardından aşağıdaki kodu sınıfına ProductsOptimisticConcurrencyBLL
ekleyin:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindOptimisticConcurrencyTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsOptimisticConcurrencyBLL
{
private ProductsOptimisticConcurrencyTableAdapter _productsAdapter = null;
protected ProductsOptimisticConcurrencyTableAdapter Adapter
{
get
{
if (_productsAdapter == null)
_productsAdapter = new ProductsOptimisticConcurrencyTableAdapter();
return _productsAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable GetProducts()
{
return Adapter.GetProducts();
}
}
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 sağlayan System.ComponentModel.DataObject
özniteliğini bulursunuz.
' ProductsOptimisticConcurrencyBLL
s Adapter
özelliği sınıfın ProductsOptimisticConcurrencyTableAdapter
bir örneğine hızlı erişim sağlar ve özgün BLL sınıflarımızda (ProductsBLL
, CategoriesBLL
vb.) kullanılan deseni izler. Son olarak, GetProducts()
yöntemi doğrudan DAL'ın GetProducts()
yöntemine çağrı yapar ve veritabanındaki her bir ürün kaydı için ProductsOptimisticConcurrencyDataTable
örneği ile doldurulmuş ProductsOptimisticConcurrencyRow
nesnesini döndürür.
İyimser Eşzamanlılık ile DB Direct Tasarımı Kullanılarak Ü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. Silmek için yeni değer olmadığından yalnızca özgün değerlerin geçirilmesi gerekir. BLL'mizde tüm özgün parametreleri giriş parametresi olarak kabul etmeliyiz.
DeleteProduct
yönteminin, ProductsOptimisticConcurrencyBLL
sınıfındaki 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 bool DeleteProduct
(int original_productID, string original_productName,
int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued)
{
int rowsAffected = 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;
}
Orijinal değerler - GridView'a (veya DetailsView veya FormView) son yüklenen bu değerler - kullanıcı Sil düğmesine tıkladığında veritabanındaki değerlerden farklı ise, WHERE
ifadesi hiçbir veritabanı kaydıyla eşleşmeyecek ve hiçbir kayıt etkilenmeyecektir. 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 deseni 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. Özellikle Update
yöntemi bir DataRow, DataRows dizisi, bir DataTable veya türlenmiş bir DataSet bekler. Özgün değerleri belirtmek için ek giriş parametresi yoktur. Bu mümkün çünkü DataTable, DataRow'larının özgün ve değiştirilmiş değerlerini takip ediyor. DAL kendi UPDATE
deyimini verdiğinde, @original_ColumnName
parametreler DataRow'un özgün değerleriyle doldurulurken ve @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
ProductRow
yöntemini kullanarak geçerli veritabanı ürün bilgilerini birGetProductByProductID(productID)
örneğe okuma - 1. Adım'dan
ProductRow
örneğe yeni değerler atama - TableAdapter'ın
Update
yöntemini,ProductRow
örneğini geçirerek çağırın.
Ancak bu adım dizisi, 1. Adımda doldurulan değerlerin doğrudan veritabanından doldurulması nedeniyle ProductRow
iyimser eşzamanlılığı 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ı uygulamak için yöntem aşırı yüklemelerini değiştirmemiz gerekir:
- TableAdapter
ProductsOptimisticConcurrencyRow
yöntemini kullanarak geçerli veritabanı ürün bilgilerini birGetProductByProductID(productID)
örneğe okuma - 1. Adım'dan örneğe
ProductsOptimisticConcurrencyRow
değerleri atama -
ProductsOptimisticConcurrencyRow
örneğinin DataRow üzerindekiAcceptChanges()
yöntemini, geçerli değerlerinin "orijinal" değerler olduğunu belirten çağırın -
Yeni değerleri
ProductsOptimisticConcurrencyRow
örneğe atayın - TableAdapter'ın
Update
yöntemini,ProductsOptimisticConcurrencyRow
örneğini geçirerek çağırın.
1. Adım, belirtilen ürün kaydı için tüm geçerli veritabanı değerlerini okur. Bu adım, UpdateProduct
ürün sütunlarını güncelleştiren aşırı yüklemede 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 ProductsOptimisticConcurrencyRow
örneğe atandıktan sonra, AcceptChanges()
yöntemi çağrılarak geçerli DataRow değerlerini deyimindeki @original_ColumnName
parametrelerinde kullanılacak özgün değerler olarak işaretler. Ardından, yeni parametre değerleri ProductsOptimisticConcurrencyRow
öğesine atanır ve son olarak, Update
yöntemi, DataRow iletilerek çağrılır.
Aşağıdaki kod, UpdateProduct
tüm ürün veri alanlarını giriş parametresi olarak kabul eden aşırı yüklemeyi gösterir. Burada gösterilmese de, ProductsOptimisticConcurrencyBLL
bu öğreticiyle birlikte indirilen sınıf, girdi parametreleri olarak sadece ürünün adını ve fiyatını kabul eden bir UpdateProduct
aşırı yükleme içerir.
protected void AssignAllProductValues
(NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product,
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued)
{
product.ProductName = productName;
if (supplierID == null)
product.SetSupplierIDNull();
else
product.SupplierID = supplierID.Value;
if (categoryID == null)
product.SetCategoryIDNull();
else
product.CategoryID = categoryID.Value;
if (quantityPerUnit == null)
product.SetQuantityPerUnitNull();
else
product.QuantityPerUnit = quantityPerUnit;
if (unitPrice == null)
product.SetUnitPriceNull();
else
product.UnitPrice = unitPrice.Value;
if (unitsInStock == null)
product.SetUnitsInStockNull();
else
product.UnitsInStock = unitsInStock.Value;
if (unitsOnOrder == null)
product.SetUnitsOnOrderNull();
else
product.UnitsOnOrder = unitsOnOrder.Value;
if (reorderLevel == null)
product.SetReorderLevelNull();
else
product.ReorderLevel = reorderLevel.Value;
product.Discontinued = discontinued;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(
// new parameter values
string productName, int? supplierID, int? categoryID, string quantityPerUnit,
decimal? unitPrice, short? unitsInStock, short? unitsOnOrder,
short? reorderLevel, bool discontinued, int productID,
// original parameter values
string original_productName, int? original_supplierID, int? original_categoryID,
string original_quantityPerUnit, decimal? original_unitPrice,
short? original_unitsInStock, short? original_unitsOnOrder,
short? original_reorderLevel, bool original_discontinued,
int original_productID)
{
// STEP 1: Read in the current database product information
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyDataTable products =
Adapter.GetProductByProductID(original_productID);
if (products.Count == 0)
// no matching record found, return false
return false;
NorthwindOptimisticConcurrency.ProductsOptimisticConcurrencyRow product = 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
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
4. Adım: ASP.NET Sayfasından BLL Yöntemlerine Özgün ve Yeni Değerleri 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 OptimisticConcurrency.aspx
klasöründeki EditInsertDelete
sayfasını açın ve Tasarımcı'ya bir GridView ekleyerek ID
özelliğini ProductsGrid
olarak ayarlayın. 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, bunu ProductsOptimisticConcurrencyBLL
nesnesini kullanacak ş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 açılan listelerden GetProducts
, 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
Parameter
yöntemindeki on giriş parametresinin ProductsOptimisticConcurrencyBLL
her biri için bir DeleteProduct
örnek içerir. Benzer şekilde, UpdateParameters
koleksiyon içindeki Parameter
giriş parametrelerinin her biri için bir UpdateProduct
ö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ğerlerin giriş parametre adlarını gösterir. Özgün değerleri BLL'ye aktardığımız için bu özelliği kaldırmayın .
Uyarı
özelliğinin OldValuesParameterFormatString
değeri, BLL'deki özgün değerleri bekleyen giriş parametresi adlarıyla eşlenmelidir. Bu parametreleri original_productName
, original_supplierID
vb. adlandırdığımız için özellik değerini olarak OldValuesParameterFormatString
bırakabilirsinizoriginal_{0}
. Ancak, BLL yöntemlerinin giriş parametrelerinin adları old_productName
, old_supplierID
gibi ve benzeri şekilde ise, OldValuesParameterFormatString
özelliğini old_{0}
olarak güncellemeniz 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öndermez -
CompareAllValues
- özgün değerleri BLL yöntemlerine gönderir; iyimser eşzamanlılık kullanırken bu seçeneği belirleyin
Bir an duraklayarak ConflictDetection
özelliğini CompareAllValues
olarak ayarlayın.
GridView'un Ö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, hem \ShowEditButton
hem de \ShowDeleteButton
'in \true
olarak ayarlandığı bir CommandField 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 hiç de kabul edilebilir değildir.
CategoryID
ve SupplierID
BoundField'ler TextBoxes olarak işlenir ve kullanıcının uygun kategoriyi ve sağlayıcıyı kimlik numaraları olarak girmesini zorunlu kılır. Sayısal alanlar için biçimlendirme ve ürün adının sağlandığından ve birim fiyatın, stoktaki birimlerin, siparişteki birimlerin ve yeniden sıralama düzeyi değerlerinin hem doğru 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 değiştirilerek özelleştirilebilir. Bu GridView ve düzenleme arabirimini aşağıdaki yollarla değiştirdim:
-
ProductID
, ,SupplierName
veCategoryName
BoundField'ler kaldırıldı -
ProductName
BoundField bir TemplateField'e dönüştürüldü ve RequiredFieldValidation denetimi eklendi. -
CategoryID
veSupplierID
BoundField'leri TemplateField'lere dönüştürdü ve düzenleme arabirimini TextBox'lar yerine DropDownList'ler 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 zaten incelediğimizden, burada sadece son bildirim söz dizimini listeleyip uygulamayı pratik 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 örneğimiz olmasına çok yaklaştık. Ancak, ortaya çıkacak ve bize sorunlar yaratacak birkaç nüans vardır. Buna ek olarak, eşzamanlılık ihlali oluştuğunda kullanıcıyı uyaran bir arabirime ihtiyacımız vardır.
Uyarı
Veri Web denetiminin özgün değerleri ObjectDataSource'a doğru şekilde geçirmesi için (daha sonra BLL'ye geçirilir), GridView EnableViewState
özelliğinin (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 örneğimizdeki gibi CompareAllValues
olarak ayarlanmışsa, GridView (veya DetailsView ya da FormView) tarafından ObjectDataSource'un Update()
veya Delete()
yöntemleri çağrıldığında, ObjectDataSource, GridView'un özgün değerlerini uygun Parameter
örneklerine kopyalamaya çalışır. 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 ifadelerindeki 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 Listeleniyor (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 FormatException
Tüm Ürün Sonuçlarını Silmeye Çalışma (Tam boyutlu görüntüyü görüntülemek için tıklayın)
FormatException
, ObjectDataSource özgün UnitPrice
değeri okumaya çalıştığında tetiklenir.
ItemTemplate
para birimi ()UnitPrice
olarak biçimlendirildiğinden<%# Bind("UnitPrice", "{0:C}") %>
, 19,95 ABD doları gibi bir para birimi simgesi içerir.
FormatException
, ObjectDataSource bu dizeyi bir decimal
içine dönüştürmeye çalıştığında gerçekleşir. Bu sorunu aşmak için çeşitli seçeneklerimiz vardır:
- para birimi biçimlendirmesini
ItemTemplate
'den kaldırın. Yani<%# Bind("UnitPrice", "{0:C}") %>
kullanmak yerine, sadece<%# Bind("UnitPrice") %>
kullanın. Bunun dezavantajı, fiyatın artık biçimlendirilmemiş olmasıdır. -
UnitPrice
biçimlendirilmiş para biriminiItemTemplate
içinde bir para birimi olarak görüntüleyin, ancak bunu yapmak içinEval
anahtar kelimesini kullanın. Şunu hatırlayın:Eval
tek yönlü veri bağlama işlemi gerçekleştirir. Özgün değerlerinUnitPrice
değerini sağlamamız gerekir, bu nedenle, yine iki yönlü veri bağlama deyimineItemTemplate
ihtiyacımız olacak, ancak buVisible
özelliği olarak ayarlanan bir Etiket Web denetiminefalse
yerleştirilebilir. ItemTemplate'da 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>
-
ItemTemplate
kullanarak<%# Bind("UnitPrice") %>
'den para birimi biçimlendirmesini kaldırın. GridView'unRowDataBound
olay işleyicisinde, içindeUnitPrice
değeri görüntülenen Label Web denetimine programatik olarak erişin veText
özelliğini biçimlendirilmiş sürüme ayarlayın. -
UnitPrice
öğesini para birimi olarak biçimlendirilmiş bırakın. GridView'unRowDeleting
olay işleyicisinde, mevcut özgünUnitPrice
değeri ($19,95)'yiDecimal.Parse
kullanarak gerçek bir ondalık değerle değiştirin.RowUpdating
benzer bir şeyin nasıl gerçekleştirileceğini gördük.
Örneğim için, özelliği biçimlendirilmemiş Text
değere bağlı iki yönlü veriler olan UnitPrice
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 InvalidOperationException
yöntemini çağırmaya çalıştığında bir UpdateProduct
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)
İstisnanın iletisine baktığımızda, açıktır ki ObjectDataSource, DeleteProduct
ve original_CategoryName
giriş parametrelerini içeren bir BLL original_SupplierName
yöntemini çağırmak istiyor. Bunun nedeni, ItemTemplate
ve CategoryID
TemplateFields için SupplierID
ve CategoryName
veri alanlarıyla iki yönlü Bind deyimlerini içeren s değerlerinin SupplierName
bulunmasıdır. Bunun yerine, Bind
ve CategoryID
veri alanlarıyla birlikte SupplierID
deyimler eklememiz gerekir. Bunu yapmak için, mevcut Bind deyimlerinin üzerine Eval
deyimlerini koyun ve ardından, Text
özellikleri CategoryID
ve SupplierID
veri alanlarına iki yönlü veri bağlama ile bağlı olan birer gizli Etiket denetimi ekleyin, aşağıda gösterildiği gibi:
<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ımda eşzamanlılık ihlallerinin algılandığını doğrulamayı inceleyeceğiz. Ancak şimdilik, tek bir kullanıcı için güncelleştirme ve silme işleminin beklendiği gibi çalıştığından emin olmak için birkaç kaydı güncelleştirmeyi ve silmeyi denemek için birkaç dakika bekleyin.
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, UnitPrice
öğesini 25.00
olarak güncelleyin. İyimser eşzamanlılık desteği olmadan, ikinci tarayıcı oturumunda güncelleme düğmesine tıklamak ürün adını tekrar "Chai" yapar ve böylece ilk tarayıcı oturumu tarafından yapılan değişiklikleri geri alır. Ancak iyimser eşzamanlılık kullanıldığında, ikinci tarayıcı örneğinde Güncelleştir düğmesine tıklanması dbConcurrencyException sonucunu doğurabilir.
Ş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'ın toplu güncelleştirme deseni kullanıldığında oluşturulur. Doğrudan veritabanı deseni ö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'ını da 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ıklandığında, sayfa yeniden yüklenir, GridView ObjectDataSource'un Delete()
yöntemini çağırır ve ObjectDataSource, ProductsOptimisticConcurrencyBLL
sınıfının DeleteProduct
yöntemine özgün değerleri geçirerek bir çağrı yapar. İkinci tarayıcı örneğinin özgün ProductName
değeri"Chai Tea"dır ve bu değer veritabanındaki geçerli ProductName
değerle eşleşmez. Bu nedenle veritabanında DELETE
yan tümcesini sağlayan bir kayıt bulunmadığından, WHERE
deyimi veritabanında hiçbir satırı etkilemez.
DeleteProduct
yöntemi döndürür false
ve ObjectDataSource'un verileri GridView'a geri döner.
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 oradadır, ancak artık "Chai" (ilk tarayıcı örneği tarafından yapılan ürün adı değişikliği) olarak listelenmiştir. 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 deseni kullanılırken kullanıcıya özel durumun nitty-gritty ayrıntılarını DBConcurrencyException
göstermek istemediğimiz açıktır. Kullanıcı komutu başarısız olduğundan veritabanı doğrudan desenini kullanma davranışı biraz kafa karıştırıcıydı, ancak nedeninin belirgin bir göstergesi yoktu.
Bu iki sorunu gidermek için, sayfada güncelleştirme 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. Doğrudan VERITABANı yöntemi için BLL yönteminin dönüş değerini inceleyebilir ( true
başka bir şekilde bir satır etkilenmişse false
) ve gerektiğinde bilgilendirme iletisi görüntüleyebiliriz.
6. Adım: Bilgilendirici Mesajlar Ekleme ve Eşzamanlılık İhlali Durumunda 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. Eğitimimizde, güncelleme için toplu güncelleştirme deseni ve silme için veritabanı doğrudan erişim deseni kullanılarak 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 Etiket Web denetimi ekleyelim. Etiket denetiminin Visible
ve EnableViewState
özelliklerini false
olarak ayarlayın; bu işlem, Visible
özelliklerinin program aracılığıyla true
olarak ayarlandığı belirli sayfa ziyaretleri hariç her sayfa ziyaretinde gizlenmelerine 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." />
Özelliklerini ayarlarken Visible
, EnabledViewState
, ve Text
dışında, CssClass
özelliğini Warning
olarak da ayarladım. Bu, 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ı, Ekleme, Güncelleştirme ve Silme ile İlişkili Olayları İzleme öğreticisinde tanımlanıp Styles.css geri eklendi.
Bu Etiketleri ekledikten sonra, Visual Studio'daki Tasarımcı Ş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 Label Web denetimleri yerleştirildiğinde, bir eşzamanlılık ihlalinin ne zaman meydana geldiğini nasıl belirleyeceğimizi incelemeye hazırız. Bu durumda, uygun olan Label'ın Visible
özelliği true
olarak ayarlanarak bilgilendirici mesaj görüntülenebilir.
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 ihlallerinin nasıl işleneceğini inceleyelim. 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, 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ında 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 bir özel durum 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üncelleme işlemi sırasında oluşturulan herhangi bir özel duruma bir referans aktarılır.
protected void ProductsGrid_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null && e.Exception.InnerException != null)
{
if (e.Exception.InnerException is System.Data.DBConcurrencyException)
{
// Display the warning message and note that the
// exception has been handled...
UpdateConflictMessage.Visible = true;
e.ExceptionHandled = true;
}
}
}
Bir DBConcurrencyException
özel durum karşısında, bu olay işleyicisi UpdateConflictMessage
Etiket denetimini görüntüler ve özel durumun işlendiğini gösterir. Bu kod devreye alındığında, kayıt güncelleştirilirken eşzamanlılık ihlali meydana geldiğinde, kullanıcının değişiklikleri kaybolur, çünkü aynı anda başka bir kullanıcının değişikliklerinin üzerine yazılmış olur. Özellikle, GridView ön düzenleme durumuna döndürülür ve geçerli veritabanı verilerine bağlıdı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. Buna ek olarak, UpdateConflictMessage
Etiket denetimi kullanıcıya ne olduğunu açıklar. Bu olay dizisi Şekil 19'da ayrıntılı olarak yer alır.
Şekil 19: Eşzamanlılık İhlaliNin Karşısında Kullanıcı Güncelleştirmeleri Kayboldu (Tam boyutlu görüntüyü görüntülemek için tıklayın)
Uyarı
Alternatif olarak, GridView'ı düzenleme öncesi durumuna döndürmek yerine, geçirilen KeepInEditMode
nesnesinin GridViewUpdatedEventArgs
özelliğini true olarak ayarlayarak GridView'ı düzenleme durumunda bırakabiliriz. Ancak bu yaklaşımı benimserseniz, diğer kullanıcının değerlerini düzenleme arabirimine yüklemek için verileri GridView'a yeniden bağladığınızdan emin olun (yöntemini çağırarak DataBind()
). Bu öğreticiyle indirilebilen kodda, RowUpdated
olay işleyicisinde bu iki kod satırı açıklama satırı olarak bulunur; GridView'un eşzamanlılık ihlalinden sonra düzenleme modunda kalmasını sağlamak için bu satırların açıklamasını kaldırmanız yeterlidir.
Silerken Eşzamanlılık İhlallerine Yanıt Verme
Veritabanı doğrudan düzeninde eşzamanlılık ihlali karşısında özel durum oluşmaz. 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ürebilecek şekilde tasarlanmıştır. Bu nedenle, bir kayıt silinirken eşzamanlılık ihlali oluşup oluşmadığını belirlemek için BLL DeleteProduct
yönteminin dönüş değerini inceleyebiliriz.
ObjectDataSource'un son düzey olay işleyicilerinde bir BLL metodunun dönüş değeri, olay işleyicisine geçirilen nesnenin ReturnValue
özelliği aracılığıyla ObjectDataSourceStatusEventArgs
incelenebilir. yönteminden dönüş değerini belirlemek istediğimizden DeleteProduct
ObjectDataSource'un Deleted
olayı için bir olay işleyicisi oluşturmamız gerekir.
ReturnValue
özelliği object
türündedir ve bir istisna kaldırıldıysa ve yöntem bir değer döndüremeden önce kesintiye uğradıysa null
olabilir. Bu nedenle, önce özelliğin ReturnValue
null
olmadığından ve bir Boole değeri olduğundan emin olmamız gerekir. Eğer bu denetim geçerse, DeleteConflictMessage
ReturnValue
ise false
Etiket kontrolünü gösteririz. Bu, aşağıdaki kod kullanılarak gerçekleştirilebilir:
protected void ProductsOptimisticConcurrencyDataSource_Deleted(
object sender, ObjectDataSourceStatusEventArgs e)
{
if (e.ReturnValue != null && e.ReturnValue is bool)
{
bool deleteReturnValue = (bool)e.ReturnValue;
if (deleteReturnValue == false)
{
// No row was deleted, display the warning message
DeleteConflictMessage.Visible = true;
}
}
}
Eşzamanlılık ihlali karşısında kullanıcının silme isteği iptal edilir. GridView yenilenir ve kullanıcının sayfayı yüklediği zaman ile Sil düğmesine tıklaması arasındaki kayıt için yapılan 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 İhlali Nedeniyle Kullanıcının Silmesi İ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 izin veren her uygulamada mevcuttur. Bu tür ihlaller göz ardı edilirse, iki kullanıcı aynı verileri aynı anda güncellediğinde, son güncellemeyi yapan kullanıcı "kazanır" ve diğer kullanıcının değişikliklerinin üzerine yazar. 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 güncelleştirme veya silme komutuna izin vermemektedir. Kötümser eşzamanlılık denetimi, eşzamanlılık ihlallerinin 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, kaydın güncelleştirilmesi kaydı kilitlemeyi ve böylece diğer kullanıcıların kilitliyken kaydı değiştirmesini veya silmesini engellemeyi içerir.
.NET'te Yazılan DataSet, 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. Ayrıca, BLL'ye çağrıda bulunan ASP.NET sayfası, ObjectDataSource'un veri Web denetiminden özgün değerleri 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. Sık sık veri güncelleştiren eşzamanlı kullanıcılarınız varsa veya güncelleştirdikleri veriler birbirinden farklıysa, eşzamanlılık denetimi önemli bir sorun değildir. Ancak, sitenizde aynı verilerle çalışan birden çok kullanıcınız varsa 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ışır. Son kitabı Sams Teach Yourself ASP.NET 24 Hours 2.0'dır. Ona adresinden mitchell@4GuysFromRolla.comulaşabilirsiniz.