Derinlemesine Entity Framework .Net Core
Selamlar,
Bu makalede, .Net Core 3.1.101 üzerinde Entity Framework’ün farklı bir kaç özelliğine değineceğiz.
Senaryo 1:
Yapılacak iş, bir Entity yani bir tablo güncellenirken belli bir işaretleyici ile işaretlenen kolonlar, o an ki zamanı değer olarak alacaklardır. Burada amaç, belli bir işaretleyici ile belirlenmiş alanlara, işaretlenme tipine göre farklı işlemler yapılabilme yeteğinin, global olarak kazandırılmasıdır.
Örnek Senaryo ile açıklama: [dolar] attribute’ü ile işaretlenmiş kolonların kaydedilme ananda, o an ki değerleri ile doların kur değerinin çarpılarak value olarak atanmasıdır.
SetCurrentDate: İlgili kolona atanacak, custom attribute’dür. İstenen tüm entitylerin, kolonuna atanabilir. Kısaca flagleme dediğimiz, bir işaretleyicidir.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; namespace DB.PartialEntites { [AttributeUsage(AttributeTargets.All)] public class SetCurrentDate : System.Attribute { public SetCurrentDate() { } } } |
Bu senaryoda DB First kullanılmıştır. Değiştirilmek istenen kolon, “Employees” Entitiy’sindeki “HireDate” property’si dir.
Employees:
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 |
using System; using System.Collections.Generic; namespace DB.Entities { public partial class Employees { public Employees() { EmployeeTerritories = new HashSet<EmployeeTerritories>(); InverseReportsToNavigation = new HashSet<Employees>(); Orders = new HashSet<Orders>(); } public int EmployeeId { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public string Title { get; set; } public string TitleOfCourtesy { get; set; } public DateTime? BirthDate { get; set; } public DateTime? HireDate { get; set; } public string Address { get; set; } public string City { get; set; } public string Region { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public string HomePhone { get; set; } public string Extension { get; set; } public byte[] Photo { get; set; } public string Notes { get; set; } public int? ReportsTo { get; set; } public string PhotoPath { get; set; } public virtual Employees ReportsToNavigation { get; set; } public virtual ICollection<EmployeeTerritories> EmployeeTerritories { get; set; } public virtual ICollection<Employees> InverseReportsToNavigation { get; set; } public virtual ICollection<Orders> Orders { get; set; } } } |
PartialEntities :DB First ile oluşan Entity sınıfına müdahale edilemeyeceği için, kısaca edilir ise bir daha ilgili Entity classlar oluştuğunda üstüne ezilebileceği için, aşağıdaki gibi BaseEntity’den türeyen yeni bir Employee Partial class’ı aşağıdaki gibi oluşturulur.
1 2 3 4 5 6 7 |
namespace DB.Entities { class PartialEntites { } public partial class Employees : BaseEntity { } } |
BaseEntity: Tüm entitylerin türetildiği, abstract classdır. UsedTime adında bir property’si ve WriteLog() şeklinde bir methodu vardır. UsedTime [NotMapped] olarak işaretlenmiştir. Çünkü DB’de bu alana karşılık gelen bir alan yoktur. Sadece, uygulama içindeki bussinesda kullanılacaktır. Ve tüm entityler için geçerlidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; using System.ComponentModel.DataAnnotations.Schema; namespace Core { public abstract class BaseEntity { private DateTime dateTime; [NotMapped] public DateTime UsedTime { get { this.dateTime = DateTime.Now; return dateTime; } set { } } public void WriteLog() { Console.WriteLine("".PadRight(40, '*')); Console.WriteLine($"UseTime: {UsedTime.ToLongDateString()}"); Console.WriteLine("".PadRight(40, '*')); } } } |
Önemli: Şimdi sıra geldi en önemli kısma. Sınıfa ait var olan bir property’i, attribute ile işaretlemek. Partial bir class’da, var olan bir property’e ayrıca bir attribute eklemeye çalışır iseniz, böyle bir kolon var şeklinde hata alırsınız. Çünkü DBContext ile Partial Class birbiri ile çakışır. Çözüm “MetaData” sınıf’ı oluşturmaktır. Bu işaretleme ile “Employee” tablosunda var olan “HireDate” kolonu, “SetCurrentDate” attribute’ü ile işaretlenmiştir.
EmployeeMetaData: Bu metada sınıfı ile, üzerinde değişiklik yapılmak istenen sınıf işaretlenir.
1 2 3 4 5 |
public class EmployeeMetaData { [SetCurrentDate] public DateTime? HireDate { get; set; } } |
Ve ilgili “MetaData” sınıfını, Employee partial class’ı üzerinde, aşağıdaki gibi tanımlanır. Böylece, biraz zahmetli de olsa, Employee sınıfına ait [HireDate] kolonu ==> [SetCurrentDate] attribute’ü ile işaretlenmiş olunur.
1 2 3 |
//Employees Partial Class'ının var olan HireDate kolonuna [SetCurrentDate] attribute'ü eklenmiştir. [MetadataType(typeof(EmployeeMetaData))] public partial class Employees : BaseEntity { } |
Sıra geldi “SetCurrentDate” ile işaretlenen kolonun, GeneralRapository katmanında bulunup güncelleme sırasında o an ki zamanın atanmasına:
- Entity üzerinde tanımlı tüm Metadata tipleri yakalanır. ==> “System.ComponentModel.DataAnnotations.MetadataTypeAttribute[] metadataType”
- Yakalanan metadatalar gezilerek, propertyleri alınır. ==> “System.Reflection.PropertyInfo[] properties = metadata.MetadataClassType.GetProperties()”
- Her bir yakalanan metadataya ait property kümesi, tek tek gezilir. ==> “foreach (System.Reflection.PropertyInfo pi in properties)”
- Metadataya ait tüm propertyler gezilirken, SetCurrentDate olan var ise bulunur. ==> “if (Attribute.IsDefined(pi, typeof(DB.PartialEntites.SetCurrentDate)))”
- Bulunan SetCurrentDate işaretli kolonlara, o anki zaman değeri atanır. ==> ” _context.Entry(setEntity).Property(pi.Name).CurrentValue = DateTime.Now”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public virtual void Update(IEnumerable<T> entities) { if (entities == null) throw new ArgumentNullException(nameof(entities)); //Entity üzerinde tanımlı Tüm Metadata tipleri yakalanır. System.ComponentModel.DataAnnotations.MetadataTypeAttribute[] metadataType //Yakalanan metadatalar gezilerek propertyleri alınır. foreach (System.ComponentModel.DataAnnotations.MetadataTypeAttribute metadata in metadataTypes) { System.Reflection.PropertyInfo[] properties = metadata.MetadataClassType.GetProperties(); //Her bir yakalanan metadataya ait property kümesi, gezilir. foreach (System.Reflection.PropertyInfo pi in properties) { //Metadataya ait tüm propertyler gezilir ve SetCurrentDate varsa bulunur. if (Attribute.IsDefined(pi, typeof(DB.PartialEntites.SetCurrentDate))) { // Bulunan SetCurrentDate işaretli kolonlara, o anki zaman değeri atanır. _context.Entry(setEntity).Property(pi.Name).CurrentValue = DateTime.Now; } } } _context.SaveChanges(); } |
Önemli : Böylece yukarıda görüldüğü gibi özel işaretlenmiş bir kolona, global güncelleme anında istenen değer atanmıştır. Bu örnekte, o anki zamanı atamak basit olsa da, amaç anlaşılmayı kolaylaştırmaktır. Siz isterseniz “SetCurrentEuro” ile işaretli bir alanı, o anki kur değeri ile çarpıp güncelleye de bilirsiniz.
Senaryo 2:
Güncellenecek 50 kolonlu bir Entity’nin sadece 5 kolonu güncellenecek ise, geri kalan 45 kolonun aynı değeri alması için, tek tek eşitlemek yerine gelin daha kısa bir yol izleyelim.
IRepository: UpdateMatchEntity methodu güncellenecek Entity ve güncel Entity şeklinde 2 parametre alarak, birini diğerine eşitler.
1 2 3 4 5 6 7 8 |
public interface IRepository<T> where T : BaseEntity { . . void UpdateMatchEntity(T updateEntity, T setEntity); . . } |
GeneralRepository/UpdateMatchEntity : Bu method sayesinde, sadece değişen kolonlar güncellenmiştir. Değişmeyen kolonlar için, ayrıca bir atamanın yapılmasına gerek kalmamıştır.
- “_context.Entry(updateEntity).CurrentValues.SetValues(setEntity)” : Sadece 5 kolonu güncellenen Entity, var olan entity üzerine birebir ezilir.
- “foreach (var property in _context.Entry(setEntity).Properties)” : Güncelenen son Entity’nin tüm propertyleri, tek tek gezilir.
- ” _context.Entry(setEntity).Properties” : İlgili entity’nin tüm propertylerine, “Entry().Properties” methodu ile erişilir.
- “if (property.CurrentValue == null)” : Eğer değeri null ise, gerçekte güncellenmemiş üzerine boş ezilmiş bir kolondur.
- “_context.Entry(updateEntity).Property(property.Metadata.Name).IsModified = false” : İşte tam bu durumda, ilgili boş alanın güncellenmemesi gerekmektedir. Boş olan kolon’un, “IsModified” property’si false olarak atanır. Böylece entity framework, ilgili kolonlardan sadece IsModified’ı “true” olanların değiştiğini kabul eder. “SaveChanges()” methodunda, sadece değişen kolonları günceller.
- “_context.SaveChanges()” : Durumunda sadece değeri atanmış 5 kolon güncellenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public virtual void UpdateMatchEntity(T updateEntity, T setEntity) { if (setEntity == null) throw new ArgumentNullException(nameof(setEntity)); if (updateEntity == null) throw new ArgumentNullException(nameof(updateEntity)); _context.Entry(updateEntity).CurrentValues.SetValues(setEntity);//Tüm kayıtlar, kolon eşitlemesine gitmeden bir entity'den diğerine atanır. //Olmayan yani null gelen kolonlar, var olan tablonun üstüne ezilmesin diye ==> "IsModified = false" olarak atanır ve var olan kayıtların null olarak güncellenmesi engellenir. foreach (var property in _context.Entry(setEntity).Properties) { if (property.CurrentValue == null) { _context.Entry(updateEntity).Property(property.Metadata.Name).IsModified = false; } } _context.SaveChanges(); } |
Senaryo 3:
Amaç, Entity FrameWork’ün biz farkında olmadan arkada oluşturduğu Sql Queryleri monitor etmektir.
CustomerContext: Öncelikle, yukarıdaki kütüphane indirilir. DB First ile otomatik oluşan context değil de, ondan türüyen diğer bir CustomerContext class’da “OnConfiguring()” methodu, aşağıdaki gibi override edilir. Custom yeni bir DBContext yaratılmasının nedeni, override edilen OnConfiguring() methodunun DBFirst ile yeniden oluşturulurken ezilmesinin önlenmesidir.
- #if DEBUG : Performans sebebi ile sadece debug modda oluşan sql script, output ekrana bastırılır.
- “base.OnConfiguring(optionsBuilder.UseLoggerFactory(CustomerLoggerFactory))” : Entity Create anında, Log amaçlı CustomerLoggerFactory sınıfı kullanılır.
- “= LoggerFactory.Create(builder =>” : Entity, SqlQuery oluştururken devreye girer.
- “builder .AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information) .AddDebug()” : Debug modda her şeyin basılmasını engellemek amaçlı, filter category ve level’e göre yazdırı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 |
public class CustomerContext : NorthwindContext { public CustomerContext() { } public CustomerContext(DbContextOptions<NorthwindContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { #if DEBUG base.OnConfiguring(optionsBuilder.UseLoggerFactory(CustomerLoggerFactory)); #endif #if RELEASE base.OnConfiguring(optionsBuilder); #endif } public static readonly ILoggerFactory CustomerLoggerFactory = LoggerFactory.Create(builder => { builder .AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information) .AddDebug(); }); } |
“https://localhost:44320/Customer” : GetCustomer() methodunun => “_customerService.SearchCustomer(“”, 0, 10).List.ToList()” sorgusunda oluşan query, output pencerede aşağıdaki gibi görülür.
Senaryo 4:
Aynı anda 3 verinin kaydedilmeye çalışıldığı zaman, oluşan SqlScript’i hep beraber inceleyelim.
CustomerController / InsertmultipleUser(): Aşağıda görüldüğü gibi 3 kayıt, tek seferde kaydedilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[HttpGet("InsertMultiUser")] public ServiceResponse<UserModel> InsertMultiUser() { var response = new ServiceResponse<UserModel>(HttpContext); List<UserModel> models = new List<UserModel>() { new UserModel(){Name="test1",LastName="lasttest1",UserName="ltest1",PasswordHash="Kol1K0t",Email="ltest1@mail",Gsm="1111"}, new UserModel(){Name="test2",LastName="lasttest2",UserName="ltest2",PasswordHash="Kol2K0t",Email="ltest2@mail",Gsm="2222"}, new UserModel(){Name="test3",LastName="lasttest3",UserName="ltest3",PasswordHash="Kol3K0t",Email="ltest3@mail",Gsm="3333"} }; response= _userService.InsertMultiUsers(models); response.IsSuccessful = true; response.Count = response.List.Count; return response; } |
UserService/InsertMultiUsers(): List<DB.Entities.Users> usersModel = _mapper.Map<List<DB.Entities.Users>>(users)” ==> User Entity’i, view model olan UserModel’e dönüştürülmüştür.
1 2 3 4 5 6 7 8 9 |
public ServiceResponse<UserModel> InsertMultiUsers(List<UserModel> users) { var response = new ServiceResponse<UserModel>(null); List<DB.Entities.Users> usersModel = _mapper.Map<List<DB.Entities.Users>>(users); _usersRepository.Insert(usersModel); response.IsSuccessful = true; response.List = users; return response; } |
GereralRepository/Insert(): Gönderilen 3 User Entity, tek tek Entities’e eklenerek, SaveChanges() methodu çağrılmıştır.
1 2 3 4 5 6 7 8 9 10 |
public virtual void Insert(IEnumerable<T> entities) { if (entities == null) throw new ArgumentNullException(nameof(entities)); foreach (var entity in entities) Entities.Add(entity); _context.SaveChanges(); } |
Oluşan SqlQuery aşağıdaki gibidir: Görüldüğü üzere, her bir kayıt tek tek insert edilmiştir.
3 Kayıt Sql Query’si, Tek Tek Insert :
Ama aynı kodların, kayıt sayısı üçten büyük olacak şekilde (kayıt sayısı > 3 yani en az 4) tekrar çalıştırılması durumunda, insert işleminin tek tek değil bulk insert şeklinde yapıldığı görülür. Bunun nedeni, tamamen performanstır. Belli bir sayının altında, bulk insert çok maliyetli bir işlemdir. Ancak 4 ve üzeri sayıda kayıt için avvantajlı bir hal almaktadır.
CustomerController / InsertmultipleUser(): Aşağıda görüldüğü gibi 4 kayıt, tek seferde kaydedilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[HttpGet("InsertMultiUser")] public ServiceResponse<UserModel> InsertMultiUser() { var response = new ServiceResponse<UserModel>(HttpContext); List<UserModel> models = new List<UserModel>() { new UserModel(){Name="test1",LastName="lasttest1",UserName="ltest1",PasswordHash="Kol1K0t",Email="ltest1@mail",Gsm="1111"}, new UserModel(){Name="test2",LastName="lasttest2",UserName="ltest2",PasswordHash="Kol2K0t",Email="ltest2@mail",Gsm="2222"}, new UserModel(){Name="test3",LastName="lasttest3",UserName="ltest3",PasswordHash="Kol3K0t",Email="ltest3@mail",Gsm="3333"}, new UserModel(){Name="test4",LastName="lasttest4",UserName="ltest3",PasswordHash="Kol4K0t",Email="ltest4@mail",Gsm="4444"} }; response= _userService.InsertMultiUsers(models); response.IsSuccessful = true; response.Count = response.List.Count; return response; } |
Eğer EntityFrameWork’de kaydedilen kayıt sayısı, 3’den büyük ise bulk insert, 3 ve 3’den küçük ise tek tek insert edilir.
4 Kayıdın Insert SqlQuery’si, Bulk Insert’dür :
Senaryo 5:
EmployeesController/UpdateEmployee() : employeID’si belli bir kayıdın güncellenmesi için, employeeService üzerindeki Update() methodu çağrılır.
1 2 3 4 5 6 7 8 9 10 11 12 |
private readonly IEmployeesService _employeesService; public EmployeesController(IEmployeesService employeesService) { _employeesService = employeesService; } [HttpPost("UpdateOneEmployee")] public ServiceResponse<EmployeesModel> UpdateEmployee(int employeeID) { var response = _employeesService.Update(employeeID); return response; } |
GeneralRepository: Repository’de tanımlıEntities ve TableNoTracking propertyleri aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class GeneralRepository<T> : IRepository<T> where T : BaseEntity { . . private readonly CustomerContext _context; private DbSet<T> _entities; public GeneralRepository(CustomerContext context) { _context = context; _entities = context.Set<T>(); } protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>()); public virtual IQueryable<T> TableNoTracking => Entities.AsNoTracking(); } |
EmployeeService/Update : “TableNoTracing” yani AsNoTracking() olarak çekilen employee datasında, yapılan güncelleme durumunda değişen kolonlar, entity tarafından belirlenememektedir. Yani, değişen sadece “HireDate” kolonu olmasına rağmen, tüm kolonlar güncellenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private readonly IRepository<DB.Entities.Employees> _employeesRepository; private readonly IMapper _mapper; public EmployeesService(IRepository<DB.Entities.Employees> employeesRepository,IMapper mapper) { _employeesRepository = employeesRepository; _mapper=mapper; } public ServiceResponse<EmployeesModel> Update(int employeeId) { var response = new ServiceResponse<EmployeesModel>(null); var employee = _employeesRepository.TableNoTracking.FirstOrDefault(e => e.EmployeeId == employeeId); employee.HireDate = new DateTime(1978, 06, 03); _employeesRepository.UpdateAsNoTrackingEmployee(employee); response.Entity = _mapper.Map<EmployeesModel>(employee); return response; } |
Repository/GeneralRepository: Aşağıda görüldüğü gibi, Employee tablosunda değişen kolon, datanın “AsNoTracking()” şeklinde çekilmesinden dolayı belirlenememiş ve tüm kolonlar güncellenmiştir. Ayrıca yine “AsNoTracking()” şeklinde datanın çekilmesinden dolayı, var olan dependency injection ile oluşturulan “_context“, kullanılamamış, ilgili Employee’i update etmek için, yeni bir Context yaratılmıştır.
1 2 3 4 5 6 7 8 9 10 |
public void UpdateAsNoTrackingEmployee(Employees entity) { if (entity == null) throw new ArgumentNullException(nameof(entity)); using (var newContext = new CustomerContext()) { newContext.Employees.Update(entity); newContext.SaveChanges(); } } |
EmployeeService/Update (2): Employee datasın Table ile çekilmesi durumunda, yani Track edilerek(izlenerek) durumunda, değişen kolon (HireDate) Entity tarafından tespit edilmiş ve sadece ilgili kolon güncellenmiştir.
1 2 3 4 5 6 7 8 9 10 |
public ServiceResponse<EmployeesModel> Update(int employeeId) { var response = new ServiceResponse<EmployeesModel>(null); //var employee = _employeesRepository.TableNoTracking.FirstOrDefault(e => e.EmployeeId == employeeId); var employee = _employeesRepository.Table.FirstOrDefault(e => e.EmployeeId == employeeId); employee.HireDate = new DateTime(1978, 06, 03); _employeesRepository.UpdateAsNoTrackingEmployee(employee); response.Entity = _mapper.Map<EmployeesModel>(employee); return response; } |
Repository/GeneralRepository(2): Track edilerek çekilen data için, ayrıca yeni bir context yaratılmamış, dependency injection ile ayağa kaldırılan “_context” kullanılarak, employee kaydı güncellenmiştir. Entity tarafından oluşturulan, aşağıdaki sql query’de de görüldüğü gibi, sadece değişen HireDate kolonu güncellenmiştir.
1 2 3 4 5 6 |
public void UpdateAsNoTrackingEmployee(Employees entity) { if (entity == null) throw new ArgumentNullException(nameof(entity)); _context.SaveChanges(); } |
Senaryo 6:
İş hayatında hepimizin ihtiyacı olan, Entity Framework ile belirlenen bir entity üzerinde, Global Filter’a ne dersiniz?
Amaç: Select çekilen bir entityde, silinmiş olan, yani “IsDeleted==false” olan dataların çekilmemesini yani filitrelenmesini, global olarak sağlamak.
CustomerContext: Öncelikle aşağıda görüldüğü gibi “CustomerContext” ==>”OnModelCreating()” methoduna, custom yazılan “modelBuilder.AddGlobalFilter()” eklenir. Tüm Entity sorgularında ilgili filtre devreye girecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class CustomerContext : NorthwindContext { public CustomerContext() { } public CustomerContext(DbContextOptions<NorthwindContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.AddGlobalFilter(); } } |
DB/ModernBuilderExtensions:
- “AddGlobalFilter” içerisinde parametre olarak gönderilen moldebuilder içindeki tüm EntityTypelar, teker teker gezilir.
- “typeof(ISoftDeletable).IsAssignableFrom(type.ClrType)”: Eğer entity içindeki tanımlı tipler arasında, makalenin devamında tanımlayacağımız “ISoftDeletable” interface’i var ise, SetSoftDeleteFilter() methodu çağrılır.
- “SetSoftDeleteFilter” methodunda “SetSoftDeleteFilterMethod” methodu, entitytype ile birlikte trigger edilir.
- SetSoftDeleteFilter<TEntity>() : Methodunda ==> “modelBuilder.Entity<TEntity>().HasQueryFilter(x => !x.IsDeleted)” silinmemiş kayıtlar çekilir.
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 |
public static class ModelBuilderExtensions { public static void AddGlobalFilter(this ModelBuilder modelBuilder) { foreach (var type in modelBuilder.Model.GetEntityTypes()) { if (typeof(ISoftDeletable).IsAssignableFrom(type.ClrType)) modelBuilder.SetSoftDeleteFilter(type.ClrType); } } public static void SetSoftDeleteFilter(this ModelBuilder modelBuilder, Type entityType) { SetSoftDeleteFilterMethod.MakeGenericMethod(entityType) .Invoke(null, new object[] { modelBuilder }); } static readonly MethodInfo SetSoftDeleteFilterMethod = typeof(ModelBuilderExtensions) .GetMethods(BindingFlags.Public | BindingFlags.Static) .Single(t => t.IsGenericMethod && t.Name == "SetSoftDeleteFilter"); public static void SetSoftDeleteFilter<TEntity>(this ModelBuilder modelBuilder) where TEntity : class, ISoftDeletable { modelBuilder.Entity<TEntity>().HasQueryFilter(x => !x.IsDeleted); } } |
ISoftDeletable: Global filtre eklenecek Entity bu interfaceden türetilir.
1 2 3 4 5 6 7 8 9 10 11 |
using System; using System.Collections.Generic; using System.Text; namespace DB.PartialEntites { public interface ISoftDeletable { bool IsDeleted { get; set; } } } |
PartialEntites: Sonuç olarak aşağıda görüldüğü gibi “ISoftDeletable” interface’inden türeyen entitylerde, silinen yani IsDeleted‘i true olan hiçbir bir kayıt gelmeyecektir. Bu da bize, kodlama anında zaman kazandıracak, gereksiz yere sorgu sonlarına “IsDeleted==false” konulmasını engelleyecektir.
1 2 3 |
class PartialEntites{} public partial class Categories : BaseEntity, ISoftDeletable { } public partial class Customers : BaseEntity, ISoftDeletable { } |
Geldik bir makalenin daha sonuna. Bu makalede .Net Core 3.1.101 ile Entity Framework üzerine pek de bilinmeyen özellikleri, derinlemesine incelemeye çalıştık. Aslında .Net Core Entity Framework üzerinde daha bizi bekleyen birçok yenilik bulunmakta ve halen de gelmeye devam etmektedir. Burada bize düşen en büyük görev, işlerimizi kolaylaştıracak yeni yöntemleri mümkün olduğunca takip etmek ve kendimizi her zaman güncel kılmaktır.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source :
Hocam merhaba,
Ben bir konu hakkında bilginiz var ise başvurmak istiyorum. İki tablom olduğunu düşünün biri user biri user detay diye, user tablosuna yeni bir kayıt eklendiğinde userdetay tablosuna o userid ile otomatik kayıt nasıl eklerim. Bunu context nesenesinde nasıl yaparım yada yapabilirmiyim bilemedim. Neyi aramam gerekiyor onuda bilmiyorum :) konu hakkında bir bilginiz mevcut ise bilgilendirmenizi rica ederim.
ilk once
context.User.Add(newUser)
daha sonra gelen userdetay bilgilerini
userId=newUser.ID
olarak goster daha sonra
context.savechanges()
Merhaba , User ve Userdetail bir ilişki içerisinde ise ve master tablo user ise , userın içerisinde userdetail tanımlanır ve doldurulur , daha sonra user ı kaydettiğinizde hali hazırda userdetail de kaydolmuş olur. Aralarında bir ilişki yoksa ve kurulması doğru seçenek olacaktır.
Örneğin
public class User : BaseEntity
{
public string UserName { get; set; }
public string Name { get; set; }
public string SurName { get; set; }
public string Email { get; set; }
public virtual UserDetail UserDetail { get; set; }
}
public UserDetail User : BaseEntity
{
public long UserId { get; set; }
public string MobilePhone { get; set; }
}
user usr = new user();
// User doldur
userdetail usrdtl = new userdetail();
// detay doldur.
usr.Userdetail =usrdtl;
save.changes();
şeklinde yapılabilir.
Diyelim ki ilişkisi yok ve otomatik bir kayıt atılacak sadece , Bora hocamım save.change metodunu ezdiği gibi entityleri gezip eğer entity user ise orada yakalayıp kayıttan sonra detail için sizde bir kayıt atabilirsiniz.
Global Filter için benim başka bir önerim var.
Aşağıdaki kod bloğu Entity’deki IsDeleted property’sine göre işlem yapıyor. Böylece eğer boolean tipinde IsDeleted property’si varsa, global query filter’a ekliyor.
private void SetEFGlobalQueryFilters(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var isDeletedProperty = entityType.FindProperty(nameof(BaseTable.IsDeleted));
if (isDeletedProperty != null && isDeletedProperty.ClrType == typeof(bool))
{
var parameter = Expression.Parameter(entityType.ClrType, “p”);
var _memberExpression = Expression.Property(parameter, isDeletedProperty.PropertyInfo);
var _matchExpression = Expression.Equal(_memberExpression, Expression.Constant(false));
var filter = Expression.Lambda(_matchExpression, parameter);
MutableEntityTypeExtensions.SetQueryFilter(entityType, filter);
}
}
}
Bora hocam eline, emeğine sağlık. Sayende çok şeyler öğrendim. Aklıma takılan ve bir türlü doğru cevabı bulamadığım bir sorum var. (Belki de ben doğru araştıramadım.)
Database modelleri tanımlarken public virtual kullanımını neye göre yapıyoruz? Yani public virtual Employee ile public Employee tanımlamaları arasındaki fark nedir? Benim mantığım, iki modelden de birbirlerine gitmek istiyorsak, yani Employee üzerinden Order’lara, Order üzerinden Employee’lere o zaman virtual tanımlamamız gerekiyor. Sadece employee üzerinden orderlara ulaşmak yetiyorsa o zaman public Order Siparisler {get;set;} işimizi görüyor diye düşünüyorum. Doğru mudur acaba? Bir de bunları virtual ya da sadece model olarak tanımlamanın performans üzerinde etkisi var mıdır?
Çok teşekkürler
Hocam bir sorum olacak. Bir veri tabanında farklı dönemlerde kullanılmak ama aynı yapıda tablolarım var. Örneğin; 01_item, 02_item, 03_item bu tabloların her biri için entity class açamayacağıma göre çalışmak istediğim tabloyu nasıl seçebilirim. 3 gün boyunca saçımı yoluyorum…
Selamlar,
Dinamik table .Net Core Entity’de ancak dynamic query ile olabilir. Yani text SqlQuery yazıp from kısmını dinamik table ismi yapıcan. Ya da dapper’a geçicen…
İyi çalışmalar.