Bunlar nesne yönelimli programlama (OOP), Nesneler arası ilişkiler (ORM), Çok katmanlı mimari (n-tier architecture), bire çok bağlantılar, POCO ve migration örneklerine değinmeye çalışacağım.
- Yazılım Uzmanları
- 13 Mayıs 2018
- 829 kez görüntülendi.
Bu yazımızda Entity Framework ile ilgili bazı konulara değineceğim. Bunlar nesne yönelimli programlama (OOP), Nesneler arası ilişkiler (ORM), Çok katmanlı mimari (n-tier architecture), bire çok bağlantılar, POCO ve migration örneklerine değinmeye çalışacağım. Tabi bu konuları ele alırken küçükte olsa bir senaryomuzun olması gerekiyor. Bunun için en bilindik yöntem Cari Hesap Kartları, Yetkili ve Adres kartları üzerinden konuyu ele almaya çalışacağım. Bu busines katmanından yola çıkarak projemizi 2. ve 3. makalelerde biraz daha genişleterek Assembler, type, invokemember getpropertyvalue ve set property value konularına da girebileceğimiz örneklere doğru yol alacağız.
Kısaca class veya tablo yapımız yukarıdaki gibi olacak. Bir Account classımız ve bu class a bağlı Contact ve Address classlarımız yer alacak. Önce projemizi oluşturarak başlayalım.
Proje tiplerinden Class Library yi seçip Solution Name i Accounting Project Name i Accounting.BusinessLayer olarak belirliyoruz. Ardından default olarak gelen Class1.cs yi projemizden silerek BusinessLayer içerisinde Add,New Folder ile Accounts klasörü oluşturuyoruz.
Artık Accounts Klasörü içerisine ilk classımız olan Account.cs oluşturabilirz.
Account Classı
using System.ComponentModel;
namespace Accounting.BusinessLayer.Accounts
{
public class Account
{
public int Id { get; set; }
public string Code { get; set; }
public string ShortName { get; set; }
public string Name { get; set; }
public string RegistrationNumber { get; set; }
public string VatNumber { get; set; }
public string VatOffice { get; set; }
public string OrderCode { get; set; }
public string PhoneNumber { get; set; }
public virtual BindingList<Contact> Contacts { get; set; }
public virtual BindingList<Address> Addresses { get; set; }
public override string ToString()
{
return Code + " - " + ShortName;
}
public Account()
{
Contacts = new BindingList<Accounts.Contact>();
Addresses = new BindingList<Accounts.Address>();
}
}
}
Bu classımızı inceleyecek olursak Primary Key olarak atayacağımız bir Id property miz var. Ardından bir cari hesap kartına ait bir kaç property mevcut. Virtual BindingList<Contact> Contacts Contact classı ile arasında one to many bağlantı kurabilmesini sağlaya bir property. Aynı şekilde Addres classı ile one to many bağlantı için bir Addresses property miz bulunuyor. Bunun dışında ToString olayını ezerek sadece Account ekrana görüntülenmek istenirse Code ve ShortName değerlerini return ediyoruz.
Bir diğer classımız Contact classı. Bu classımız Cari Hesap Kartlarına ait yetkili bilgilerini tutacak. Bu classımızın
Contact Classı
namespace Accounting.BusinessLayer.Accounts
{
public class Contact
{
public int Id { get; set; }
public int AccountId { get; set; }
public virtual Account Account { get; set; }
public bool IsDefault { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName {
get
{
return Title + " " + FirstName + " " + LastName;
}
set
{
string[] _fullName = value.Split(’ ’);
if (_fullName != null)
{
switch (_fullName.Length)
{
case 3:
Title = _fullName[0];
FirstName = _fullName[1];
LastName = _fullName[2];
break;
case 2:
FirstName = _fullName[0];
LastName = _fullName[1];
break;
case 1:
FirstName = _fullName[0];
break;
case 0:
break;
default:
Title = _fullName[0];
FirstName = _fullName[1];
for (int i = 2; i < _fullName.Length-1; i++)
{
LastName += _fullName[i] + " ";
}
break;
}
}
}
}
public string Email { get; set; }
public string GsmNumber { get; set; }
public string PhoneNumber { get; set; }
public override string ToString()
{
return Title + " " + FirstName + " " + LastName + " {" + GsmNumber + "}";
}
public Contact()
{
}
}
}
namespace Accounting.BusinessLayer.Accounts { public class Address { public int Id { get; set; } public int AccountId { get; set; } public virtual Account Account { get; set; } public bool IsDefault { get; set; } public string Name { get; set; } public string AddressDetail { get; set; } public string Country { get; set; } public string City { get; set; } public string County { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public override string ToString() { return Name + " {" + City + " / " + Country + "}"; } public Address() { } } }
#region Accounts public DbSet<Accounts.Account> Accounts { get; set; } public DbSet<Accounts.Contact> Contacts { get; set; } public DbSet<Accounts.Address> Addresses { get; set; } #endregion
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); #region Accounts #endregion }
AccountMapping
//Account Classının Mapping ayarlarını yapıyoruz. //İlk olarak Id property sinin Primary Key olduğunu tanımlıyoruz. modelBuilder.Entity<Accounts.Account>().HasKey(t => t.Id); // Code Property sinin maximum size ını belirliyoruz. // Aynı zamanda bir index oluşturarak Code property nin Unique özelliği atıyoruz. // Böylelikle tablomuza kayıtlar eklendiğinde aynı koddan birden fazla kayıt oluşturulmasını engelliyoruz. // Birden fazla kolona göre unique index oluşturmayı Contact tablosunda yapacağız. modelBuilder.Entity<Accounts.Account>().Property(t => t.Code).HasMaxLength(20).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Code", 1) { IsUnique = true })); // Diğer property lerin maximum özelliklerini belirliyoruz. modelBuilder.Entity<Accounts.Account>().Property(t => t.ShortName).HasMaxLength(50); modelBuilder.Entity<Accounts.Account>().Property(t => t.Name).HasMaxLength(100); modelBuilder.Entity<Accounts.Account>().Property(t => t.RegistrationNumber).HasMaxLength(20); modelBuilder.Entity<Accounts.Account>().Property(t => t.VatNumber).HasMaxLength(20); modelBuilder.Entity<Accounts.Account>().Property(t => t.VatOffice).HasMaxLength(50); modelBuilder.Entity<Accounts.Account>().Property(t => t.PhoneNumber).HasMaxLength(20);
Mapping işleminde HasKey kısmında hangi property nin primary key olacağını tanımlıyoruz. Bir sonraki işlemimiz Code property si. Bu property de bir unique index tanımlıyoruz. Bu işlemle aynı koddan iki tane cari hesap kartının açılmasını engelliyoruz. Ardından diğer property lerin Maximum length lerini tanımlıyoruz. Bir property nin maximum length ini tanımlamazsak Sql Server daki tabloya nvarchar max olarak kolon açacaktır.
Contact Mapping :
// Contact Classının mapping ayarlarını yapıyoruz. modelBuilder.Entity<Accounts.Contact>().HasKey(t => t.Id); // Contact classı ile Account clası arasında bire çok relation oluşturyoruz. // Burada relation oluştururken will cascade on delete özelliğini false veriyoruz. // true olarak verseydik Account silindiğinde account lara ait tüm contact lar silinecekti. modelBuilder.Entity<Accounts.Contact>().HasRequired(t => t.Account).WithMany(t => t.Contacts).HasForeignKey(t => t.AccountId).WillCascadeOnDelete(false); // Şimdi ise 3 ayrı kolona göre unique index özelliği tanımlayacağız. // Bunlar Bir Account un aynı ad ve aynı soyad a göre yetkilisi olamaz şeklinde tanımlayacağız. modelBuilder.Entity<Accounts.Contact>().Property(t=>t.AccountId).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Account_FirstLastName", 1) { IsUnique = true })); modelBuilder.Entity<Accounts.Contact>().Property(t => t.FirstName).HasMaxLength(50).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Account_FirstLastName", 2) { IsUnique = true })); modelBuilder.Entity<Accounts.Contact>().Property(t => t.LastName).HasMaxLength(50).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Account_FirstLastName", 3) { IsUnique = true })); // Burada dikkat ederseniz IndexAttribute kısmındaki Order ı her kolona farklı olarak tanımladık. // Bir unique index oluşturacak ve bu index in kolonları sırasıyla yukarıdaki gibi olacak. // Veritabanına yazmasını istemediğimiz Property leri Ignore ile belirtiyoruz. // FullName Contact Clasının içerisinde Title, FirstName, LastName den oluşuyor. // Buna tamamen uygulamada ihtiyacımız olacak. modelBuilder.Entity<Accounts.Contact>().Ignore(t => t.FullName); // Diğer propertylerin maximum length lerini tanımlıyoruz. modelBuilder.Entity<Accounts.Contact>().Property(t => t.Title).HasMaxLength(10); modelBuilder.Entity<Accounts.Contact>().Property(t => t.Email).HasMaxLength(100); modelBuilder.Entity<Accounts.Contact>().Property(t => t.GsmNumber).HasMaxLength(20); modelBuilder.Entity<Accounts.Contact>().Property(t => t.PhoneNumber).HasMaxLength(20);
Burada da önce primary key imizi tanımladık. Ardından bir cari hesap kartına ait aynı isimde ve aynı soyisimde iki tane yetkili kaydedilmesinin önüne geçebilmek için unique index oluşturuyoruz. Diğer indexten farkı bu index in 3 ayrı property si bulunuyor. Bir diğer konu ise Contact ta oluşturduğumuz FullName property si. Bu property yi de tabloya yazmasını Ignore ile engelliyoruz.
Account ile Contact arasında ilişkiyi kurarken Contact classı içerisindeki Account property sinden Account taki Account taki Contacts porperty si ile relation ı kuruyoruz. Burada ben silme işlemleri risk taşıdığından daima WillCascadeOnDelete değerini false olarak tanımlarım. Bu özelliği true yaparsak bir cari hesap kartı silindiğinde ona bağlı tüm yetkili kartları da silinmey zorlanır.
Diiğer propertylerimizin uzunluklarını tanımlayarak bu classımızın da mapping ini tamamlamış olduk.
Address Mapping :
//Address clasının mapping ayarlarını yapıyoruz. modelBuilder.Entity<Accounts.Address>().HasKey(t => t.Id); //Address classını da Account classına AccountId üzerinden bağlıyoruz. // Burada da will cascade on delete özelliğini false tanımlıyoruz. // Buradaki bağlantı optional olsaydı Address classındaki Account Id yi int? şeklinde tanımlamamız gerekirdi. modelBuilder.Entity<Accounts.Address>().HasRequired(t => t.Account).WithMany(t => t.Addresses).HasForeignKey(t => t.AccountId).WillCascadeOnDelete(false); modelBuilder.Entity<Accounts.Address>().Property(t => t.AccountId).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Account_Name", 1) { IsUnique = true })); modelBuilder.Entity<Accounts.Address>().Property(t => t.Name).HasMaxLength(50).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Account_Name", 2) { IsUnique = true })); // AddressDetail property sinin maximum length ini tanımlamadık. // migration oluştururken addressdetail nvarchar(max) olarak tanımlanmış olacak. modelBuilder.Entity<Accounts.Address>().Property(t => t.Country).HasMaxLength(50); modelBuilder.Entity<Accounts.Address>().Property(t => t.City).HasMaxLength(50); modelBuilder.Entity<Accounts.Address>().Property(t => t.County).HasMaxLength(50); // Package Manager Console dan add-migration CreateInitialize ile migrate işlemini gerçekleştiriyoruz. // update database
enable-migrations
add-migration CreateInitialize
public override void Up() { CreateTable( "dbo.Accounts", c => new { Id = c.Int(nullable: false, identity: true), Code = c.String(maxLength: 20), ShortName = c.String(maxLength: 50), Name = c.String(maxLength: 100), RegistrationNumber = c.String(maxLength: 20), VatNumber = c.String(maxLength: 20), VatOffice = c.String(maxLength: 50), OrderCode = c.String(), PhoneNumber = c.String(maxLength: 20), }) .PrimaryKey(t => t.Id) .Index(t => t.Code, unique: true); CreateTable( "dbo.Addresses", c => new { Id = c.Int(nullable: false, identity: true), AccountId = c.Int(nullable: false), IsDefault = c.Boolean(nullable: false), Name = c.String(maxLength: 50), AddressDetail = c.String(), Country = c.String(maxLength: 50), City = c.String(maxLength: 50), County = c.String(maxLength: 50), Latitude = c.Double(nullable: false), Longitude = c.Double(nullable: false), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Accounts", t => t.AccountId) .Index(t => new { t.AccountId, t.Name }, unique: true, name: "IX_Account_Name"); CreateTable( "dbo.Contacts", c => new { Id = c.Int(nullable: false, identity: true), AccountId = c.Int(nullable: false), IsDefault = c.Boolean(nullable: false), Title = c.String(maxLength: 10), FirstName = c.String(maxLength: 50), LastName = c.String(maxLength: 50), Email = c.String(maxLength: 100), GsmNumber = c.String(maxLength: 20), PhoneNumber = c.String(maxLength: 20), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.Accounts", t => t.AccountId) .Index(t => new { t.AccountId, t.FirstName, t.LastName }, unique: true, name: "IX_Account_FirstLastName"); } public override void Down() { DropForeignKey("dbo.Contacts", "AccountId", "dbo.Accounts"); DropForeignKey("dbo.Addresses", "AccountId", "dbo.Accounts"); DropIndex("dbo.Contacts", "IX_Account_FirstLastName"); DropIndex("dbo.Addresses", "IX_Account_Name"); DropIndex("dbo.Accounts", new[] { "Code" }); DropTable("dbo.Contacts"); DropTable("dbo.Addresses"); DropTable("dbo.Accounts"); }
PM> update-database Specify the '-Verbose' flag to view the SQL statements being applied to the target database. Applying explicit migrations: [201701142200572_CreateInitialize]. Applying explicit migration: 201701142200572_CreateInitialize. Running Seed method.
Database imizde tablo ve kolonları oluşturdu. Bir sonraki konuda bu bölümde değinmediğimiz Migrations klasörü içerisindeki Configuration.cs var. Bunun içerisinde Seed olayını biraz inceleyeceğiz. Bu Seed her update-database dediğimizde işlediği bölüm. İçerisine otomatik olarak oluşturulmasını istediğimiz kayıtları tanımlayacağız. Ardından DataAccess katmanına geçeceğiz.
Bu yazıya 0 yorum yapılmış.