Aklımda Kalası Kelimeler

* давайте работать вместе
* Zarf ve Mazruf, Zerafet(xHoyratlık) ile aynı kökten(za-ra-fe) gelir
* Bedesten
* Suç subuta ermiştir - Suç sabit olmuştur

14 Ocak 2014 Salı

Fluent NHibernate Notlarım

İsimlendirme Yöntemleri

namespace Ornek.NHibernate
{
    public static class NHibernateHelper
    {
        public static ISessionFactory CreateSessionFactory()
        {
// .Conventions.Add(conventions) tarafında eklenmek üzere isimlendirme kurallarımızı içeren dizi
            IConvention[] conventions =
              {
                  new MyPrimaryKeyConvention(),
                  new MyNameConvention()
              };

            return Fluently.Configure()
                .Database(MySQLConfiguration.Standard.ConnectionString(c => c.FromConnectionStringWithKey("test_nh")))
                .Mappings(m => m
                    .FluentMappings.AddFromAssemblyOf()
                    .Conventions.Add(conventions))
                .ExposeConfiguration(cfg =>
                                     {
                                         //var a = cfg;
                                         //Debugger.Break();
                                         // Eğer db nin yaratılmasını istemiyor, varolan db ye sadece maplemek istiyorsak return ile çıkarız,
                                         return;
                                         // ya da db yaratmasını isteriz
                                         new SchemaExport(cfg).Create(false, true);
                                     })
                .BuildSessionFactory();
        }
    }
// Maksadım denelemer yapmaktı ama aşağıdaki convention sınıflarının bir örneklerini 
// yukarıdaki dizide kullandığımızı bilmeniz yeterli
    public class MyPrimaryKeyConvention : IIdConvention
    {
        public void Apply(IIdentityInstance instance)
        {
            if (instance.Name.StartsWith("_"))
            {
                var yeni = instance.Name.Remove(0, 1);
                instance.Column(yeni);
                return;
            }
        }
    }

    public class MyForeignKeyConvention : ForeignKeyConvention
    {
        protected override string GetKeyName(Member property, Type type)
        {
            // property == null for many-to-many, one-to-many, join 
            // property != null for many-to-one
            var refName = property == null
                ? type.Name
                : property.Name;
            return string.Format("{0}Id", refName);
        }
    }

    public class MyNameConvention : IPropertyConvention
    {
        public void Apply(IPropertyInstance instance)
        {
            var regexString = @"([A-Z][\w^[A-Z]]*)([A-Z][\w^[A-Z]]*)*";
            var newName = Regex.Replace(instance.Name, regexString, (m => (m.Index != 0 ? "_" : "") + m.Value)).ToUpper();
            //instance.Column(newName);

            if (instance.Name.StartsWith("_"))
            {
                var yeni = instance.Name.Remove(0,1);
                instance.Column(yeni);
                return;
            }

            //instance.Length(50);
            //instance.Not.Nullable();
        }
    }
}

CASCADE

Ref:
  1. stackoverflow.com
  2. ayende.com

  • none - do not do any cascades, let the users handles them by themselves.
  • save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
  • delete - when the object is deleted, delete all the objects in the association.
  • delete-orphans - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
  • all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
  • all-delete-orhpans - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.

AsBag(), AsSet(), AsList()

Ref: http://stackoverflow.com/a/1921727/104085
  1. List: Ordered collection of entities, duplicate allowed. Use a .net IList in code. The index column will need to be mapped in NHibernate.

  2. Set: Unordered collection of unique entities, duplicates not allowed. Use Iesi.Collection.ISet in code. It is important to override GetHashCode and Equals to indicate the business definition of duplicate. Can be sorted by defining a orderby or by defining a comparer resulting in a SortedSet result.

  3. Bag: Unordered list of entities, duplicates allowed. Use a .net IList in code. The index column of the list is not mapped and not honored by NHibernate.


Tek yönlü HasMany

using System.Collections.Generic;
using FluentNHibernate.Mapping;

namespace Tekfark.SuccessStory.FNHibernate
{
    public class Firma
    {
        public virtual int _Id { get; set; }
        public virtual string _Ad { get; set; }
        public virtual string _VD { get; set; }
        public virtual string _VNo { get; set; }
        public virtual string _EPosta { get; set; }
        public virtual IList _FirmaAdresleri { get; set; }
    }

    public class FirmaMap : ClassMap
    {
        public FirmaMap()
        {
            Id(x => x._Id, "Id");
            Map(x => x._Ad, "Ad");
            Map(x => x._EPosta, "EPosta");
            Map(x => x._VD, "VD");
            Map(x => x._VNo, "VNo");
            HasMany(x => x._FirmaAdresleri).KeyColumn("FirmaId").Cascade.All();

            Table("Firmalar");
        }
    }
}

Tek taraflı HasManyToMany


Program.cs
using System.Collections.Generic;
using Test_NH;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            using (var session = NHibernateHelper.CreateSessionFactory().OpenSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    var firma = new Firma()
                                {
                                    Ad = "firma adı",
                                    Adresleri = new List()
                                                {
                                                    new Adres()
                                                    {
                                                        BinaAdi = "cemo bina",
                                                        BinaNo = "110",
                                                        BinaHede = "ahuhu",
                                                    }
                                                }
                                };
                    session.SaveOrUpdate(firma);
                    tx.Commit();
                }
            }

        }
    }
}

HibernateHelper.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using FluentNHibernate;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using NHibernate;
using NHibernate.Tool.hbm2ddl;

namespace Test_NH
{
    public static class NHibernateHelper
    {
        public static ISessionFactory CreateSessionFactory()
        {
            IConvention[] conventions =
            {
                new MyPrimaryKeyConvention(),
                new MyNameConvention()
            };


            return Fluently.Configure()
                .Database(MySQLConfiguration.Standard.ConnectionString(c => c.FromConnectionStringWithKey("test_nh")))
                .Mappings(m => m
                    .FluentMappings.AddFromAssemblyOf()
                    .Conventions.Add(conventions))
                .ExposeConfiguration(cfg => new SchemaExport(cfg).Create(true, true))
                .BuildSessionFactory();
        }
    }

    public class MyPrimaryKeyConvention : IIdConvention
    {
        public void Apply(IIdentityInstance instance)
        {
            if (instance.Name.StartsWith("_"))
            {
                var yeni = instance.Name.Remove(0, 1);
                instance.Column(yeni);
                return;
            }
        }
    }

    public class MyForeignKeyConvention : ForeignKeyConvention
    {
        protected override string GetKeyName(Member property, Type type)
        {
            var refName = property == null
                ? type.Name
                : property.Name;
            return string.Format("{0}Id", refName);
        }
    }

    public class MyNameConvention : IPropertyConvention
    {
        public void Apply(IPropertyInstance instance)
        {
            if (instance.Name.StartsWith("_"))
            {
                var yeni = instance.Name.Remove(0, 1);
                instance.Column(yeni);
            }
        }
    }

}

Firma.cs
using System.Collections;
using FluentNHibernate.Mapping;

namespace Test_NH
{
    public class Firma
    {
        public virtual int Id { get; set; }
        public virtual string Ad { get; set; }
        public virtual string VD { get; set; }
        public virtual string VNo { get; set; }
        public virtual string EPosta { get; set; }
        public virtual IList Adresleri { get; set; }
    }

    public class FirmaMap : ClassMap
    {
        public FirmaMap()
        {
            Id(x => x.Id);
            Map(x => x.Ad);
            Map(x => x.EPosta);
            Map(x => x.VD);
            Map(x => x.VNo);

            HasManyToMany(x => x.Adresleri)
                .LazyLoad()
                .AsBag()
                .ParentKeyColumn("FirmaId")
                .ChildKeyColumn("AdresId")
                .Table("FirmaAdresleri")
                .Cascade.All();

            Table("Firmalar");
        }
    }
}

Adres.cs
using FluentNHibernate.Mapping;

namespace Test_NH
{
    public class Adres
    {
        public virtual int Id { get; set; }
        public virtual string BinaAdi { get; set; }
        public virtual string BinaNo { get; set; }
        public virtual string BinaHede { get; set; }
    }
    

    public class AdresMap : ClassMap
    {
        public AdresMap()
        {
            Id(x => x.Id);
            Map(x => x.BinaAdi);
            Map(x => x.BinaNo);
            Map(x => x.BinaHede);

            Table("Adresler");
        }
    }
}

Tablo oluşturma scripti
alter table FirmaAdresleri  drop foreign key FK71A64230891C7B59



alter table FirmaAdresleri  drop foreign key FK71A642307D8D2989


    drop table if exists Adresler

    drop table if exists Firmalar

    drop table if exists FirmaAdresleri

    create table Adresler (
        Id INTEGER NOT NULL AUTO_INCREMENT,
       BinaAdi VARCHAR(255),
       BinaNo VARCHAR(255),
       BinaHede VARCHAR(255),
       primary key (Id)
    )

    create table Firmalar (
        Id INTEGER NOT NULL AUTO_INCREMENT,
       Ad VARCHAR(255),
       EPosta VARCHAR(255),
       VD VARCHAR(255),
       VNo VARCHAR(255),
       primary key (Id)
    )

    create table FirmaAdresleri (
        FirmaId INTEGER not null,
       AdresId INTEGER not null
    )

    alter table FirmaAdresleri
        add index (AdresId),
        add constraint FK71A64230891C7B59
        foreign key (AdresId)
        references Adresler (Id)

    alter table FirmaAdresleri
        add index (FirmaId),
        add constraint FK71A642307D8D2989
        foreign key (FirmaId)
        references Firmalar (Id)





Çift yönlü many-to-many

Sadece Adres.cs değişecek:
using System.Collections.Generic;
using FluentNHibernate.Mapping;

namespace Test_NH
{
    public class Adres
    {
        public virtual int Id { get; set; }
        public virtual string BinaAdi { get; set; }
        public virtual string BinaNo { get; set; }
        public virtual string BinaHede { get; set; }
        public virtual IList Firmalar { get; set; }
    }
    

    public class AdresMap : ClassMap
    {
        public AdresMap()
        {
            Id(x => x.Id);
            Map(x => x.BinaAdi);
            Map(x => x.BinaNo);
            Map(x => x.BinaHede);

            HasManyToMany(x => x.Firmalar)
                .AsBag()
                .Table("FirmaAdresleri")
                .ParentKeyColumn("AdresId")
                .ChildKeyColumn("FirmaId");

            Table("Adresler");
        }
    }
}

Sonuç aynı.

Ara tablo



Sonuç:


Ara tablo hem otomatik oluşacak hem ek alanları olabilecek

İstediğim şey Kisi ve Firma adresleri girilebilen 2 tablo ve bu adreslerin kişiye göre tanımlarını içerebilmesi. Örneğin bulunduğum bina benim için genel merkez iken bir başka çalışan için Antalya ofisi bir başkası için Bölge Merkezi olabilir. Adres aynı tanımları ve ilgili kişi ve firmaları farklı. Örneğin bu kat ve dairede yan masadaki arkadaşım A şirketi için çalışırken ben B şirketi için aynı adresi kullanıyor olabilirim. Bu durumda Tanım alanı adresten bağımsız olmalı ve Kisi_Adresleri, Firma_Adresleri tablolarına bağlı olmalı.

Peki nasıl yapacağız..?

Önce şunu söyleyeyim. Ben sınıfa bağlı değişkenleri _(alt çizgi) ile başlatarak ayrıştırmak hepsini görebilmek istiyorum ama veritabanı alanları oluşturulurken _(alt çizgi) olmasın diye alanların isimlerini ayrıca belirttiğimi göreceksiniz.

Adres sınıfı:
public class Adres
{
    public virtual int _Id { get; set; }
    public virtual Ulke _Ulke { get; set; }
    public virtual Sehir _Sehir { get; set; }
    public virtual Ilce _Ilce { get; set; }
    public virtual string _BinaAdi { get; set; }
    public virtual string _BinaNo { get; set; }
    public virtual string _Cadde { get; set; }
    public virtual string _SokakMahalle { get; set; }
    public virtual string _PostaKodu { get; set; }
    public virtual string _Koordinat { get; set; }

    public virtual IList<Firma> _Firmalar { get; set; }
    public virtual IList<Kisi> _Kisiler { get; set; }
}
Bir adres birden fazla Firma veya Kisiye ait olabilir. Yani A firmasında 5 kişi aynı adresi kullanabilir ya da 5 Firma aynı adreste bulunabilir. Adres tarafından bakılınca HasMany ama Firma ve Kisi tarafından da bakılınca HasMany var. O halde HasManyToMany ilişkisi kurulacak. IList<> generic tipi ile hem Firma hem de Kisi tiplerini taşıdığını belirteceğiz.
Herşey yolunda gidiyor gibi ;)

Adres sınıfının alanlarını veritabanına bağlama işine bakalım:
Ulke, Sehir, Ilce alanları başka tablolardan ForeignKey olarak gelecek. O halde References ile bağlıyoruz.
HasManyToMany ile de birden fazla Kisi ve Firma ilişkisi olabileceğini ve aynı şekilde onlarında birden fazla Adres bilgisi olabileceğini vurguladık. Bu sayede çoktan çoka bir ilişki için ortak bir tablonun yaratılmasını sağladık.
Tablonun adına "Kisi_Adresleri" ve "Firma_Adresleri" dedik.
Bu tablolara önce FK olarak gidecek kendi PK(primary key) alanımızı ParentKeyColumn metodunu kullanarak, sonrada karşılığında hangi tablonun PK sının geleceğini ChildKeyColumn metoduyla bağladık.
Son olarak herhangi bir değişiklik yaşandığında iki taraftada değişikliğin uygulanmasını istediğimizi belirttik(Cascade.All()).
public class AdresMap : ClassMap<Adres>
{
    public AdresMap()
    {
        Id(x => x._Id, "Id");
        Map(x => x._BinaAdi, "BinaAdi");
        Map(x => x._BinaNo, "BinaNo");
        Map(x => x._Cadde, "Cadde");
        Map(x => x._SokakMahalle, "SokakaMahalle");
        Map(x => x._PostaKodu, "PostaKodu");
        Map(x => x._Koordinat, "Koordinat");

        References(x => x._Ulke, "UlkeId").Cascade.None();
        References(x => x._Sehir, "IlId").Cascade.None();
        References(x => x._Ilce, "IlceId").Cascade.None();

        HasManyToMany<Firma>(x => x._Firmalar)
            .AsBag()
            .Table("Firma_Adresleri")
            .ParentKeyColumn("AdresId")
            .ChildKeyColumn("FirmaId")
            .Cascade.All();

        HasManyToMany<Kisi>(x => x._Kisiler)
            .AsBag()
            .Table("Kisi_Adresleri")
            .ParentKeyColumn("AdresId")
            .ChildKeyColumn("KisiId")
            .Cascade.All();

        Table("adresler");
    }
}


Şimdi Firma sınıfına bakalım:
public class Firma
{
    public virtual int _Id { get; set; }
    public virtual string _Ad { get; set; }
    public virtual string _VD { get; set; }
    public virtual string _VNo { get; set; }
    public virtual string _EPosta { get; set; }
    public virtual IList<Adres> _Adresleri { get; set; }
}
IList tipinde ve içerisinde Adres tipini barındıran property sayesinde bir Firma'nın birden fazla (HasMany) Adresi olabilecek. Ama Adres tarafında da HasMany ilişkisi olduğuna göre HasManyToMany ilişkisinden bahsedebiliriz.

Map (ingilizceyi türkçeye karıştırmaktan tiksiniyorum ama bitmeyecek gibi geliyor paylaşımım) dosyasına bakalım.
public class FirmaMap : ClassMap<Firma>
{
    public FirmaMap()
    {
        Id(x => x._Id, "Id");
        Map(x => x._Ad, "Ad");
        Map(x => x._EPosta, "EPosta");
        Map(x => x._VD, "VD");
        Map(x => x._VNo, "VNo");

        HasManyToMany<Adres>(x => x._Adresleri)
            .LazyLoad()
            .AsBag()
            .ParentKeyColumn("FirmaId")
            .ChildKeyColumn("AdresId")
            .Table("Firma_Adresleri")
            .Cascade.All();

        Table("Firmalar");
    }
}
Burada açıklanacak şey LazyLoad olabilir. Demek istiyoruz ki; buna ihtiyaç duyduğumuz vakit çalıştır ve sunucuda harekete geç, aksi halde çalışma.


Şimdi Kisi sınıfı ve Map dosyasına bakalım:
public class Kisi
{
    public virtual int _Id { get; set; }
    public virtual string _Adi { get; set; }
    public virtual string _IkinciAdi { get; set; }
    public virtual string _Soyadi { get; set; }
    public virtual DateTime _DogumTarihi { get; set; }
    public virtual bool _Cinsiyeti { get; set; }
    public virtual IList<Adres> _Adresleri { get; set; }
}

public class KisiMap : ClassMap<Kisi>
{
    public KisiMap()
    {
        Id(x => x._Id,"Id");
        Map(x => x._Adi,"Adi");
        Map(x => x._Cinsiyeti,"Cinsiyeti");
        Map(x => x._DogumTarihi, "DogumTarihi");
        Map(x => x._IkinciAdi, "IkinciAdi");


        HasManyToMany<Adres>(x => x._Adresleri)
            .LazyLoad()
            .AsBag()
            .Table("Kisi_Adresleri")
            .ParentKeyColumn("KisiId")
            .ChildKeyColumn("AdresId")
            .Cascade.All();

        Table("Kisiler");
    }
}


NHibernate ayar dosyası şöyle:
public static class NHibernateHelper
{
    public static ISessionFactory CreateSessionFactory()
    {
        return Fluently.Configure()
            .Database(MySQLConfiguration.Standard.ConnectionString(c => c.FromConnectionStringWithKey("test_nh")))
            .Mappings(m => m.FluentMappings
                .AddFromAssemblyOf()
                )
            .ExposeConfiguration(cfg =>
                                    {
                                        #region Sıfırdan tablolar yaratılsın istiyorsan

                                        var config = new SchemaExport(cfg);
                                        config.Create(true, true);

                                        #endregion

                                        #region Sadece yenilikler eklensin istiyorsan
                                        //var config = new SchemaUpdate(cfg);
                                        //config.Execute(false, true); 
                                        #endregion
                                    })
            .BuildSessionFactory();
    }
}


Yukarıdaki kodlar otomatik olarak "Firma_Adresleri" ve "Kisi_Adresleri" tablolarını oluşturacak ve içine "FirmaId, AdresId" ve "KisiId, AdresId" alanlarını ekleyecek.
Böyle bir ilişkiyi kullanmak istediğimizde aşağıdaki basit istemci kodunu çalıştırabileceğiz:
using (var session = NHibernateHelper.CreateSessionFactory().OpenSession())
{
    using (var tx = session.BeginTransaction())
    {
        var ulke = new Ulke
                    {
                        _UlkeAdi = "Türkiye"
                    };
        session.Save(ulke);

        // Böyle de adres eklenebilir
        var adres2 = new Adres()
                    {
                        _BinaAdi = "2222 bina",
                        _BinaNo = "1110",
                        _Cadde = "Cadde adı",
                        _SokakMahalle = "Sokak adı",
                        _PostaKodu = "84151",
                        _Ulke = session.Get(1)
                    };

        var firma = new Firma()
                    {
                        _Ad = "firma adı",
                        _Adresleri = new List()
                                        {
                                            // Böyle de 
                                            new Adres()
                                            {
                                                _BinaAdi = "111 cem bina",
                                                _BinaNo = "1110",
                                                _Ulke = session.Get(1)
                                            },
                                            adres2
                                        }
                    };
        session.SaveOrUpdate(firma);
        tx.Commit();
    }
}

Sonuçta bu oldu:



Ama benim istediğim ortak bir tablo olarak başka alanlarda ekleyebilmek. Mesela bu firmaya bu adres hangi tarihte eklendi, aktif mi, hangi kullanıcı ekledi v.s.
Bunu yapmak için oluşturalacak bu tabloyu bir entity olarak NHibernate içinde tanımlamak. Bunun içinde "Kisi_Adresleri", "Firma_Adresleri" adında iki sınıf ve map sınıflarını oluşturup ek olan alanları burada tanımlamak.

Kisi_Adresleri sınıfı ve Map dosyası:
public class KisiAdresi
{
    public virtual int _Id { get; set; }
    public virtual string _Tanimi { get; set; }
    public virtual Adres _Adres { get; set; }
    public virtual Kisi _Kisi { get; set; }
}

public class KisiAdresiMap : ClassMap<KisiAdresi>
{
    public KisiAdresiMap()
    {
        Id(x => x._Id, "Id");
        Map(x => x._Tanimi, "Tanimi");
        References(x => x._Adres, "AdresId").Cascade.All();
        References(x => x._Kisi, "KisiId").Cascade.All();
        Table("Kisi_Adresleri");
    }
}

Firma_Adresleri sınıfı ve Map dosyası:
public class FirmaAdresi
{
    public virtual int _Id { get; set; }
    public virtual string _Tanimi { get; set; }
    public virtual Adres _Adres { get; set; }
    public virtual Firma _Firma { get; set; }
    public virtual DateTime _TarihEklenme { get; set; }
    public virtual bool _Aktif { get; set; }

}

public class FirmaAdresiMap : ClassMap<FirmaAdresi>
{
    public FirmaAdresiMap()
    {
        Id(x => x._Id, "Id");
        Map(x => x._Tanimi, "Tanimi");
            
        // Her kayıt ilk girişte aktif olacaktır.
        Map(x => x._Aktif, "Aktif")
            .Default("1");
            
        // timestamp tipinde
        Map(x => x._TarihEklenme, "TarihEklenme")
            .CustomSqlType("timestamp");

        References(x => x._Adres, "AdresId").Cascade.All();
        References(x => x._Firma, "FirmaId").Cascade.All();

        Table("Firma_Adresleri");
    }
}

Şimdi görebileceğiniz üzere Tanim, Aktif ve TarihEklenme diye 3 alan daha ekledim. Bu alanların güncellenmesini ise aşağıdaki gibi istemci koduyla yapabiliriz:
using (var session = NHibernateHelper.CreateSessionFactory().OpenSession())
{
    using (var tx = session.BeginTransaction())
    {
        var ulke = new Ulke
                    {
                        _UlkeAdi = "Türkiye"
                    };
        session.Save(ulke);

        // Böyle de adres eklenebilir
        var adres2 = new Adres()
                    {
                        _BinaAdi = "2222 bina",
                        _BinaNo = "1110",
                        _Cadde = "Cadde adı",
                        _SokakMahalle = "Sokak adı",
                        _PostaKodu = "84151",
                        _Ulke = session.Get<Ulke>(1)
                    };

        var firma = new Firma()
                    {
                        _Ad = "firma adı",
                        _Adresleri = new List<Adres>()
                                        {
                                            // Böyle de 
                                            new Adres()
                                            {
                                                _BinaAdi = "111 cem bina",
                                                _BinaNo = "1110",
                                                _Ulke = session.Get<Ulke>(1)
                                            },
                                            adres2
                                        }
                    };
        session.SaveOrUpdate(firma);

        // Tanım bilgisi giriliyor...
        var a = session
            .QueryOver<FirmaAdresi>()
            .Where(q => q._Firma._Id == firma._Id && q._Adres._Id == adres2._Id)
            .Take(1)
            .List()[0];
        a._Tanimi = "Yok tanım manım";

        session.SaveOrUpdate(a);

        tx.Commit();
    }
}

Şimdilik en son sonuç şudur:



NHibernate Event Listeners