Öncelikle C# entegrasyonu için kendiniz mongodb driverlarını kullanarak geliştirme yapabileceğiniz gibi mongorepository gibi open source kütüphanelerde mevcuttur.Codeplextten mongorepository ait dllleri indirerek bizim kendi örneğimizde yapacağımız pek çok işlemi yapabilirsiniz.
https://mongorepository.codeplex.com/SourceControl/latest
Ancak biz hem esneklik hem öğrenme amaçlı olarak bu makalede kendi sınıflarımızı yazacağız ama pek çok noktada mongorepository yazılmışı var cümlesini karşılamaktadır
Öncelikle mongodb .net driverlarını projemize eklememiz gerekiyor
Bunun için birkaç yöntem kullanabiliriz
İlk olarak github tan gerekli dllleri indirebilirsiniz.(En son release dosyalarını indirmeye dikkat edin)
https://github.com/mongodb/mongo-csharp-driver/releases
Ya da mongo dbnin nuget desteğide mevcuttur
Install-Package mongocsharpdriver
Biz kendi örneğimiz için bir tane Windows application projesi açarak nuget yardımıyla güncel driverımızı indirelim
Nuget ile dlleri kendimizi aldıgımızda proje referanslarımızda mongodb dllerini görüyor olacağız.
Ancak yinede kendi mongo db kütüphanemizi yazarken githubta bulunan örnek repositorylerden faydalanıyor olacağız.
https://github.com/rsingh85/MongoDbRepository
Öncelikle bir tane base nesne classı oluşturalım,mongo dbye gidecek bütün nesnelerimiz entitybase türetiyor olacağız.Mongodb collectionda bulunan her bir documentin bir idsi vardır ve deserialize edilirken bu idde deserialize edilir.Bsonid attribute ile ilgili propertimizin bu dökümana ait identification alanı oldugunu belirttik
namespace MongoDbSample.MongoRepository { public class EntityBase { [BsonId] public string Id { get; set; } } }
Not: Esasta bson attribute yerine direkt Objectid kullanabilirdik ancak json deserialize ederken bu şekilde kullanım size sorun çıkartıyor.Bu yüzden biz serialize işlemlerinde sorun yaşamamak için string bir alan tanımladık.ve bunu BsonId attribute ile işaretledik.
public ObjectId Id { get; set; }
İkinci olarak repository pattern yapısına uygun olarak bir tane repository interface hazırlayım.Bu interfacede temel crud işlemleri ve search ve getbyid gibi detay getiren fonksiyonlarımız olsun.
namespace MongoDbSample.MongoRepository { public interface IRepository<TEntity> where TEntity : EntityBase { WriteConcernResult Insert(TEntity entity); WriteConcernResult Update(TEntity entity); WriteConcernResult Delete(TEntity entity); IList<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate); IList<TEntity> GetAll(); TEntity GetById(string id); } }
Bu interfacede değişik olarak dikkatinizi çeken nokta WriteConcernResult olmuştur.WriteConcernResult ile mongodbden işleminize ait dönüş bilgisini alabilirsiniz.
WriteConcernResultla mongodb de hata oluşmussa nedenini vs gibi bilgilerini elde edebilirsiniz
Şimdide bu interfaceden türeyecek kendi repositorymizi MongoDbRepository ismiyle oluşturalım
using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Driver.Builders; using MongoDB.Driver.Linq; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace MongoDbSample.MongoRepository { public class MongoDbRepository<TEntity> :IRepository<TEntity> where TEntity : EntityBase { private MongoDatabase database; private MongoCollection<TEntity> collection; public MongoDbRepository() { GetDatabase(); GetCollection(); } public WriteConcernResult Insert(TEntity entity) { entity.Id = ObjectId.GenerateNewId().ToString(); WriteConcernResult result = collection.Insert(entity); return result; } public WriteConcernResult Update(TEntity entity) { if (entity.Id == null) return Insert(entity); WriteConcernResult result=collection .Save(entity); return result; } public WriteConcernResult Delete(TEntity entity) { WriteConcernResult result=collection .Remove(Query.EQ("_id", entity.Id)); return result; } public IList<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate) { return collection .AsQueryable<TEntity>() .Where(predicate.Compile()) .ToList(); } public IList<TEntity> GetAll() { return collection.FindAllAs<TEntity>().ToList(); } public TEntity GetById(string id) { return collection.FindOneByIdAs<TEntity>(id); } #region Private Helper Methods private void GetDatabase() { var client = new MongoClient(GetConnectionString()); var server = client.GetServer(); database = server.GetDatabase(GetDatabaseName()); } private string GetConnectionString() { return ConfigurationManager .AppSettings .Get("MongoDbConnectionString") .Replace("{DB_NAME}", GetDatabaseName()); } private string GetDatabaseName() { return ConfigurationManager .AppSettings .Get("MongoDbDatabaseName"); } private void GetCollection() { collection = database .GetCollection<TEntity>(typeof(TEntity).Name); } #endregion } }
Repositorymizi yukarıdaki gibi yazdıktan sonra database bağlantı bilgilerimizi aşağıdaki gibi app.configimize ekleyelim
Şimdi bir kişi ve bu kişiye ait mailleri tutan model sınıflarımızı yazalım.Burda kilit nokta eklemek istediğimiz ana nesnenin EntityBaseden türeyerek bir bsonid yani mongodb id alanına sahip olmasıdır.
namespace MongoDbSample.Model { public class Person:EntityBase { public string Name { get; set; } public string Surname{get;set;} public List<Mail> Mails { get; set; } } } namespace MongoDbSample.Model { public class Mail { public string MailAddress { get; set; } public bool IsBusiness{get;set;} } }
Sınıflarımızı oluşturdak sonra ui projemize bir tane button ekleyip mongo dbye ilk kaydımızı ekleyelim
private void btnInsert_Click(object sender, EventArgs e) { Person p = new Person(); p.Name = "Bilgehan"; p.Surname = "Yıldız"; p.Mails = new List<Mail>() { new Mail(){ IsBusiness=true, MailAddress="abc@d.com"}, new Mail(){ IsBusiness=false, MailAddress="e@f.com"}, }; MongoDbRepository<Person> Repository = new MongoDbRepository<Person>(); WriteConcernResult result= Repository.Insert(p); if (!result.HasLastErrorMessage) { MessageBox.Show("Kayıt başarı ile eklendi"); } }
Görüldüğü üzere oluşturdugumuz sınıfın adında ve sınıftaki propertylere göre veritabanımıza kayıt eklendi peki biz eğer veritabanındaki kolonlarla .net sınıfmızdaki alanların farklı olmasını istersek.
Temelde baktıgımızda mongodbye kayıt atmak ve kaydı okumak serialization işlemleri dolayısıyla burada nasıl ki datacontract serializerda propertylere alias vermek için [DataMember(Name=”xxxxx “)] gibi yöntemler kullanıyorsak ise BsonElement kavramı devreye
devreye giriyor örneğimizi test etmek amaclı Mail sınıfında bulunan IsBusiness alanını IsBusinessMail yapalım ama geçmiş kayıtlarla uyumlu olması açısından database bu alanın eski adıyla IsBusiness olarak atılmasını istiyorum BsonElementin attributeın name özelliği burada devreye giriyor
public class Mail { public string MailAddress { get; set; } [BsonElement("IsBusiness")] public bool IsBusinessMail{get;set;} }
Şimdi Windows applicationdaki kayıt ekleme örneğimizdede alanın adını IsBusinesstan IsBusinessMaile çevirelim ve tekrar çalıştıralım.
private void btnInsert_Click(object sender, EventArgs e) { Person p = new Person(); p.Name = "Ümit"; p.Surname = "Gündüz"; p.Mails = new List<Mail>() { new Mail(){ IsBusinessMail=true, MailAddress="mybusinessmail@d.com"}, new Mail(){ IsBusinessMail=false, MailAddress="mypersonelmail@f.com"}, }; MongoDbRepository<Person> Repository = new MongoDbRepository<Person>(); WriteConcernResult result= Repository.Insert(p); if (!result.HasLastErrorMessage) { MessageBox.Show("Kayıt başarı ile eklendi"); } }
Görüldüğü üzere propertyimize bir nevi alias vermiş olduk.Bu şekilde serializable işlemlerinde size yardımcı olabilecek farklı attributelarda mevcuttur
Örnek olarak .net classınıza koydugunuz ancak database kaydının atılması istediğiniz alan varsa BsonIgnore attribute kullanabilirsiniz tıpkı xml serialization işlemlerinde kullandınız XmlIgnore attribute gibi
Veritabanında hangi alana collectiona kayıt atacağımız ise repositorydeki getcollection metodundan kalıyor bizim mantıgımızda classın adı collectionın adına eşit oluyor ancak isterseniz custom attributelar yapıp bu metodu değiştirerek istediğiniz collection adına göre kayıt işlemi yapmanızda mümkün.
Mesela mongorepository kütüphanesinde CollectionName diye bir attribute yaratıp bu attribute varsa collection adını buradan al yoksa sınıf adından al gibi bir yöntem geliştirmişler ,kendi mongo kütüphanenizi yazacaksanız bile mongorepository open source açıp kodlarına bakmak faydalı olabilir.
[AttributeUsage(AttributeTargets.Class, Inherited = true)] public class CollectionName : Attribute { ………… private static string GetCollectioNameFromInterface<T>() { string collectionname; // Check to see if the object (inherited from Entity) has a CollectionName attribute var att = Attribute.GetCustomAttribute(typeof(T), typeof(CollectionName)); if (att != null) { // It does! Return the value specified by the CollectionName attribute collectionname = ((CollectionName)att).Name; } else { collectionname = typeof(T).Name; } return collectionname; }
Diğer metodlarımızada kısaca göz atalım
Örnek search metodumuz linq sorgusu yazar gibi kullanabiliyoruz
private void btnSearch_Click(object sender, EventArgs e) { MongoDbRepository<Person> Repository = new MongoDbRepository<Person>(); var Searchlist = Repository.SearchFor(p=>p.Name.Contains("Bilgehan")); }
Örnek olarak mongodbde bir kayıt güncelleme kaydın son halini çekip yeni bir mail ekleme örneği yapalım
private void btnUpdate_Click(object sender, EventArgs e) { MongoDbRepository<Person> Repository = new MongoDbRepository<Person>(); Person p= Repository.GetById("553365800529210aa0a24054"); p.Mails.Add(new Mail() { IsBusinessMail = false, MailAddress = "extraaddedmail@f.com" }); WriteConcernResult result = Repository.Update(p); if (!result.HasLastErrorMessage) { MessageBox.Show("Kayıt başarı ile güncellendi"); } }
Bu şekilde de bu yazımızda mongodb c# entegrasyonun temel halini görmüş olduk.
At gibi olmuş
Ellerinize sağlık hocam Çok güzel ayrıntılı çalışma olmuş.Asenkron metodlar malumunuz yeni driverlara konuldu. Onunla ilgili bir çalışmanız var mı? Saygılarımla….
Merhabalar yeni driverla beraber bu makaledeki bazı metodlar (minor değişikler) değişti.
Asenkronlarla ilgili bir çalışma yapmadım maalesef.İlginiz için teşekkür ederim.
Merhaba, gayet güzel bir makale ellerinize sağlık fakat yeni sürümle beraberinde bir sürü problem getirmekte. Bununla ilgili bir çalışmanız var mı? Bilgi verebilir misiniz?
Teşekkürler.
Merhabalar,evet haklısınız mongodb .net library 2.0 lı versiyonları ile epey bir farklılık getirmiş durumda repository bazındada artık MongoCollection yerine IMongoCollection üzerinden işlemlerimizi yapmamız gerekiyor,ancak asenkron metodlar ve iç içe sorgular konusunda yenilikler getiriyor.Konu ile alakalı olarak kendi projelerimde yeni sürüme geçip kullandım ,ancak henüz makale olarak yazma fırsatım olmadı.Özetle IMongoCollection üstünden repositorynizi kurgulamanız 1.0larda olan özellikle insert,update,deletelerde kullanılan WriteConcernResult classından vazgeçmemiz gerekiyor.Araştırdıgım kadarıyla 3.0 kütüphanesi çıkana kadar yine 1.0 daki bu özellikler kullanılabilecek.iyi Çalışmalar
Selamlar,
Çok güzel ve faydalı bir çalışma olmuş, elinize sağlık.
Merhaba
Değerli bilgilerinizi bizimle paylaştığınız için teşekkür ederim. Yalnız size bir sorum olacak. Sipariş>Sipariş Stokları>Stok şeklinde 3 tane class ım tablom var. Entityde sipariş stokları classına tanımlamış olduğum stok virtual property sinden stok ile ilgili bilgileri alabiliyorum. Mongoda yapı nasıldır, nasıl tasarlamak gerekiyor?
Bir sipariş stoğu eklendiğinde stok değerlerini her kaleme atmak mantıksız geliyor(Mongo yapısı bu şekilde ama).
Bu konu hakkındaki değerli bilgilerinizi rica ederim.
Mongodb yapısı gereği döküman base tabanlıdır.Ve cok fazla update yapılması önerilmez.Yapın şöyle olsa Kişi->Siparişleri->Sipariş yorumları gibi hepsi aynı kişi altında tek dökümanda tutulabilirsin.Mongodb de de bahsettiğin sipariş ve stokları ayrı tablolarda tutuabilirsin.Ama baktıgın zaman stok direkt kişi nesnesinin altında değil ,bu durumdada ilişkisel veritabanındadaki gibi belli keylerle tabloları ilişkilendirmen lazım.Update maliyetini düşünerek tasarım yapmanız gerekli burada.Mesela uygulamana ait logları tutmak istersen mongodb iyi bir alternatif.Çünkü logu sen bir bütün olarka tek bir objede tutacaksın kolay sorgulama yapabileceksin ama logun updateti olmayacak.
Merhabalar. Makaleniz güzel olmuş. Ancak bunu “Dependency Injection” mantığı ile nasıl yöneteceğiz? Bu kısım eksik olmuş gibi.
Merhaba mongodb driver yapısı değişti yeni yapı ile beraber WriteConcernResultlar da kalktı dolayısıyla dependency injection uyumlu genel classları daha kolay yapabilirsiniz.Makaleden bağımsız olarak dependency injection uyumlu yapılar yapmak başka bir makale konusu