Entity Framework

אחרי שעשינו מימוש עם Minimal Api אנחנו רוצים לראות צורת מימוש נוספת.

Entity Framework היא טכנולוגיה שעוזרת לנו לחבר את ה-DB לאובייקטים של net. בצורה אינטואיטיבית. היא עוזרת לחבר בין שכבת המידע לביזנס logic.

אנחנו רוצים מנגנון שיודע לשמור רשימות של items, ו-Entity Framework הוא אחד המנגנונים שיודע לעשות את זה. הדרך שבה הוא שומר את המידע יכולה להיות מותאמת ולשמור את המידע בכמה סוגים של DB ויש אפשרות לעשות שימוש בזיכרון.

כדי לעבוד עם Entity Framework צריך להתקין את Entity Framework Core מתוך nuget.

יצירת קובץ Context

נייצר תיקייה בשם Infra שבה נשים דברים תשתיתיים יותר, בתוכה נייצר תיקייה של ApplicationDbContext שבתוכה נשמור את הדברים. נייצר class בשם CourseDbContext. ב-class שיצרנו נשתמש ב-DbContext.

נגדיר constructor שמקבל DbContextOptions שלנו ואנחנו מעבירים אותו ל-base.

קובץ CourseDbContext.cs

public class CourseDbContext : DbContext
{
    public CourseDbContext(DbContextOptions<CourseDbContext> contextOptions) : 
        base(contextOptions) {

    }
}

הגדרנו מעין DB וירטוואלי שנמצא בזיכרון והוא במבנה של אובייקטים ו-collection.

נגדיר DbSet מהסוג שאנחנו צריכים.

public class CourseDbContext : DbContext
{
    public DbSet<Product> products { get; set; }
    public CourseDbContext(DbContextOptions<CourseDbContext> contextOptions) : 
        base(contextOptions) {

    }
}

עכשיו נלך ל-ProductsRepository ושם נשנה את ה-implementation מ-list שנמצא שם שיעשה שימוש ב-context שיצרנו הרגע.

יצירת מבנה הנתונים

נסיר את הנתונים הראשוניים שיש לנו.

זה מה שהיה לנו בקובץ עד עכשיו:

קובץ ProductsRepository.cs

public class ProductsRepository : IProductsRepository
{
    private List<Product> _products = new List<Product>();
    public ProductsRepository() {
        _products.Add(new Product 
        {
                id = 1,
                name = "ASUS Computer",
                description = "Best computer",
                price = 2400,
                amount = 4,
                producer = "Asus"
        });
    }
   ...
}

ניצור משתנה DbContext ונשתמש בו.

public class ProductsRepository : IProductsRepository
{
    private readonly CourseDbContext courseDbContext;
    public ProductsRepository(CourseDbContext courseDbContext) {
        this.courseDbContext = courseDbContext;
    }
   ...
}

נתחיל לעבוד על הפונקציה הכי פשוטה שמביאה את כל המוצרים מהמאגר. נשתמש בפונקציה ToListAsync, כי תמיד נשתמש ב-async בפנייה לפעולה חיצונית כלשהי.

public async Task<List<Product>> GetAllProducts() {
    var products = await courseDbContext.products.ToListAsync();
    return products;
}

נמשיך לסדר את שאר הפעולות. בפעולת ה-delete ה-create וה-update נצטרך לבצע שמירה מול מאגר הנתונים אחרי הפעולה. בפעולת היצירה, המאגר כבר יודע ליצור לבד את ה-id דנמית. בפעולת הכדכון נצטרך לבדוק שהמוצר קיים.

זה הקוד אחרי השינויים.

קובץ ProductsRepository.cs

public class ProductsRepository : IProductsRepository
{
    private readonly CourseDbContext _courseDbContext;
    public ProductsRepository(CourseDbContext courseDbContext) {
        this._courseDbContext = courseDbContext;
    }

    public async Task<Product> AddNewProduct(Product product) {
        _courseDbContext.products.Add(product);
        await _courseDbContext.SaveChangesAsync();
        return product;
    }

    public async Task DeleteProduct(int id) {
        var item = await _courseDbContext.products.FirstOrDefaultAsync(x => x.id == id);
        if(item != null) {
            _courseDbContext.products.Remove(item);
        }
        await _courseDbContext.SaveChangesAsync();
    }

    public async Task<List<Product>> GetAllProducts() {
        var products = await _courseDbContext.products.ToListAsync();
        return products;
    }

    public async Task<Product?> GetProductById(int id) {
        var item = await _courseDbContext.products.FirstOrDefaultAsync(x => x.id == id);
        if(item == null) {
            return null;
        }
        return item;
    }

    public async Task<Product?> UpdateProduct(int id, Product product) {
        //Product item = await courseDbContext.products.FirstAsync(x => x.id == id);
        _courseDbContext.products.Update(product);
        await _courseDbContext.SaveChangesAsync();

        return product;
    }
}

ה-DbContext מחזיק את הנתונים וקבלנו יישום קטן ופשוט יותר. כרגע זה לא יעבוד. מכיוון ש-DbContext מגיע ב-injection נצטרך לראות הגדרה שלו ב-program.

הגדרת המנגנון

יש לנו אפשרות להגדיר ב-program את DbContext. נצטרך לתת לו את ה-type שזה CourseDbContext ונצטרך לתת לו buillder עבור זה. ה-buillder מאפשר לקבל options שדרכם אנחנו יכולים להשתמש במנגנונים קיימים. למשל שמירה ב-SQL, או בזיכרון לצורך פיתוח ואפשר גם לכתוב מנגנונים לבד.

נשתמש ב-package שישמור לנו את המידע בזיכרון. נלך ל-nuget ונחפש entityframework inmemory. ואז נוכל לשלוח ב-option שאנחנו רוצים להשתמש בזה.

נצטרך לשנות גם את AddSingleton ל-repo להיות scoped.

קובץ program.cs

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<CourseDbContext>
    (options => options.UseInMemoryDatabase("CourseDb"));

builder.Services.AddCors(Action => Action.AddPolicy("aspnet-course",
    config => config.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));

// Resolve DI
builder.Services.AddScoped<IProductsRepository, ProductsRepository>();

var app = builder.Build();