.Net Core’da DbContext ile Database’de Yapılan Değişiklikleri, ElasticSearch’de Saklama
Selamlar,
Sizden gelen istek ve sorular ışında, bu makalede güncel iş hayatında da bolca kullanılan, audit denilen Databasedeki değişiklikleri DbContext middleware katmanında yakalayıp, daha sonra sorgulama amaçlı ElasticSearch’e kaydedeceğiz. Bunun için Entity Framework 6 ve üstünde bir framework ile çalışılması gerekmektedir. Platform .Net Core. Bu projede sadece Update işleminin log’u tutulacaktır. Diğerleri size bırakılmıştır. Peki bunu neden yapıyoruz? Çünkü loglamak hayati bir önem taşımaktadır. Değişen bir kaydın önceki halini saklamak, bazen hata durumlarında tam bir can simididir. Ayrıca Bankalar, online alışveriş siteleri gibi işin içinde para geçen tüm projelerde audit, ayrı bir önem kazanmaktadır.
Şimdi gelin ilk sorulması gereken soruya cevap bulalım. Değişiklik olan datanın hangi alanlarını logluyacağız?
Models/ChangeLog.cs:
- Hangi Tablo’da “EntityName“
- Hangi Kayıt “PrimaryKeyValue“
- Hangi Alan “PropertyName“
- Değişen Datanın Ilk Değeri “OldValue“
- Değişen Datanın Son Değeri “NewValue“
- Data Nasıl Değişti “State“
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ChangeLog { public string EntityName { get; set; } public string PropertyName { get; set; } public int PrimaryKeyValue { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } public System.DateTime DateChanged { get; set; } public EnumState State { get; set; } } public enum EnumState { Update = 1, Delete = 2, Added = 3 } |
Ne yapacağımız belli. Peki bu kaydı nerde yapmalıyız ? Her kaydetme işleminde mi yapmalıyız ? Ekstra kod yazmadan en az efor ile nasıl yapabiliriz ? Sürekli her yere kendini tekrarlayan koddan nasıl kaçınabiliriz? Data Annotation mı kullansam, yoksa Action Fitler yazıp “OnActionExecuted“‘da mı yakalasam. Hiçbiri değil. Bunların hepsi bir şekilde olsa da, kod tekrarı gerektiren çözüm yollarıdır. Her yeni Action eklendiğinde, en azından üstüne bir Fitler konması gerekmektedir. Her kaydetme işleminden bahsetmiyorum bile. Bu projede, piyasada C# kullanan projelerin %80’inde tercih edilen, Entity Framework mimari yapısı kullanılmıştır. En üst katman olarak bizim de çözümümüz, ya DB katmanında yani Tabloya olan Trigger ile ki bu da sunucuya çok fazla yük bindireceğinden dolayı çok ihtiyaç olmadıkça tercih edilmemelidir. Ya da Data Acess Katmanında DBContext middleware’inde ilgili loglar tutularak yapılmalıdır..
Gelin test senaryosu için “auidtDB” adında Mvc Projemizi yaratalım.
1 |
dotnet new mvc -o auditDB |
Şimdi senaryo gereği, üzerinde işlem yapacağımız Product modelimizi oluşturalım.
Models/Product.cs: Aşağıda görüldüğü gibi “ID” Primary Index olarak tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 |
using System.ComponentModel.DataAnnotations; public class Product{ [Key] public int ID { get; set; } public string Name { get; set; } public string SerieNo { get; set; } public int TotalCount { get; set; } public decimal Price { get; set; } public string WarehouseAddress { get; set; } } |
Gelelim DAL katmanı DBContext’e.
ProductContext.cs: İşte tüm bu loglama işinin yapılacağı esas kısm burasıdır. Bu projede, loglamaya ait kod işlemleri, buranın dışında herhangi bir yerde yapılmamaktadır.
- DBContext olarak “ProductContext” ve üzerinde işlem yapılacak entity de “Products” olarak tanımlanmıştır.
- OnConfiguring(): Database Azure üzerinde bir SqlServer’da bulunmaktadır. Connection string burada tanımlanır.
- SaveChanges() : DBContext’in save işleminin yapıldığı yer burasıdır. Önce değişen tablolar bulunur ve değiştiği alanların önceki ve sonraki halleri kaydedilir.
- “ChangeTracker.Entries()” :Tüm entry’ler alınır.
- “.Where(p => p.State == EntityState.Modified).ToList()” : Update olanlar bir listeye atılır.
- “foreach (var change in modifiedEntities”) : Tam değişen Entry’ler tek tek gezilir.
- “var entityName = change.Entity.GetType().Name” : Değişen tablonun ismi alınır. Bu projede sadece “Product” bulunmaktadır.
- “var PrimaryKey=change.OriginalValues.Properties.FirstOrDefault(prop=>prop.IsPrimaryKey()==true).Name” : Bu entry’e ait [Key] yani PrimaryKey alanı adı bulunur. Bu örnek de “ID” alanına denk gelmektedir.
- “foreach (IProperty prop in change.OriginalValues.Properties)” : İlgili entry’e ait tüm propertyler yani kolonlar gezilir.
- “var originalValue = change.OriginalValues[prop.Name].ToString();var currentValue = change.CurrentValues[prop.Name].ToString();” : İlgili field’ın önceki ve sonraki değerleri alınır.
- “if (originalValue != currentValue)” : Değişiklik var mı diye bakılır.
- “ChangeLog log = new ChangeLog()” : Eğer değişiklik var ise loglanma amaçlı ChangeLog modeli oluşturulur.
- “EntityName = entityName” : Tablo adı alınır.
- “PrimaryKeyValue = int.Parse(change.OriginalValues[PrimaryKey].ToString())” : Product tablosuna ait PrimaryKey yani, “ID” değeri kaydedilir.
- “OldValue = originalValue, NewValue = currentValue,”: Değişen field’ın eski ve yeni değerleri kaydedilir.
- “DateChanged” : Değişim zamanı kaydedilir.
- “State = EnumState.Update” : Bu projede sadece Update işleminin log’u tutulacaktır. Burada yapılan işlemin tipi kaydedilmektedir.
- “ElasticSearch.CheckExistsAndInsert(log)” : Tüm çekilen Log bilgisi ElasticSearch’e kaydedilir. Bir sonraki aşamada bu method derinlemesine incelenecektir.
- “return base.SaveChanges()” Tüm loglama işleminden sonra, base’e ait Save() işlemine ait sonuç geri dönülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using System; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; public class ProductContext : DbContext { public DbSet<Product> Products { get; set; } override protected void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("Server=*****Product.database.********;Initial Catalog=ProductDB;Persist Security Info=False;User ID=******;Password=******;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"); } public override int SaveChanges() { try { // Değişen Var mı? Boya Var mı :) var modifiedEntities = ChangeTracker.Entries() .Where(p => p.State == EntityState.Modified).ToList(); var now = System.DateTime.UtcNow; foreach (var change in modifiedEntities) { var entityName = change.Entity.GetType().Name; var PrimaryKey=change.OriginalValues.Properties.FirstOrDefault(prop=>prop.IsPrimaryKey()==true).Name; foreach (IProperty prop in change.OriginalValues.Properties) { var originalValue = change.OriginalValues[prop.Name].ToString(); var currentValue = change.CurrentValues[prop.Name].ToString(); if (originalValue != currentValue) //Sadece Değişen kayıt Log'a atılır. { ChangeLog log = new ChangeLog() { EntityName = entityName, PrimaryKeyValue = int.Parse(change.OriginalValues[PrimaryKey].ToString()), PropertyName = prop.Name, OldValue = originalValue, NewValue = currentValue, DateChanged = now, State = EnumState.Update }; //Değişen kayıt Log'u ElasticSearch'e atılır. ElasticSearch.CheckExistsAndInsert(log); } } } return base.SaveChanges(); } catch (Exception ex) { var error = ex.Message; return 0; } } } |
ElasticSearch/ElasticSearch.cs: .Net Core üzerinde ElasticSearch hakkında daha detaylı bilgiye buradan erişebilirsiniz. Öncelikle gelin neden elastik search’ü tercih ettik, onu tartışalım. Eğer binlerce kayıt üzerinde anlık değişimin yoğun olduğu bir ortamda, ve bizden belli bir ID’ye ait ürünün son 1 yıldaki fiyat değişim grafiği istense, Relational DB yerine NoSql çözümlerine gidilmesi ve bunlardan biri olarak Elastic Search’ün kullanılması, hiç de yanlış olmaz.
Eğer makinanızda ElasticSearch kurulu ise, terminal açılarak alttaki komutun çalıştırılması ile, 9200 portundan ElasticSearch kolayca ayağı kaldırılır.
Ayrca ElasticSearch’ün monütor edilebilmesi için, eğer kurulu ise aşağıdaki gibi “Kibana” yazılarak, 5601 portundan ayağı kaldırılması sağlanır.
- “private static readonly ConnectionSettings connSettings” : İle Elastic Search’ün connection yolu, performans için static olarak tanımlanmıştır.
- “ChangeLog” modeline göre ilgili index oluşturulur.
- “private static readonly ElasticClient elasticClient = new ElasticClient(connSettings);” : Performas amaçlı singleton ElasticClient nesnesi, tüm clientlar için ortak olarak oluşturulmuştur.
- “CheckExistsAndInsert()” : Eğer Elastic Search’e atılmak istenen “ChangeLog” modeli, daha önceden Elastic Search’e ait “change_log” Index’i oluşturulmamış ise önce ilgili index oluşturulur sonra istenen model ElasticClient’a atanır.
- “var indexSettings = new IndexSettings();”,” indexSettings.NumberOfReplicas = 1;”,”indexSettings.NumberOfShards = 3;” : Yeni index oluşturulup Replica ve Shardingleri tanımlanır.
- “var createIndexDescriptor = new CreateIndexDescriptor(“change_history”)” : “change_history” adında index yaratılır.
- “.Mappings(ms => ms .Map<ChangeLog>(m => m.AutoMap()) )” : ChangeLog modeline göre Mapleme işi yapılır.
- “.InitializeUsing(new IndexState() { Settings = indexSettings })” : Yukarıda tanımlı index ayarları, yeni oluşturulan bu “change_history” Index’ine atanır.
- “.Aliases(a => a.Alias(“change_log”));” : “change_histoy” adından farklı olarak, ilgili index’a “change_log” adında bir alias atanmıştır. Böylece ilgili index güncellenmek istendiğinde alias adına göre yeni bir index yaratılarak eski index silinebilir. Bu şekilde, var olan index’e ait kayıtların silinmesi engellenir.
- “elasticClient.Index<ChangeLog>(log, idx => idx.Index(“change_history”));” : Eğer “change_log” index’i yok ise oluşturulup, yeni gönderilen “ChangeLog” datasi, Elastic Search’ün “change_history” index’i altına kaydedilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using System; using Nest; public static class ElasticSearch { private static readonly ConnectionSettings connSettings = new ConnectionSettings(new Uri("http://localhost:9200/")) .DefaultIndex("change_history") .MapDefaultTypeIndices(m => m .Add(typeof(ChangeLog), "change_history")); private static readonly ElasticClient elasticClient = new ElasticClient(connSettings); public static void CheckExistsAndInsert(ChangeLog log) { //elasticClient.DeleteIndex("change_log"); if (!elasticClient.IndexExists("change_log").Exists) { var indexSettings = new IndexSettings(); indexSettings.NumberOfReplicas = 1; indexSettings.NumberOfShards = 3; var createIndexDescriptor = new CreateIndexDescriptor("change_history") .Mappings(ms => ms .Map<ChangeLog>(m => m.AutoMap()) ) .InitializeUsing(new IndexState() { Settings = indexSettings }) .Aliases(a => a.Alias("change_log")); var response = elasticClient.CreateIndex(createIndexDescriptor); } elasticClient.Index<ChangeLog>(log, idx => idx.Index("change_history")); } } |
Buraya kadar olan kısımda, DBContext middleware katmanında “SaveChanges()” methodu override edilerek, ilgili güncelleme yapılan datanın değişen kolonları tek tek gezilerek bulunmuş ve değişen her bir kolona ait “ChangeLog” modeli oluşturulmuştur. Ayrıca belirli bir connection’a göre bağlanılan Elastic Search’de yoksa “change_log” Index’i oluşturulup, değişen datalar bu Index altına kaydedilmişlerdir.
Sıra elimizdeki Product ürünleri Listeleyip, düzenleyeceğimiz CRUD sayfalarını oluşturmaya geldi.
HomeController.cs:
- Product ürünlerin tamanın çekilip dönüldüğü yer Index() Action’ıdır.
- Detail(int? ID) Action’ında ilgili ID’ye göre product bulunup, üzerinde işlem yapılması amacı ile geri dönülmektedir.
- Update(Product product) methodunda, 2 işlem yapılmaktadır. Eğer işlem yapılması istenen Product’ın ID’si yok ise bu yeni bir ürün demektir. Bu durum da “var newProduct = new Product();” ile yeni yaratılan Product’a ait tüm propertyler atanıp, “await dbContext.Products.AddAsync(newProduct);” ile dbContext’e asenkron olarak eklenir. Bu durumda tüm data yeni girildiği için, yukarıda override ettiğimiz SaveChanges() methodunda “ChangeLog” modeline herhangi bir kayıt atılmayacaktır. Eğer Product’a ait bir “ID” değer var ise, bu durumda ilgili product bulunup, yeni değerleri üzerine güncellenir. İşte bu durumda, en sonda çalışan “dbContext.SaveChanges()” methodu ile Product’a ait değişen kolonların eski ve yeni değerleri o anki tarih değeri ile “ChangeLog” modeline atanıp, Elastic Search’e kaydedilir. Böylece Update() methodunun sonunda, ürün ya güncellenir ya da yenisi kaydedilir.
- Önemli Not: Burada dikkat edilmesi gereken konu, loglama işinin var olan projenin dışında, yani o anki geliştirmeden bağımsız bir şekilde ekstra efor gerektirmeden DB katmanında yapılmasıdır. Bu da bize farklı projelerde ve farklı guruplarda, istenen tablolar bazında, efor sarf etmeden kolaylıkla loglamayı sağlar :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using auditDB.Models; namespace auditDB.Controllers { public class HomeController : Controller { public IActionResult Index() { using (ProductContext dbContext = new ProductContext()) { var data = dbContext.Products.ToList(); return View(data); } } public IActionResult Detail(int? ID) { Product product = null; if (ID == null) { product = new Product(); } else { using (ProductContext dbContext = new ProductContext()) { product = dbContext.Products.FirstOrDefault(prod => prod.ID == ID); } } return View(product); } public async Task<IActionResult> Update(Product product) { using (ProductContext dbContext = new ProductContext()) { if (product.ID == 0) { var newProduct = new Product(); newProduct.Name = product.Name; newProduct.SerieNo = product.SerieNo; newProduct.Price = product.Price; newProduct.TotalCount = product.TotalCount; newProduct.WarehouseAddress = product.WarehouseAddress; await dbContext.Products.AddAsync(newProduct); } else { var updateProduct = dbContext.Products.FirstOrDefault(prod => prod.ID == product.ID); updateProduct.Name = product.Name; updateProduct.SerieNo = product.SerieNo; updateProduct.Price = product.Price; updateProduct.TotalCount = product.TotalCount; updateProduct.WarehouseAddress = product.WarehouseAddress; } dbContext.SaveChanges(); } return RedirectToAction("Index"); } } } |
Index.cshtml: Aşağıda görüldüğü gibi Razor ile gelen “List<Product>” model, içinde gezilerek sayfaya basılmaktadır. Bir de sayfa üzerinde “Yeni Kayıt Gir” ve herbir ürüne ait “Detay” adında buttonlar bulunmaktadır. Her ikisi de “Detail()” action’ına gitmektedir. Yalnız yeni kayıt’da ID parametresi yok iken, product’a ait detay sayfası için parametre olarak, ilgili secilen ürüne ait “ID” değeri gönderilmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@model List<Product> <div class="container"> <div class="jumbotron"> <h3>Borsoft & Warehouse</h3> </div> <hr> <a href="/Home/Detail"><input type="button" value="Yeni Kayıt Gir" class="btn btn-success"></a> <table class="table"> <thead> <th></th> <th>ID</th> <th>Ad</th> <th>Seri No</th> <th>Toplam Adet</th> <th>Fiyat</th> <th>Depo Adresi</th> </thead> <tbody> @foreach(var product in Model) { <tr> <td> <a href="/Home/Detail/@product.ID"><input type="button" value="Detay" class="btn btn-danger"></a> </td> <td> @product.ID </td> <td>@product.Name</td> <td>@product.SerieNo</td> <td>@product.TotalCount</td> <td>@product.Price</td> <td>@product.WarehouseAddress</td> </tr> } </tbody> </table> </div> |
Detail.cshtml: Aşağıda görüldüğü gibi Detay sayfası hem bir yeni ürün giriş hem de seçilen bir ürünün güncelleme sayfasıdır. Sayfada “Form.Post” kullanılmaktadır.
- @using(Html.BeginForm(“Update”,”Home”,FormMethod.Post)) : Güncelleme veya Kaydetme işlemi Home Controller’ın “Update()” Action’ında yapılmaktadır.
- btnText=Model.ID > 0 ? “Güncelle” : “Kaydet” : Kaydetme button’unun üzerine gelen Product model’in ID’si var ise “Güncelle” yok ise “Kaydet” yazısı yazılır.
- “<input type=”hidden” id=”ID” name=”ID” value=”@Model.ID”>” : İlgili Form içerisinde, hidden olarak var ise, product’ın ID’si tutulmalıdır. Aksi takdirde güncelleme durumunda “ID”, ilgili Action’a null olarak gönderilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
@model Product <div class="container"> <div class="jumbotron"> <h3>Borsoft Warehouse Admin</h3> </div> <hr> @using(Html.BeginForm("Update","Home",FormMethod.Post)) { string btnText = ""; btnText=Model.ID > 0 ? "Güncelle" : "Kaydet"; <table class="table"> <tr> <td> Ad: </td> <td> <input type="text" value="@Model.Name" id="Name" name="Name"> </td> </tr> <tr> <td> Seri No: </td> <td> <input type="text" value="@Model.SerieNo" id="SerieNo" name="SerieNo"> </td> </tr> <tr> <td> Toplam Sayı: </td> <td> <input type="text" value="@Model.TotalCount" id="TotalCount" name="TotalCount"> </td> </tr> <tr> <td> Fiyat: </td> <td> <input type="text" value="@Model.Price" id="Price" name="Price"> </td> </tr> <tr> <td> Depo Adres: </td> <td> <textarea cols="30" rows="5" id="WarehouseAddress" name="WarehouseAddress">@Model.WarehouseAddress</textarea> <input type="submit" value=@btnText class="btn btn-warning"> <!-- @if(Model.ID>0) { <input type="submit" value="Güncelle" class="btn btn-warning"> } else { <input type="submit" value="Kaydet" class="btn btn-warning"> } --> </td> </tr> </table> <input type="hidden" id="ID" name="ID" value="@Model.ID"> } |
Productlarda değişiklik yapıldığı zaman, “Elastic Search“‘deki Log kayıtları “Kibana” ile aşağıdaki gibi görülmektedir .
Bu makalede, iş hayatında da bolca kullanılan bir konuya hep beraber parmak bastık. Kullanılan teknolojiler değişse de, mantığı değişimeyen yegane şey Log tutmaktır. Eğer sizin de log dosyanız çok büyük ve yüksek trafikli bir projede ilgili dosyalardan anlık sorgulama yapmanız gerekiyor ise, Elastic Search’e bir şans vermenizi şiddet ile tavsiye ediyorum. Ayrıca eğer DB katmanında EntityFramework kullanıyorsanız, loglama işlemlerini DbContext’de “SaveChanges()” methodu üzerinde yapmanız, sizi birçok zahmet ve kod kalabalığından kurtaracaktır.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Source Code: https://github.com/borakasmer/AuditDB
Elinize sağlık hocam, güzel bir konu olmuş.
Yalnız şöyle bir sorum olacak; Modelimizde Relational bir alanımız olursa ve bu alan değişirse bunu nasıl yakalayabiliriz?
Şimdiden teşekkürler
Merhaba,
ChangeTracker.Entries sana ilgili modelde ki bütün değişiklikleri verir. Yanın Product sınfıında ProductBarcode isimli ayrı bir list oduğunu düşünelim. Alt tabloda bir değişiklik olduğunda ChangeTracker.Entries().Where(e => e.State == Modified) sorgusunda bu değişiklikte gelecektir.
Merhaba Hocam,
Transactional bir işlemi elastic search ile nasıl yönetebiliriz? Logları veritabanına kaydettiğimiz senaryoda bütün işlem aynı transaction içinde olduğundan herhangi bir hatada rollback olacağı için tutarlılığı sağlamış oluyoruz. Ama farklı bir kaynağa gönderdiğimizde tutarlılığı sağlayabilmemiz için neler yapmamız gerekiyor?
Teşekkürler.
Selam Berdan,
Öncelikle sorun nefis :) Mesela Redisdeki kayıtları düşünelim. Redis’de transection var da, o daha çok transection cık :) Ne yapıyoruz peki redise atılan datalarda bir sorun olabileceği için, manuel yapıyoruz :) Memento Design Pattern mantığındaki aynı Undo Redo’da olduğu gibi bir önceki hallerini tablo:columnName:id keyword’ü ile NoSql tipi bir db’de tutup, hata olursa o sistem buradan eski hallerini geri gönderiyor.
Kolay gele…
Merhaba,
benim her seferinde old data & new data aynı geliyor. Sebebi sanırım _dbSet.AsNoTracking() kullandığımız generic repository de böyle cağırıp atach edip kullanıyor. Bu durum için bir öneriniz var mı?
Selamlar,
NoTracking readonly datada kullanılan bir yapıdır. Doğal olarak eski yeni değerler alınamaz. NoTracking kullanmanız durumunda, yeni tarih bilgisini elle manuel çekmeniz ve eskisini de yine elle bu esnada güncellemeniz sorunu çözebilir.
Bu eski değeri verir: var originalEntity = context.MyEntities.AsNoTracking().FirstOrDefault(me => me.MyEntityID == myEntity.MyEntityID);
Yeni değeri de elle set edebilirsiniz.
Merhabalar Hocam,
Anlatışınız sunumunuz çok iyi geldi bana. Her bir videonuzu izlediğimde bir okadar sarılıyorum kod yazmaya. Her defasında aa bu böylemiymiş diyorum ve yeni yeni şeyleri sayenizde öğreniyorum. Emeğinize sağlık. Her paylaşımınız için ayrı ayrı çok çok teşekkür ediyorum hocam.
Teşekkür ederim Nurullah.
Bu güzel yorumlarınız bana moral oluyor :)
Para parayı, bilgi bilgiyi çeker:)
İyi çalışmalar.
Hocam merhaba biraz konu dışı olabilir ama bir sorum var :)
.NET Core ile .Net arasında ki fark nedir ve .NET Core ile MVC arasında ki farklar nelerdir? .Net Core u neden kullanmalıyız vs.. bu konu da çok yabancıyım ve anlaşılır bir makale bulamadım, bu konuda yardımcı olursanız sevinirim.
Selam Umut,
Şunu bir izlemende fayda var: https://youtu.be/wyKZZTT_S70
İyi çalışmalar.
Merhaba. Hocam VsCode ‘da .cshtml tarafında intellisense çalışmıyor .. Yardım edebilirmisiniz
Dönün artık amerikadan hocam sorularımız var yaaa :D
Döndüm :)
Hocam merhaba.dbcontext sınıfının dışında başka bir sınıfta savechanges metodunu nasıl override edebilirim.
Database code first model olusturyorum ve her seferinde dbcontext sinifim tekrar oluşuyor.tesekkur ederim
Selamlar,
DBContext sınıfını her seferinde oluşturmana gerek yok. Autofac veya ninject gibi yapılarla bunu aşabilirsin.
İyi çalışmalar.
Merhaba hocam,
Emeğinize sağlık. Çok yararlı bir post idi.
Teşekkürler İsmail,
İşine yaradı ise ne mutlu bana :)
Selam Bora abi bir sorum olacak changetracker ile değişikliği buluyorum bana şöyle bir senaryo gerekiyor id değişiyor ama elimde yeni id nin verisi var bir propertynin bağlı olduğu (foreignkey) modeli bulup o modeldeki entityi çekip json vb şekilde almam gerekiyor bu nasıl mümkün olabilir
Bora hocam selam :)
Bu gerekliydi gerçi gene ben mysql e kaydediyorum ama olsun :)
burada originalValue ve currentValue hep aynı geliyordu kaynaklarınızda birinde bir düzeltme olmuş onu buraya not bırakayım :)
Elinize sağlık.
Replace: var originalValue = change.Property(prop.Name).OriginalValue;
to: var originalValue = change.GetDatabaseValues().GetValue(prop.Name);