İ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:- 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/104085List: Ordered collection of entities, duplicate allowed. Use a .net IList in code. The index column will need to be mapped in NHibernate.
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.
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 IListFirmalar { 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: