Keri sisuni

Entity Framework Core

Käesoleva aine raames kasutame me ORM-ina Microsofti ehitatud komponenti nimega Entity Framework Core (arendajate keeles EF Core). Entity Framework on tüüpiline ORM. Eelisteks võib lugeda selle, et tegemist on paindliku, küllaltki pisikese ja arendajatele vähe nõudmisi esitava komponendiga. Lisaks on see pidevas arenduses Microsofti poolt ning sellel on palju kasutajaid üle maailma.

EF Core abil saame me kirjutada päringuid kasutades klasse, mille oleme defineerinud. Päringute kirjutamiseks on kasutusel LINQ. Siin on üks päring, mida vaatasime tunnis.

var products = await _dataContext.Products
                                 .Include(p => p.Manufacturer)
                                 .OrderBy(p => p.Manufacturer.Name)
                                 .ThenBy(p => p.Name)
                                 .Select(p => new ProductListModel { 
                                    Id = p.Id,
                                    ProductName = p.Name,
                                    ManufacturerName = p.Manufacturer.Name
                                  })
                                  .ToListAsync();

Selle päringu juurde tuleme hiljem tagasi.

Lisaks sellele saame EF Core abil luua andmebaasi alates nullist koos tabelite ja seostega ning muuta andmebaasi struktuuri versioonitavaks. On muidki häis asju, kuid nendega tegeleme sügise jooksul jooksvalt edasi.

Andmekonteksti klass DbContext

EF Core korral algab andmebaasiga liidestus sellest, et loome oma andmekonteksti klassi. Selle klassi abil pääseme juurde andmetele, saame neid muuta ning teha päringuid. Kogu maagia juhtub seepärast, et meie andmekonteksti klassi baasklassiks on EF Core poolt pakutav DbContext klass.

Tunnis vaatasime lihtsat andmebaasi, kus oli toodete ja tootjate tabelid. Sellele vastav andmekonteksti klass ApplicationDbContext oli selline:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Manufacturer> Manufacturers { get; set; }
}

Klassid, mille andmeid soovime andmebaasis hoida, defineerime oma andmekonteksti klassis DbSet tüüpi omadustena. Selle järgi saab DbContext baasklass aru, et need asjad peavad jõudma andmebaasi.

Andmekonteksti tutvustamine ASP.NET Core rakendusele

Selleks, et saaksime andmekonteksti klassi kasutada läbi dependency injectioni peame me selle klassi rakenduse käivitumisel veebirakendusele ette ütlema. Seda teeme Startup klassi ConfigureServices() meetodis.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseSqlite("Data source=mydb.db");
    });

    services.AddControllersWithViews();
}
AddDbContext<>() on see meetod, mis meie andmekonteksti ära seadistab ja ASP.NET Core rakenduse teenuste hulka lisab. UseSqlite() meetod ütleb, et meie andmekontekst kasutab SQLite andmebaasi ning see andmebaas asub failid nimega mydb.db. Stringi "Data source=mydb.db" nimetatakse ühenduse stringiks (ametlik termin on connection string). Tegemist on stringiga, mis sisaldab andmebaasiga ühendumiseks kõiki vajalikke parameetreid. Antud juhul on parameetreid küll üks, kuid päris elu on ühenduste stringid oluliselt pikemad.

Andmebaasi automaatne loomine

Kui andmebaasi struktuur muutub või kui on vaja andmebaas uuesti luua, siis mugavuse huvides laseme sellel juhtuda automaatselt. Kui andmebaas uuesti loodi, siis lisame sinna ka automaatselt katseandmed sisse. See näide kasutab eriti primitiivset nõksu, mida praktikas teha ei tohiks.

Startup klassi Configure() meetod lõpus kutsume meetodi EnsureDatabase(). See meetod loob meile andmebaasi kui seda pole ja lisab sinna testandmed sisse. Näiteks:

private void EnsureDatabase(IApplicationBuilder app)
{
    var serviceScopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
    using (var serviceScope = serviceScopeFactory.CreateScope())
    {
        var dbContext = serviceScope.ServiceProvider.GetService<ApplicationDbContext>();
        dbContext.Database.EnsureCreated();

        if (dbContext.Products.Count() == 0)
        {
            var testManufacturer = new Manufacturer { Name = "Nissan" };
            dbContext.Manufacturers.Add(testManufacturer);                    

            var testProduct = new Product { Name = "Qashqai", Manufacturer = testManufacturer };
            dbContext.Products.Add(testProduct);

            testProduct = new Product { Name = "Leaf", Manufacturer = testManufacturer };
            dbContext.Products.Add(testProduct);

            testProduct = new Product { Name = "Navara", Manufacturer = testManufacturer };
            dbContext.Products.Add(testProduct);

            testManufacturer = new Manufacturer { Name = "Mercedes" };
            dbContext.Manufacturers.Add(testManufacturer);

            testProduct = new Product { Name = "Maybach", Manufacturer = testManufacturer };
            dbContext.Products.Add(testProduct);

            testProduct = new Product { Name = "SL Roadster", Manufacturer = testManufacturer };
            dbContext.Products.Add(testProduct);

            dbContext.SaveChanges();
        }
    }
}

EnsureDatabase() meetod küsib ASP.NET Core käest meie andmekonteksti klassi ning laseb sellel luua andmebaasi kui andmebaasi pole. Järgmiseks kontrollib see toodete arvu toodete tabelis. Kui toodete arv on null, siis lisatakse andmebaasi ports testandmeid ning salvestatakse need.

Andmekonteksti kasutamine kontrollerites

Kontrolleritesse toome oma andmekonteksti dependency injection abil.

public class ManufacturersController : Controller
{
    private readonly ApplicationDbContext _context;

    public ManufacturersController(ApplicationDbContext context)
    {
        _context = context;
    }

    // ...
}

Selliselt on andmekontekst meil läbi _context atribuudi kättesaadav läbi kogu klassi.

Näiteid päringutest ja andmetega seotud toimingutest

Mõned näited EF Core päringutest, mida ka tunnis vaatasime.

Tagasta loend kõikidest tootjatest:

var manufacturers = await _context.Manufacturers.ToListAsync();

Tagasta tootja, mille ID on andmebaasis 12:

var manufacturer = await _context.Manufacturers.FirstOrDefaultAsync(m => m.Id == 12);

Tootja eemaldamine andmebaasist:

var manufacturer = await _context.Manufacturers.FindAsync(id);
_context.Manufacturers.Remove(manufacturer);
await _context.SaveChangesAsync();

Muuda nime tootjal, mille ID on 10:

var manufacturer = await _context.Manufacturers.FirstOrDefaultAsync(m => m.Id == 10);
manufacturer.Name = "The Very Big Corporation of America";
_context.Update(manufacturer);
await _context.SaveChangesAsync();

Uue tootja lisamine andmebaasi:

var manufacturer = new Manufacturer { Name = "Peetri Pizza" };
_context.Add(manufacturer);
await _context.SaveChangesAsync();