.Net Core 3 Üzerinde AutoMapper ve Entity Framework Araçları
Selamlar bu makalede, bana gelen sorulardan, .Net Core üzerinde bir Entity’i kaydetmek için, tüm kolonlarını tek tek set mi etmeliyiz sorusuna cevap arayacağız.
Öncelikle aşağıdaki tablolar sqlDB’de oluşturulur.
Sıra geldi .Net Corer bir WebApi proje oluşturmaya:
1 |
dotnet new webapi -o automapperblog |
Şimidi sıra geli DBContext’i DB First şeklinde oluşturmaya. Makinamda kurulu .Net Core 3.0 Versiyonu aşağıdaki gibidir.
Makinanızda dotnet ef tool’un kurulu olup olmadığı aşağıdaki komut ile kontrol edilir.
1 |
dotnet ef -h |
Eğer makinanızda “ef tools” yok ise, aşağıdaki komut ile “dotnet ef” tools global olarak yüklenir. ef tools’un amacı, Database First mantığında DB’den ilgili DB Context’in ve entity sınıflarının oluşturulmasını sağlamaktır.
1 |
dotnet tool install --global dotnet-ef --version 3.0.0-preview.19074.3 |
EntityFrameworkCore’a ait SqlServer, SqlServer.Design ve Tools kütüphaneleri aşağıdaki gibi eklenir.
1 2 3 |
dotnet add package Microsoft.EntityFrameworkCore.SqlServer.Design; dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools |
Sıra geldi aşağıdaki komut ile, Automapper projesinde Models/DB altında DBContext’in oluşturulmasına. Alttaki komut ile gösterilen DB’ye ait Db Context ve Entityler “Models/DB” klasörünün altında oluşturulur.
1 |
dotnet ef dbcontext scaffold "Server=.;Database=Quiz; Trusted_Connection=True" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models/DB |
Program.cs altına, Kesterl custom port’un değiştirilmesi için aşağıdaki satır eklenir.
Projeye Dependency Injection ile QuizContext aşağıdaki gibi eklenir. Amaç, proje ayağa kalkerken ilgili context’in bir kere oluşturulmasını sağlamak ve ihtiyaç anında tekrar tekrar oluşturulması yerine constructorda alınarak kullanılmasının sağlamaktır.
Startup.cs/ConfigurationServices():
1 2 3 4 5 6 |
public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddNewtonsoftJson(); services.AddDbContext<QuizContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), opt => opt.UseRowNumberForPaging())); } |
appsettings.json‘a aşağıdaki “DefaultConnection” tanımlanır. Bu Sql DB erişim için gerekli olan connection stringdir.
1 2 3 |
"ConnectionStrings": { "DefaultConnection": "Server=.;Initial Catalog=Quiz; Trusted_Connection=True;" }, |
Aşağıdaki gibi bir sorgu çekildiğinde, Question model’ine ait 4 kolon gelmektedir. Bu örnekte view model olarak, sadece “id” ve “text” alanları kullanılmak istenmiştir.
Not: Kısaca 100 kolonu olan bir entity’nin View’da sadece 5 kolonuna ihtiyacımız var ise ayrıca bir viewModel oluşturulmalıdır.
QuizController.cs : Aşağıda, örnek amaçlı istenen bir Id’ye göre kayıt çeken bir servis yazılmıştır. İlgili QuizContext constructor’da alınmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class QuizController : ControllerBase { private QuizContext _context; public QuizController(QuizContext context) { _context = context; } [HttpGet("{id}")] public ActionResult<Question> Get(int id) { return _context.Question.Where(q => q.Id == id).FirstOrDefault(); } } |
İlgili servisden dönen sonuç, aşağıdaki gibidir.
Model/IViewQuestion.cs: View’da görünmesi istenen kolonlar için, aşağıdaki ViewQuestion model oluşturulur.
1 2 3 4 5 6 7 8 |
namespace AutoMapperBlog.Models { public interface IViewQuestion { int Id { get; set; } string Text { get; set; } } } |
Model/ViewQuestion.cs:
1 2 3 4 5 6 7 8 9 |
namespace AutoMapperBlog.Models { public class ViewQuestion : IViewQuestion { public int Id { get; set; } public string Text { get; set; } public string CustomName { get; set; } } } |
ViewQuestion modelinin services’de dependency injection ile kullanılabilmesi için, ConfigureService’e aşağıdaki gibi singleton eklenmesi gerekmektedir.
Startup.cs/ConfigurationServices():
1 2 3 4 5 6 7 |
public void ConfigureServices(IServiceCollection services) { . . . services.AddSingleton<IViewQuestion, ViewQuestion>(); } |
Şimdi sıra AutoMapper’ı projeye eklemeye geldi.
AutoMapper’ın amacı : View’dan Entity’e veya Entity’den View’a gelirken, eşitlenme anında ayarlanan kolonların belirtilen konfigürasyonlar ile otomatik olarak atanması sağlamaktır.
1 |
dotnet add package AutoMapper --version 9.0.0 |
1 |
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection --version 7.0.0 |
Görümesi istenen Viewmodel’i (ViewQuestion)’ı ve data çekilen model’i (Question)’ı tanımladık.
Şimdi sırada AutoMapper’ı tanımlamaya geldi.
QuizMapper.cs: Bu kısım, View’da görünmesi istenen kolonların, Db’den gelen data ile ya da tam tersi şekilde eşleştirildiği kısımdır.
- CreateMap<ViewQuestion, Question>() : ViewModel’den Entity’ye doğru olan bir eşleşme.
- CreateMap<Question, ViewQuestion>() : DB’den çekilen datanın ViewModel’e doldurulması.
- .ForMember(q => q.CustomName, opt => opt.MapFrom(s => “Category:” + s.CategoryId + “- Id:” + s.Id)) : ViewModel’de bulunu “CustomName“‘in DB’deki Question tablosunda karşılığı yoktur. Bu neden ile CategoryID ve Id, alanları birleştirilerek özel bir kolon oluşturulmuştur. Kısaca Mapleme işlemi sırasında, araya girilmiştir.
- ForMember(q => q.Id, opt => opt.Ignore()) : DB’den gelen “Id” alanının, ViewModel’a atanması engellenmiştir. “Ignore()“. View’da Id alanı, maplenmediği için 0 gözükecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using AutoMapper; using AutoMapperBlog.Models; using AutoMapperBlog.Models.DB; public class QuizMapper : Profile { public QuizMapper() { CreateMap<ViewQuestion, Question>(); CreateMap<Question, ViewQuestion>() .ForMember(q => q.CustomName, opt => opt.MapFrom(s => "Category:" + s.CategoryId + "- Id:" + s.Id)) .ForMember(q => q.Id, opt => opt.Ignore()); } } |
Sıra geldi AutoMapper’ın .Net Core tarafında tanımlanmasına.
Startup.cs / ConfigureServices() : Aşağıda görüldüğü gibi yukarıda tanımlanan QuizMapper, IMapper interface’inden türeyen mapper nesnesinin oluşturulmasında, config olarak kullanılmıştır. Ve .Net Core projeye mapper nesnesi singleton olarak eklenerek, automapper tanımlaması yapılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 |
public void ConfigureServices(IServiceCollection services) { . . . var mappingConfig = new MapperConfiguration(mc => { mc.AddProfile(new QuizMapper()); }); IMapper mapper = mappingConfig.CreateMapper(); services.AddSingleton(mapper); } |
Kullanım Şekli : QuizController sınıfının Constructor’ında “IMapper mapper” Dependency Injection ile alınır. İlgili mapleme işi, “_mapper.Map<ViewQuestion>(data)” bu satır ile yapılmaktadır. DB’den gelen data, ViewModel için QuizMapper’da yapılan tanımlamalar ile istenen yapıya dönüştü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 |
using System.Linq; using AutoMapper; using AutoMapperBlog.Models; using AutoMapperBlog.Models.DB; using Microsoft.AspNetCore.Mvc; namespace AutoMapperBlog.Controllers { [Route("api/[controller]")] [ApiController] public class QuizController : ControllerBase { private QuizContext _context; private readonly IMapper _mapper; public QuizController(QuizContext context, IMapper mapper) { _context = context; _mapper = mapper; } [HttpGet("{id}")] public ActionResult<ViewQuestion> Get(int id) { var data=_context.Question.Where(q => q.Id == id).FirstOrDefault();; var result = _mapper.Map<ViewQuestion>(data); return result; } } } |
Query’nin son görünüm hali, aşağıdaki gibidir : Aşağıda görüldüğü gibi id alanı Ignore edildiği için “0” gelmiştir. Ayrıca olmayan “customName” alanı, automapper sayesinde doldurulmuştur.
Update : Aşağıda bir güncelleme işleminde çalışacak Action() method tanımlanmıştır. Burada özel bir durum vardır. ViewModel, DataModel’den farklıdır. Bu durumda “CategortyId” ve “IsActive” kolonları viewmodel’den gelemiyeceği için, önce var olan datadan çekilmiş ve güncelleme anında tekrar atanmıştır. Normal şartlar altında, eksik kolonların olduğu Custom Viewlarda, update işlemi mümkün olduğunca tercih edilmemelidir.
Not: View ve Data modellerin eşleştiği durumlarda, bu örnekte Update anında Automapper ile gelen view modelin ilgili entity’ye maplenmesi yeterli olacaktır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// POST api/values [HttpPost] public void Post([FromBody] ViewQuestion model) { Question desData=_context.Question.AsNoTracking().Where(q=>q.Id == model.Id).FirstOrDefault(); Question data = _mapper.Map<Question>(model); data.CategoryId=desData.CategoryId; data.IsActive=desData.IsActive; _context.Update(data); _context.SaveChanges(); } |
Postman: Postman ile Update() Action’a gönderilen ViewModel örneği, son olarak aşağıdaki gibidir.
1 2 3 4 5 |
{ "id": 3, "text": "Özel Nesnelerin inşasında ve başlatılmasında kullanılan Python'un 3 sihirli yöntemini isimlendirin ve açıklayın.", "customName": "Category:2- Id:3" } |
Geldik bir makalenin daha sonuna. Bu makalede .Net Core bir projede, Automapper kullanılarak kayıt çekme ve kaydetme sırasında nasıl araya girilinebileceği ve Entity’e bir kayıt atmak için tek tek kolonların eşitlenmesinden nasıl kaçınılabileceği gösterilmiştir. Ayrıca mapleme işleminde araya girilerek, custom yeni bir kolon oluşturulmuş hatta bir başka kolon da tamamı ile ihmal edilmiştir. Kolon sayısı 50 ve üzeri olan Entitylerde, Crud işlemleri sırasında çokça zaman kazandıran Automapper’ı, şiddet ile tavsiye ederim. Ayrıca CustomViewların olduğu sayfalarda, Automapper ile Update işleminden uzak durmanızı da öneririm. Aksi takdirde bu örnekte olduğu gibi, araya girip eksik veya farklı kolonlar için ayrıca birtakım işlemler yapmak zorunda kalabilirsiniz.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source :
- https://github.com/AutoMapper/AutoMapper
- https://stackoverflow.com/questions/40275195/how-to-set-up-automapper-in-asp-net-core
- https://dotnettutorials.net/lesson/ignore-using-automapper-in-csharp/
Teşekkürler
Ben teşekkür ederim.
yalın güzel anlatmışsınız teşekkürler
Ben Teşekkür ederim.
Makalelerinizde subminal mesajlar saklı, Başarılarınızın devamını dilerim :)
Teşekkürler :)
Bora Hocam ellerine sağlık, sayende daha da ileriye..
Teşekkür ederim Serhat.Her zaman!