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.

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()
        {

        }
    }
}
Bu classımızı incelediğimizde de aynı şekilde primary key olarak Id property si bulunuyor. Tabi neden baseclass oluşturup Id yi içerisine atıp bu classları ondan türetmedin diyebilirsiniz. Anlaşılabilir olması için bu yöntemi seçtim. Burada dikkatimizi çeken bir diğer konu AccountId ve Account property leri. Bunlar Account ile arasında bağlantı kurmamızı sağlayacak olan propertyler. Burada dikkat etmemiz gereken bir diğer property ise FullName. FullName in mapping ini tanımlarken ignore ile database e yazmasını engelleyeceğiz. Bunun nasıl olacağını gösterebilmek için FullName diye bir property oluşturdum. Bu property nin Get inde Title, FirstName ve LastName birleştirilerek dönüyor. Setinde ise yazılan Fullname i arasındaki boşluklara göre bölümleyerek Title, FirstName ve LastName e yazılacak değerlerin kararını vermeye çalışıyoruz. Account ta olduğu gibi ToString olayını override edip geriye kişinin ad soyad ve telefon numarasını dönüyoruz. 
 
Bu yazımızın son class ı Address. Bu da her Cari Hesap Kartının birden fazla adres bilgilerini tutabilmesini sağlayacağız. 
 
Address Classı
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()
        {

        }
    }
}
Bu classımızda da diğer iki classımızda olduğu gibi primary key özelliği katacağımız Id property si mevcut ve bir adreste ihtiyaç duyulan diğer genel propertyleri bu classımıza ekledik. ToString aynı şekilde Adresin adını, ülke ve şehir bilgisini dönüyor. 
 
 
 
Bu classlarımızı oluşturduktan sonra SQL Server da bir veritabanı oluşturmamız gerekiyor. Ben veritabanımı Accounting olarak oluşturdum. Ardından Projemizde BusinessLayer üzerinde sağ tıklayıp EntityFramework Code First ile SQL Server ımızda oluşturduğumuz veritabanına bağlayarak Connection ımızı oluşturuyoruz. Bunları oluşturduktan sonra bize AccountingContext olarak Contextimizi kendisi yaratıyor. 
Ardından AccountingContext imizin içerisine geçerek aşağıdaki gibi DbSet lerimizi oluşturalım.
 
        #region Accounts
        public DbSet<Accounts.Account> Accounts { get; set; }
        public DbSet<Accounts.Contact> Contacts { get; set; }
        public DbSet<Accounts.Address> Addresses { get; set; }
        #endregion
Burada dbset lerimizi oluşturmazsak Context tanımladığımızda bu classların değerlerine ulaşamayız. 
Şimdi ise OnModelCreating içerisine girerek Accounts region arasına Mapping lerimizi  yazacağız. 
        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 
Son mappingimiz olan address mapping ini de tanımladık. Burada yaptığımız işlem de diğer iki class ta olduğu gibi primary key imizi tanımladık. Ardından Unique index oluşturduk. HasRequired diyerek Account taki Addresses property sinden Address teki Account Id ile iki class arasında relation kurduk. Daha sonraki örneklerimizde senaryomuzu daha genişleterek HasOptional relation üzerinde duracağız. 
 
Mappinglerimizi tanımladıktan sonra Package Manager Console a geçerek sırasıyla enable-migrations ile migration özelliğini açıyoruz. Ardından add-migration [migration_Adı] ile yazdığımız mapping kurallarına göre sql e göndereceği sorguyu hazırlamasını sağlıyoruz. 
 
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");
        }
Oluşturduğu migration ı inceleyecek olursak bir up bir de down olayı mevcut. Up olayında update database ile sql de oluşturacağı işlemler mevcut. işlemi geri almak istediğimizde update-database -target [migration_adı] veya update-database -target:0 dediğimizde de down işlemini çalıştıracak ve bu migration da oluşturulan tablo, kolon, index leri geri silecek. 
 
Up içerisinde Account tablomuzu oluşturabilmek için CreateTable içerisinde property lerin mappinglerindeki tanımlara göre Primarykey, maxlength ve index leri oluşturdu. Aynı şekilde Address ve Contact classlarının da tablolarını oluşturan Createtable kodunu hazırladı. Contact tablosunun sorgusuna dikkat edecek olursak bunun içerisinde FullName property sini ignore olarak tanımladığımız için görmüyoruz. 
 
Şimdi Update Database zamanı 
 
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. 

 

 

İlgili Makaleler

Bu yazıya 0 yorum yapılmış.

Yorum Gönder