Entity Framework Core - InMemory Veri Tabanı İle Çalışmak

Entity Framework Core'de eğer geliştirme aşamasında veri tabanınız yoksa ya da veri tabanında çalışmayıp test amaçlı çalışacaksak InMemory yapısını düşünebiliriz. Bu teknikte veriler, veri tabanında tutulmak yerine bellekte tutulur.

Konuyu örneklendirebilmek adına .NET 6 Web API projesi oluşturuyoruz. Önümüzdeki iki görselde proje oluşturma adımları mevcuttur. Framework için .NET 6 seçtiğimizden emin olmalıyız.



Projemiz oluştuktan sonra yeni bir API controller oluşturuyoruz. Önümüzdeki iki görselde ProductsController isminde bir controller oluşturuluyor.



Web API projemiz oluşturuldu. EntityFrameworkCore'de InMemory özelliğini kullanabilmek için Nuget Package Manager'den Microsoft.EntityFrameworkCore.InMemory paketini buluyoruz. 


Sonraki çıkan adımlarda OK butonuna tıklayarak Microsoft.EntityFrameworkCore paketlerini de geçişli paket (transitive packages alanında görünür) olarak yüklenmiş olacaktır.



Paketin yüklenmesi tamamlandıktan sonra projemize Product ve Category sınıfları ekliyoruz. Bu sınıflar, Models klasörü altındadır. InMemory olarak oluşturduğumuz veri tabanında DbContext sınıfımızda DbSet türünde tanımlanacaktır.

Product ve Category sınıfları
Product ve Category sınıfları

OnModelCreating metodunda kullanılmak üzere ModelBuilderExtensions sınıfı oluşturuldu. Bu sınıftaki Seed metodu, InMemory veri tabanımızdaki Category ve Product tablosuna eklenecek verileri içerir.  OnModelCreating metodundan Seed metodu çağrıldığında InMemory veri tabanına kayıt olarak ekler. Kullandığım veriler, Northwind veri tabanındaki Products ve Categories tablolarından size tanıdık gelebilir.

public static class ModelBuilderExtensions
{
public static void Seed(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, Name = "Beverages", Description = "Soft drinks, coffees, teas, beers, and ales" },
new Category { Id = 2, Name = "Condiments", Description = "Sweet and savory sauces, relishes, spreads, and seasonings" },
new Category { Id = 3, Name = "Confections", Description = "Desserts, candies, and sweet breads" }
);
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, CategoryId = 1, Name = "Chai", Price = 18, IsAvailable = true },
new Product { Id = 2, CategoryId = 1, Name = "Chang", Price = 19, IsAvailable = true },
new Product { Id = 24, CategoryId = 1, Name = "Guaraná Fantástica", Price = 4.5M, IsAvailable = true },
new Product { Id = 34, CategoryId = 1, Name = "Sasquatch Ale", Price = 14, IsAvailable = true },
new Product { Id = 35, CategoryId = 1, Name = "Steeleye Stout", Price = 18, IsAvailable = true },
new Product { Id = 38, CategoryId = 1, Name = "Côte de Blaye", Price = 263.5M, IsAvailable = true },
new Product { Id = 39, CategoryId = 1, Name = "Chartreuse verte", Price = 18, IsAvailable = true },
new Product { Id = 43, CategoryId = 1, Name = "Ipoh Coffee", Price = 46, IsAvailable = true },
new Product { Id = 70, CategoryId = 1, Name = "Outback Lager", Price = 15, IsAvailable = true },
new Product { Id = 67, CategoryId = 1, Name = "Laughing Lumberjack Lager", Price = 14, IsAvailable = true },
new Product { Id = 75, CategoryId = 1, Name = "Rhönbräu Klosterbier", Price = 7.75M, IsAvailable = false },
new Product { Id = 76, CategoryId = 1, Name = "Lakkalikööri", Price = 18, IsAvailable = true },
new Product { Id = 13, CategoryId = 2, Name = "Chef Anton's Cajun Seasoning", Price = 125, IsAvailable = true },
new Product { Id = 14, CategoryId = 2, Name = "Chef Anton's Gumbo Mix", Price = 55, IsAvailable = true },
new Product { Id = 15, CategoryId = 2, Name = "Grandma's Boysenberry Spread", Price = 22, IsAvailable = true },
new Product { Id = 16, CategoryId = 2, Name = "Northwoods Cranberry Sauce", Price = 95, IsAvailable = true },
new Product { Id = 17, CategoryId = 2, Name = "Genen Shouyu", Price = 17, IsAvailable = true },
new Product { Id = 18, CategoryId = 3, Name = "Teatime Chocolate Biscuits", Price = 2.8M, IsAvailable = true },
new Product { Id = 19, CategoryId = 3, Name = "Sir Rodney's Marmalade", Price = 2.8M, IsAvailable = true },
new Product { Id = 20, CategoryId = 3, Name = "NuNuCa Nuß-Nougat-Creme", Price = 2.8M, IsAvailable = true },
new Product { Id = 21, CategoryId = 3, Name = "Gumbär Gummibärchen", Price = 2.8M, IsAvailable = true },
new Product { Id = 22, CategoryId = 3, Name = "Schoggi Schokolade", Price = 2.8M, IsAvailable = true },
new Product { Id = 23, CategoryId = 3, Name = "Zaanse koeken", Price = 2.8M, IsAvailable = true },
new Product { Id = 80, CategoryId = 3, Name = "Chocolade", Price = 24.99M, IsAvailable = true },
new Product { Id = 25, CategoryId = 3, Name = "Valkoinen suklaa", Price = 9.99M, IsAvailable = true },
new Product { Id = 26, CategoryId = 3, Name = "Maxilaku", Price = 12.49M, IsAvailable = true },
new Product { Id = 27, CategoryId = 3, Name = "Tarte au sucre", Price = 13.99M, IsAvailable = true },
new Product { Id = 28, CategoryId = 3, Name = "Scottish Longbreads", Price = 12.49M, IsAvailable = true });
}
}
ECommerceDbContext adını verdiğimiz DbContext sınıfı, Microsoft.EntityFrameworkCore paketindeki DbContext sınıfından miras alır. 
override olarak kullandığımız OnModelCreating metodunda önce Product ve Category arasındaki ilişkiyi tanımladık. Oluşturulan ilişki, bir kategorinin birden çok ürünü olabilirken; bir ürünün yalnızca bir kategorisi olabilir. Sonra Seed metodunu çağırarak veri tabanına kayıt eklenmesi sağlandı.

Tablolarımızın ismi, isimlendirme kurallarına bağlı kalınarak Products ve Categories olarak verildi.

public class ECommerceDbContext : DbContext
{
public ECommerceDbContext(DbContextOptions<ECommerceDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.HasMany(c => c.Products)
.WithOne(a => a.Category)
.HasForeignKey(a => a.CategoryId);
modelBuilder.Seed();
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
ProductsController ile temel CRUD testlerimizi yapabiliriz. Metotlar aşağıdaki gibi oluşturuldu. 

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly ECommerceDbContext _context;
public ProductsController(ECommerceDbContext context)
{
_context = context;
_context.Database.EnsureCreated();
}
[HttpGet]
public async Task<ActionResult> GetAllProducts()
{
IQueryable<Product> products = _context.Products;
return Ok(await products.ToArrayAsync());
}
[HttpGet("{id}")]
public async Task<ActionResult> GetProduct(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
[HttpPost]
public async Task<ActionResult<Product>> PostProduct(Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return CreatedAtAction(
"GetProduct",
new { id = product.Id },
product);
}
[HttpPut("{id}")]
public async Task<ActionResult> PutProduct(int id, Product product)
{
if (id != product.Id)
{
return BadRequest();
}
_context.Entry(product).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Products.Any(p => p.Id == id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
[HttpDelete("{id}")]
public async Task<ActionResult<Product>> DeleteProduct(int id)
{
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return NotFound();
}
_context.Products.Remove(product);
await _context.SaveChangesAsync();
return product;
}
Dikkatinizi çekeceğim metot olan EnsureCreated, eğer veri tabanı bellekte yoksa, oluşturma işlemini yapar, varsa tekrar oluşturmaz. Kontrol mekanizması olarak düşünülebilir.

builder.Services.AddDbContext<ECommerceDbContext>(options =>
{
options.UseInMemoryDatabase("ECommerceDb");
});
view raw InMemory.cs hosted with ❤ by GitHub
Program.cs'de DbContext ayarını yapmamız gerekir. Kullanılacak metot, AddDbContext olup, bağımılılık, ECommerceDbContext olarak verildi. options tanımlamasında UseInMemoryDatabase metodunu veri tabanı ismi vererek tanımlarız.

Projedeki bütün dosyalar oluştuğunda temel katmanlar aşağıdaki görseldedir.

Projemizi ayağa kaldırdığımızda, swagger yapısı ile aşağıdaki gibi görüntü olacaktır. Bütün testler buradan yapılır. 


/api/Products/1 endpointiyle gönderdiğimiz id'ye ait kaydı bellekteki veri tabanından getirebildik.


Örnek projeye Github repo linkinden ulaşabilirsiniz. 

Bu yazımda Entity Framework Core'de bulunan InMemory özelliğine .NET 6 Web API projesi ile örnekleyerek değindim.

Keyifli ve faydalı olmasını umarak, çalışmalarınızda kolaylıklar diliyorum...

Yorumlar