Bu yazımda, ADO.Net'e hızlı bir giriş yapmış olacak, sql Server veritabanı için bazı temel işlemleri anlatmaya çalışacağım. Örnek olarak, kayıt ekleme, kayıt silme, kayıt güncelleme ve kayıt seçme işlemi yapacağım. Kodları ve ekran görüntülerini de paylaşacağım tabiki. Hadi başlayalım, öncelikle veritabanı kısmını yapalım ki, kodlama kısmını daha anlaşılır hale getirebilelim. Sql Server'da bir veritabanı oluşturun. Bunu yapmak için sql komutlarını kullanabileceğiniz gibi, menü seçeneklerinden de yapabilirsiniz. Örnek olarak ben, kullanıcı ekleme, silme, güncelleme ve kullanıcı girişi işlemlerini sağlayabileceğim bir program(cık) yapacağım. Siz daha basit bir uygulama seçebilir, veritabanınızı buna göre düzenleyebilirsiniz. Biraz düşünelim, öncelikle kendimize sormamız gereken soru şu, veritabanı nasıl olmalı? Ben bu soruya birkaç kısa not ile cevap veriyorum kendimce,
1- Her kullanıcının bir şifresi olmalı
2- Aynı kullanıcı adı veya aynı mail ile birden çok kayıt oluşturulamamalı
3- Şifreler veritabanında şifrelenmiş olarak tutulmalı
Gelelim veritabanı tablolarımıza,
Kullanıcılar için Tablo yapısı şöyle olmalı,
Kullanıcılar Tablosu | ||
---|---|---|
Tablo Adı | Users | |
UserId | Primary Key, not null, int, auto increment | |
Name | not null, nvarchar(30) | |
Surname | not null, nvarchar(50) | |
not null, nvarchar(50) | ||
Nick | not null, nvarchar(30) | |
Password | not null, nvarchar(500) | |
BirthDate | not null, datetime | |
IsDeleted | not null, nvarchar(30) |
Bu tabloyu ve Database'i hazırladıktan sonra, sql server'da şöyle bir görüntü olmalı;
Not: Siyah ile boyalı yer, var olan ve bazı sebeplerden dolayı isminin görülmesini istemediğim veritabanı isimleridir.
Tablomuzu bu yapıda hazırladıktan sonra, Visual Studio'da bir proje oluşturmanızı istiyorum. Desktop Application olsun, adına istediğiniz birşey verebilirsiniz. Ben DBApp (DataBase Application) diyeceğim. Ardından karşınıza çıkan pencerede ToolBox yardımı ile, form üzerine ihtiyacınız olan alanları ekleyip, bu alanların properties penceresindeki Name değerlerini, uygun şekilde düzeltin. Hangi alanlara ihtiyacınız olacağını, yapacağınız uygulamada hangi alanları kullanıcı girebilir, hangilerini güncelleyebilir, hangilerini görebilir veya göremez gibi kriterleri düşünerek belirleyebilirsiniz. Örneğin, benim kendi uygulamamda kullanıcı, isDeleted alanını görmeyecek, çünkü bu bilgi, sadece program açısından faydalı bir bilgi. Bir kullanıcının bunu görmesine gerek yok. Uygulamamıza devam edecek olursak, form düzenlerken örnek olarak ben, her bir eleman için, elemanın türünü ve ne için kullanılacağını anlayabileceğim isimler veriyorum. Mesela ekranda isim değerini gireceğim bir textbox elementi olsun, ben bu textbox'un ismini txtName olarak değiştiriyorum. Siz de size kolay gelen bir yolu seçebilirsiniz. Formu düzenledikten sonra, projemize yeni bir class ekleyelim ve ismini de veritabanındaki tablo adımızla aynı verelim. (Örnek veritabanında tablomuz Users ise Class'ımız user olmalı). Sonra da bu class'ın içerisini, veritabanındaki kolonları karşılayacak şekilde özellikler ile dolduralım. Yani veritabanındaki her bir kolon, class'daki bir değişkene karşılık gelmeli. Ve bu class'ımızı yazarken de, anlaşılabilir comment satırları yazalım. Unutmayın, kodlarınıza comment yazmanız, hem ileriki zamanlarda kodlarınızı okurken kolaylık sağlayacak, hem de sizden başkası kodunuzu okurken anlayabilecektir.
Şimdi gelelim c# ve sql server bağlantısına ve bunun ardından ekleme, silme güncelleme işlemlerine. Bunun için öncelikle bir connection string'e ihtiyacınız olacak. Ben connection stringler için genellikle ConnectionStrings sitesini kullanıyorum, ancak yine de örnek olması için size bir örnek vereceğim.
Örnek bağlantı cümlesi;
Data Source=localhost;Initial Catalog=DbApp;Persist Security Info=True;Integrated Security=True
Eğer uzaktaki bir sql server'a bağlanıyorsanız, localhost yerine server'ınızın bulunduğu makinenin ip adresini yazmalısınız. Initial Catalog yerine, kendi database adınızı yazmalısınız. Geri kalan değerler örnekte olduğu gibi kalabilir. Gelelim kodların devamına, iki çeşit kod göstereceğim, hangisini kullanmak isterseniz kullanın, ben kendi kullandığımı da söyleyeceğim;
1 -
private string connStr = "Data Source=localhost;Initial Catalog=DBApp;Persist Security Info=True;Integrated Security=True";
/// <summary>
/// add user to users table and return the last inserted id
/// </summary>
/// <param name="user">user to add</param>
/// <returns>last inserted id</returns>
public int AddUser(User user)
{
SqlConnection con = new SqlConnection(connStr);
string query = "INSERT INTO Users (Name, Surname, Nick, Mail, Password, BirthDate, IsDeleted) " +
"VALUES(@Name, @Surname, @Nick, @Mail, @Password, @BirthDate, 0);SELECT SCOPE_IDENTITY();";
SqlCommand cmd = new SqlCommand(query, con);
cmd.Parameters.AddWithValue("@Name", user.Name);
cmd.Parameters.AddWithValue("@Surname", user.Surname);
cmd.Parameters.AddWithValue("@Nick", user.Nick);
cmd.Parameters.AddWithValue("@Mail", user.Mail);
cmd.Parameters.AddWithValue("@Password", user.Password);
cmd.Parameters.AddWithValue("@BirthDate", user.BirthDate);
con.Open();
int retVal = Convert.ToInt32(cmd.ExecuteScalar().ToString());
cmd.Dispose();
con.Close();
con.Dispose();
return retVal;
}
2-
private string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ToString();
public int AddUser(User user)
{
int retVal;
using (SqlConnection con = new SqlConnection(connStr))
{
string query = "INSERT INTO Users (Name, Surname, Nick, Mail, Password, BirthDate, IsDeleted) " +
"VALUES(@Name, @Surname, @Nick, @Mail, @Password, @BirthDate, 0);SELECT SCOPE_IDENTITY();";
using (SqlCommand cmd = new SqlCommand(query, con))
{
cmd.Parameters.AddWithValue("@Name", user.Name);
cmd.Parameters.AddWithValue("@Surname", user.Surname);
cmd.Parameters.AddWithValue("@Nick", user.Nick);
cmd.Parameters.AddWithValue("@Mail", user.Mail);
cmd.Parameters.AddWithValue("@Password", user.Password);
cmd.Parameters.AddWithValue("@BirthDate", user.BirthDate);
con.Open();
retVal = Convert.ToInt32(cmd.ExecuteScalar().ToString());
}
}
return retVal;
}
iki kod çeşidi de istediğiniz işlemi yapacaktır, yani veritabanınızdaki istediğiniz tabloya istediğiniz kaydı ekleyecektir. Ben ikinci çeşidini kullanıyorum. Bu arada ikinci kod bloğunda gördüğünüz gibi, connStr adında bir şeyi, ConfigurationManager'dan alıyorum. Bu aslında, uygulamamızdaki app.config dosyamızda, connectionStrings elementinin altında adı connStr olan bir eleman olduğunu gösterir. Bize şöyle bi avantaj sağlar, uygulamanın kullandığı database'in adı, ya da bulunduğu makinenin ip'si değişirse, bizim sadece app.config'deki connectionstring değerimizi değiştirmemiz gerekir. 1. kod bloğunda ise, uygulamamızı tekrar derlememiz ve tekrar exe haline getirmemiz gerekecektir, ki biz bunu istemeyiz. Ayrıca ikinci kod bloğumuzda, ilk kod bloğundan farklı olarak con.Dispose() ve cmd.Dispose() satırları bulunmamaktadır. Nedeni şu ki, biz kullandığımız değişkenlerin her seferinde yok edildiğine emin olmalıyız. Çünkü bellekte boşuna yer tutmalarını istemeyiz. 1. kod bloğunda biz bunu kendimiz yapıyorken, ikinci kod bloğundaki using blokları bizim için bu işi yapıyorlar. Biliyorsunuz ki programlar, satır satır işletilmekte ve ikinci kod bloğunda, program using satırından çıkar çıkmaz, con veya cmd gibi bir değişken kalmayacak, yok edilecektir. Query'de gördüğünüz scope_identity() bölümü de bize veritabanına eklenen son kullanıcıya ait primary key değerini verecektir. Yani bir şekilde bunu kullanarak biz, veritabanına ekleme yapıldı mı yapılmadı mı onu kontrol edebiliriz.
Kullanıcı ekleme işi için, yapacaklarımız bu kadar. Şimdi Güncellemeye geçelim, güncelleme işlemi de,
public void UpdateUser(User user)
{
int retVal;
using (SqlConnection con = new SqlConnection(connStr))
{
string query = "UPDATE Users SET Name = @Name, Surname = @Surname, Nick = @Nick, Mail = @Mail, Password = @Password, " +
"BirthDate = @BirthDate, IsDeleted = @IsDeleted WHERE UserId = @UserId ";
using (SqlCommand cmd = new SqlCommand(query, con))
{
cmd.Parameters.AddWithValue("@Name", user.Name);
cmd.Parameters.AddWithValue("@Surname", user.Surname);
cmd.Parameters.AddWithValue("@Nick", user.Nick);
cmd.Parameters.AddWithValue("@Mail", user.Mail);
cmd.Parameters.AddWithValue("@Password", user.Password);
cmd.Parameters.AddWithValue("@BirthDate", user.BirthDate);
cmd.Parameters.AddWithValue("@UserId", user.UserId);
con.Open();
cmd.ExecuteNonQuery();
}
}
}
kod bloğuyla yapılabilir, Bu şekilde istediğiniz kullanıcıya ait değerleri güncelleyebilirsiniz, Ancak dikkat edin, kullanıcıya ait tüm değerler bu metoda gelmeli. Yani kullanıcıya ait tüm değerleri tek bir metod içerisinde güncelleyebilmelisiniz.
Silme işlemi için, ufak bi kandırmaca yapalım, çünkü hiçkimse veri kaybetmek istemez, bu sebeple silinen kullanıcıyı IsDeleted = true olarak işaretleyeceğiz. Yani yine güncelleme yapacağız. Bunu da yine
public void DeleteUser(User user)
{
this.UpdateUser(user);
}
kod bloğu ile yapabiliriz. Tamam tamam, kızmayın, gerçek silme işlemini de anlatacağım.
Bu işlem için, DELETE FROM komutunu kullanmanız gerekiyor. Yani şöyle bir kod bloğu işinizi görecektir;
public void PurgeUser(User user)
{
using (SqlConnection con = new SqlConnection(connStr))
{
string query = "DELETE FROM Users WHERE UserId = @UserId";
using (SqlCommand cmd = new SqlCommand(query, con))
{
cmd.Parameters.AddWithValue("@UserId", user.UserId);
con.Open();
cmd.ExecuteNonQuery();
}
}
}
Şimdi gelelim select işlemlerine ki bu sql için en zor konulardan biri, ilerleyen günlerde joinler, group by'lar ile birlikte select'ler yapacağız, işin açığı şu an için ben de tam anlamıyla biliyorum diyemem, ama birlikte öğreneceğimizi umuyorum.
İlk olarak, userId'ye göre bir user'ı alalım, çok yakın bir arkadaşımın da dediği gibi "NASIL OLUYORMUŞ BAKALIM" :) :)
Şimdi... Biz veritabanından birşeyler select edeceğimize göre, bu bize bir değer dönmeli. Peki ne dönecek? Users tablosundan bir satıra ait kolonlar döndüğümüze göre, tabiki user tipinde bir değişken dönecek. Eğer tek bir kolon seçecek olsaydık, bunu bize string veya int dönecek bir şekilde yapabilirdik. Metodumuz şöyle olmalı;
public User GetSingleUser(int userId)
{
User user = null;
using (SqlConnection con = new SqlConnection(connStr))
{
string query = "SELECT UserId, Name, Surname, Nick, Mail, Password, BirthDate FROM Users WHERE UserId = @UserId";
using (SqlCommand cmd = new SqlCommand(query, con))
{
cmd.Parameters.AddWithValue("@UserId", user.UserId);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
user = new User();
user.UserId = Convert.ToInt32(reader["BirthDate"]);
user.Name = Convert.ToString(reader["Name"]);
user.Surname = Convert.ToString(reader["Surname"]);
user.Nick = Convert.ToString(reader["Nick"]);
user.Mail = Convert.ToString(reader["Mail"]);
user.Password = Convert.ToString(reader["Password"]);
user.BirthDate = Convert.ToDateTime(reader["BirthDate"]);
}
}
}
return user;
}
Burada değinmek istediğim birkaç şey var, öncelikle, reader nesnesinden değerleri alırken kolon isimleri yerine, indexleri de kullanabilirdik, yani reader[0] da olabilirdi. Ancak bu çok tercih edilen ve güvenli bir yöntem değildir. Çünkü birisi select cümlesindeki kolon isimlerinin yerini değiştirirse sizin yazdığınız index değeri değişmiş olacaktır. İkinci nokta, biz reader nesnesinin önüne (int) (DateTime) gibi değerler yazarak, istediğimiz türe Convert edebilirdik. Ancak Convert class'ını kullanmak daha güvenli, daha performanslı ve iyi bir yöntemdir. Üçüncü nokta, biz reader nesnesinden alınan değerleri null kontrolü yapmadan user'ın değerlerine eşitledik. Çünkü veritabanında bu alanlar not null olarak ayarlandı yani bize null dönmesi mümkün değil. Ancak, sizin veritabanınızda null dönebilen alanlar varsa, bunu kendi projenizde de kontrol etmeniz gerekiyor. Şöyle ki, öncelikle Kendi örneğim üzerinden anlatayım, Örnek olarak birthDate değeri veritabanında null olabilen bir değer olsaydı ben bunu User class'ımda DateTime? birthDate şeklinde tanımlayacaktım. DateTime'ın sonundaki "?", bu değerin null olabileceğini anlatıyor. Ardından select işleminde de
if (object.ReferenceEquals(reader["BirthDate"], null))
{
user.BirthDate = null;
}
else
{
user.BirthDate = Convert.ToDateTime(reader["BirthDate"]);
}
kontrollerini yapmamız gerekecekti. Şimdi sırada bize tüm kullanıcıları dönen bir metod gerekiyor. Bunun için liste dönen bir metod yazacağız, yapısal olarak GetSingleUser metoduna benzeyecek. Ama gelin bir güzellik yapalım, bu metoda, bir tane includeDeleted adında bool bir parametre geçirelim, bu da bize eğer true ise silinen kullanıcıların (isDeleted=true) da içinde bulunduğu bir liste döndersin. Değil ise, bize sadece isDeleted = 0 olan kullanıcıları döndersin.
public List<User> GetUsers(bool includeDeleted = false)
{
List<User> users = null;
using (SqlConnection con = new SqlConnection(connStr))
{
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.AppendLine("SELECT UserId, Name, Surname, Nick, Mail, Password, BirthDate FROM Users");
if (!includeDeleted)
{
queryBuilder.AppendLine(" WHERE IsDeleted = 0");
}
using (SqlCommand cmd = new SqlCommand(queryBuilder.ToString(), con))
{
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
users = new List<User>();
while (reader.Read())
{
User user = new User();
user.UserId = Convert.ToInt32(reader["BirthDate"]);
user.Name = Convert.ToString(reader["Name"]);
user.Surname = Convert.ToString(reader["Surname"]);
user.Nick = Convert.ToString(reader["Nick"]);
user.Mail = Convert.ToString(reader["Mail"]);
user.Password = Convert.ToString(reader["Password"]);
user.BirthDate = Convert.ToDateTime(reader["BirthDate"]);
}
}
}
return users;
}
Burada biz, parametre olarak verilen includeDeleted = false kısmını şu sebeple kullandık, eğer bu metoda parametre olarak includeDeleted değeri verilmezse, bu değer false olsun, yani silinenler dahil edilmesin. Sonrasında da, eğer user'larımız varsa, bu user'lar bize verilsin. Şimdi gelelim formdaki işlemlerimize... Çünkü veritabanı işlemlerimiz şimdilik bu kadar. Daha spesifik şeyler olursa, onları da ekleyebileceğiz. ADO.Net'in en önemli yanı, her bir tablo için yapılan işlemlerin, tek bir class altında toplanmasıdır. Bu kurala dikkat edilmelidir. Örneğin user ile ilgili bir işlemi, başka bir tablo ile ilişkili bir class'da yapmamalıyız.
Form işlemleri için, öncelikle bir tane, regex'e ihtiyacımız var, neden mi? Çünkü kullanıcı bir mail girecek, ve eğer mail adresi, mail formatlarına uymuyorsa hata vermeli. Bunu sağlayacak regex'i MailRegex'te bulabiliriz, Buradaki regex ifadesini de
public bool isEmail(string txt, string pattern)
{
Regex r = new Regex(pattern);
return r.IsMatch(txt);
}
bu şekilde kullanarak, kullanıcının doğru bir mail girip girmediğini kontrol edebiliriz. Aynı zamanda kullanıcının girdiği nick bilgisinin veritabanında olup olmadığını da kontrol etmeliyiz ki bir nick birden fazla kişi tarafından kullanılamasın. Bunu da, yine UserDbOperations class'ımızın içerisindeki bir metod yardımı ile yapacağız. Ancak bu metod bize sadece, kullanıcıların nicklerinin olduğu string bir liste dönmeli, çünkü sadece nickleri kullanacağımız bir yerde tüm verilere ihtiyacımız yok. Ardından bu listede, kullanıcının girdiği nick bilgisinin olup olmadığını kontrol edecek, var ise kullanıcıya nick bilgisinin geçersiz olduğu bilgisini vereceğiz. Bunu, liste dönen bir metodla kendi algoritmanızı uygulayarak yapabilirsiniz. Ben, isEmail gibi bir metod daha ekleyerek yaptım, eğer kullanıcının nicki listemizde yoksa, geçerli, değilse nick geçersiz olacaktır. Eğer tüm veriler doğrulandıysa, kullanıcının ekleme işlemi yapılmış olacaktır.