Table of Contents
הקדמה: האתגר של Data Access Layer אבסטרציה ב .NET Core
יישומים מודרניים .NET Core לעתים קרובות אינטראקציה עם נתונים באמצעות מספר החזרי אחסון - מסדי נתונים יחסיים, NoSQL חנויות, כאבי ראש, או API של צד שלישי.כפי שהיישומים גדלים, הפיכה הדוקה בין לוגיקה עסקית ויישומים ספציפיים גישה נתונים הופך לנטל שמירה על נתונים מופשטים, שינוי מסד הנתונים הבסיסי או הוספת מנגנון אחסון חדש יכול לקרוע באמצעות קוד שלם, מה שהופך את ה-Dreperation ל-Rateexitation ל-Rateationeration.
הבנת תבנית המפעל: מעבר לאובייקט פשוט
בליבתו, תבנית המפעל היא דפוס עיצוב בריא המעביר את האחריות של אובייקטים מיידיים לכיתת מפעל ייעודית.תבנית זו נופלת על שלושה וריאציות משותפות: מפעל פשוט, שיטת המפעל, ומפעל אבסטרקטי.עבור שכבות גישה נתונים מופשטות, ה-FLT:0Simple FactoryFLT:1 (או מפעל סטטי) הוא לעתים קרובות נקודת ההתחלה הפרגמטית ביותר, אבל אנחנו גם לחקור כמה שיותר ויותר את המפעלים.
המניע העיקרי לשימוש במפעל ב-.NET Core הוא לשמור על ה-FLT:0Open/Closed PrincipleveFLT:1: שיעורים צריכים להיות פתוחים להרחבה אך סגורים לשינוי.על ידי תקשור את כל אובייקט הגישה לנתונים שנוצר באמצעות מפעל, באפשרותך להציג יישומים חדשים (למשל, מעבר ממסגרת תלותית לדאפר) ללא מגע עם הלוגיקה גבוהה, בנוסף לדרגה נמוכה (DLCF)
(FLT:0) דפוס המפעל אינו כדור כסף.זה יעיל ביותר כאשר יש לך קבוצה מוגדרת היטב של אסטרטגיות גישה נתונים משתנה צריך לבודד את ההיגיון הבריאה משאר היישום.
עיצוב הביטול: החוזה
הצעד הראשון בשימוש בתבנית המפעל לגישה לנתונים הוא להגדיר ממשק משותף שכל הרשומות הבטוניות חייבות ליישם.ממשק זה משמש כחוזה בין ההיגיון העסקי שלך לבין שכבת הנתונים.ב.NET Core, ממשק כזה לעתים קרובות מפות לפעולות CRUD סטנדרטיות, אבל אתה יכול להתאים אותו לצרכים שלך.
תגית: Generic Repository Interface
public interface IDataRepository<TKey, TEntity> where TEntity : class
{
Task<IEnumerable<TEntity>> GetAllAsync();
Task<TEntity?> GetByIdAsync(TKey id);
Task AddAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(TKey id);
}
ממשק גנרי זה עובד היטב כאשר אתה צריך פעולות נתונים עקביות על פני סוגים שונים של ישות.עם זאת, עבור פשטות במאמר זה, אנו נצמד עם ממשק לא-גנטי שפועל על סוג ישות אחת.
טבלאות מיוחדות ל- Advanced Scenarios
בפרויקטים אמיתיים בעולם, ייתכן שתצטרך שיטות סודיות מעבר ל-CRUD בסיסי, כגון שאילתות מדמיונות, סינון או הדבקה. שקול להגדיר ממשקים נפרדים לקריאה בלבד ופעולות לכתוב רק כדי לעקוב אחר עקרון ה- Interface Segregation Principle.
public interface IDataReader<TEntity>
{
Task<IEnumerable<TEntity>> QueryAsync(Expression<Func<TEntity, bool>> predicate);
Task<TEntity?> GetByIdAsync(int id);
}
public interface IDataWriter<TEntity>
{
Task InsertAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(int id);
}
תבנית המפעל יכולה לייצר יישום משולב המעכב את שני הממשקים בעת הצורך, או להחזיר פריטים נפרדים לקריאה ולכתוב אם תבחר גישה CQRS.
יישום קידוד Data Access
לאחר שהממשק מוגדר, אתה יוצר יישום קונקרטי עבור כל טכנולוגיית גישה לנתונים. להלן דוגמאות באמצעות ההרחבה:0 (Entity Framework CoreFLT:1 ו-FLT:2DapperirFLT:3, שניים ממסגרות הגישה הנפוצות ביותר של נתונים Core.
המונחים: Entity Framework Relementation
public class EfDataRepository : IDataRepository
{
private readonly AppDbContext _context;
public EfDataRepository(AppDbContext context)
{
_context = context;
}
public async Task<IEnumerable<DataItem>> GetAllAsync()
{
return await _context.Set<DataItem>().AsNoTracking().ToListAsync();
}
public async Task<DataItem?> GetByIdAsync(int id)
{
return await _context.Set<DataItem>().FindAsync(id);
}
// Additional methods omitted for brevity
}
שימו לב כי (FLT 3: 3) מצפה לדוגמה של FLT:4, אשר ביישום הליבה של .NET מוזר הוא בדרך כלל מוזרק באמצעות מיכל DI. זה תואם את תבנית המפעל: המפעל יהיה צורך גישה למכל DI כדי לפתור תלות כזו.
« « ⁇
public class DapperDataRepository : IDataRepository
{
private readonly IDbConnection _connection;
private readonly string _connectionString;
public DapperDataRepository(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection");
_connection = new SqlConnection(_connectionString);
}
public async Task<IEnumerable<DataItem>> GetAllAsync()
{
var sql = "SELECT * FROM DataItems";
return await _connection.QueryAsync<DataItem>(sql);
}
public async Task<DataItem?> GetByIdAsync(int id)
{
var sql = "SELECT * FROM DataItems WHERE Id = @Id";
return await _connection.QueryFirstOrDefaultAsync<DataItem>(sql, new { Id = id });
}
}
שני המימושים ממלאים את אותו החוזה, אך ישתמשו במכניקה שונה לחלוטין.המפעל יחליט מי ישתתתת על בסיס תנאי ריצה.
בניית המפעל: מפשוט לפשט
המפעל עצמו מבסס את לוגיקה ההחלטה. מפעל סטטי מספיק כאשר הבחירה של יישום תלויה רק בערכה של תצורה (למשל, מפתח יישום).
מפעל פשוט סטטי (Configuration-Driven)
public static class DataRepositoryFactory
{
public static IDataRepository Create(IServiceProvider serviceProvider, string provider)
{
return provider switch
{
"EntityFramework" => serviceProvider.GetRequiredService<EfDataRepository>(),
"Dapper" => ActivatorUtilities.CreateInstance<DapperDataRepository>(serviceProvider),
_ => throw new NotSupportedException($"Data provider '{provider}' is not supported.")
};
}
}
(ה) , (ה) , ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
מפעל אבסטרקטי למשפחות מוצרים מרובות
כאשר היישום שלך דורש סוגים שונים של אובייקטים גישה לנתונים (למשל, אחד עבור הזמנות, אחר עבור מלאי, כל אחד יכול להשתמש מנוע אחסון שונה), המפעל הפשוט הופך ללא נאמנות. AnFLT:0Abstract Factory Factory Factory LACFLT:1 מגדיר ממשק ליצירת משפחות של אובייקטים קשורים ללא ציון של כיתות קונקרטיות שלהם.
public interface IDataAccessFactory
{
IDataRepository CreateOrderRepository();
IDataRepository CreateInventoryRepository();
// etc.
}
public class EfDataAccessFactory : IDataAccessFactory
{
private readonly AppDbContext _context;
public EfDataAccessFactory(AppDbContext context) => _context = context;
public IDataRepository CreateOrderRepository() => new EfOrderRepository(_context);
public IDataRepository CreateInventoryRepository() => new EfInventoryRepository(_context);
}
public class DapperDataAccessFactory : IDataAccessFactory
{
private readonly string _connectionString;
public DapperDataAccessFactory(IConfiguration configuration) => _connectionString = configuration.GetConnectionString("DefaultConnection");
public IDataRepository CreateOrderRepository() => new DapperOrderRepository(_connectionString);
public IDataRepository CreateInventoryRepository() => new DapperInventoryRepository(_connectionString);
}
המפעל הפשטי חזק יותר אך גם כבד יותר.למשוך אותו ליישומים שבהם עליך להחליף מחסניות גישה נתונים שלמות (למשל, החלפת כל מאגרי ה-Entity Framework עם סורקי דאפר) בבת אחת, ולא יישום פרטני של דובדבן.
שילוב המפעל עם .NET Core תלות בזריקת .NET Core
הכוח האמיתי של תבנית המפעל ב .NET Core עולה כאשר אתה משלב אותו עם מיכל DI במקום לרשום סוגים של מחסנים קונקרטיים, לרשום את המפעל ולתת לו לייצר את היישום המתאים על הביקוש.
הרשמה בתוכנה (או Startup.cs)
builder.Services.AddTransient<EfDataRepository>();
builder.Services.AddTransient<IDataRepository>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var provider = config.GetValue<string>("DataProvider");
return DataRepositoryFactory.Create(sp, provider);
});
ברישום זה, ה-FLT:15 נרשם באופן קבוע (כך מיכל DI יכול לפתור אותו בתוך המפעל) רישום FLT:16 משמש נציג מפעל קורא ערך FLT:17 מ- 18 ומנציגים למפעל סטטי.
שימוש בשירותים של תמיכה רב-ספקית
אם היישום שלך צריך (FLT:0)multipleFancy 1) Repositories באמצעות ספקים שונים בו זמנית (למשל, אחד עבור נתונים היסטוריים באמצעות Dapper, ואחד עבור נתונים בזמן אמת באמצעות מסגרת אנטי), אתה יכול לרשום בשם שיטות מפעל או להשתמש דפוס מילון.
builder.Services.AddSingleton<IDataRepositoryFactory>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var providers = config.GetSection("DataProviders").Get<Dictionary<string, string>>();
return new DataRepositoryFactory(sp, providers);
});
המפעל יכול לחשוף שיטה (FLT:21) אשר מחזירה את היישום המתאים על בסיס פרמטר פרמטר פרמטר , אשר ניתן לעבור כתלויה באמצעות FLT:23 או דפוס הזרקת מותאם אישית.
יתרונות אמיתיים ו-Scenarios
בואו נבחן מצבים קונקרטיים שבהם תבנית המפעל זורחת בשכבות גישה לנתונים Core.
1.בדיקה ו-Mocking
לוגיקה עסקית יחידה הופכת טריוויאלית כאשר אתה יכול להחליף מיזם לעג.המפעל ניתן להגדיר בקביעת מבחן כדי להחזיר לעג או תוך הטמעת אינטגרציה, למשל, במהלך בדיקות אינטגרציה, להגדיר את מפתח תצורה של FLT:24 ל-FLT:25 ויש מפעל אשר מחזיר FLT:26 מגובה על ידי FLT 27.
2. יישומים רבים-Tenant
כל דייר עשוי לדרוש טכנולוגיית אחסון נתונים אחרת בשל רישוי, מגבלות מורשת או הפצה גיאוגרפית. מפעל יכול לבחון את metadata של Tenant בזמן ריצה ולעדכן את המאגר המתאים - אולי אחד עשר משתמשים SQL Server באמצעות Entity Framework, משתמש אחר PostgreSQL באמצעות Dapper, ושלישי משתמש ב- Azure Cosmos DB.
3.הגירה הגואדאלית
כאשר נודדים מ-ORM אחד למשנהו (למשל, מ-Entity Framework ל- Dapper עבור שאילתות קריטיות ביצועים), המפעל מאפשר לך להנעת התנועה בהדרגה.You יכול לבנות מערכת דגל תכונה אשר, עבור אחוז של משתמשים או נקודות קצה ספציפיות, מחזיר את רצף Dapper החדש בעוד רוב היישום עדיין משתמש ב- Entity Framework.
בדיקת שכבת הגישה של נתונים מופשטת
מפעל מעוצב היטב עושה בדיקות פשוטות.אתה יכול ליצור מפעל מבחן מחזיר לעג יישומים או גירסאות קלות משקל של מאגר הנתונים שלך.
public class TestDataRepositoryFactory
{
public static IDataRepository CreateInMemory()
{
return new InMemoryDataRepository();
}
}
ה-FLT:29 פשוט מחזיק FLT:30 וליישם את הממשק באמצעות פעולות תוך-זיכרון. מבחנים יחידה יכולים לאחר מכן מיידית שירותים עסקיים עם מפעל בדיקה זה מבלי לדרוש חיבור מסד נתונים או לעג מסגרות. אינטגרציה בדיקות עדיין יכול להשתמש במפעל האמיתי כדי לאמת את מסד הנתונים.
ההליכים הטובים ביותר והמלכודות הנפוצות
אל תתגברו על-Abstract
תבנית המפעל מוסיפה עקיפין.אם היישום שלך לעולם לא יחליף את ספקי הגישה לנתונים, הפשטות רק מגבירה את המורכבות של ללא תועלת. השתמש בו רק כאשר יש לך דרישה ברורה, נוכחית עבור יישומים מרובים משתנים.
להימנע מספקים סטריפדיים
שימוש בחוזים קסם לשמות ספק (כמו FLT:31 או ;FLT:32) הוא נשך במקום, להגדיר enumeration או להשתמש אובייקט תצורה עם סוגים חזקים.
public enum DataProvider
{
EntityFramework,
Dapper,
InMemory
}
לנהל את החיים בזהירות
לעתים קרובות יש חיבורים או מקרים של Dbcontext.וודא כי מיכל DI מנהל את היקף שלהם כראוי. עבור יישומי אינטרנט, חיים בקנה מידה (למשל בקשה) הוא בדרך כלל מתאים ל-EF Core Dbcontext, אבל חיבורי Dapper עשויים להיות זקוקים ל transient או בקנה מידה על בסיס אסטרטגיית החיבור.המפעל לא צריך למצוץ ללא הגבלת זמן אלא אם הם חסרי מדינה.
לשקול שימוש במפעל עם רישום
עבור יישומים גדולים, לשקול יישום של FLT:0 registry PatternsFLT 1 לצד המפעל. A הרישום חנויות מפעלים pre-configed על ידי כמה מזהה (למשל, מזהה דייר, שם הגיוני) הלקוח שואל את הרישום עבור המפעל המתאים, אשר לאחר מכן יוצר את המאגר.
השוואה עם תבניות חלופיות
תבנית המפעל אינה הדרך היחידה לגישה נתונים מופשטת.כאן היא משווה גישות נפוצות אחרות ב-.NET Core:
- (FLT:0)Strategy PatternsFLT:1 - דומה למפעל, אבל המוקד הוא על אלגוריתמים מרתיעים (למשל, אסטרטגיות מיון או סינון שונים) ולא יצירת אובייקטים.תבנית המפעל היא דפוס בריא; דפוס אסטרטגיה הוא התנהגות.
- (FLT:0) Decorator PatternsFLT:1 - שימושי להוספת חששות בחיתוך צלב (הקיגה, ריצוף, retry) למחסן מבלי לשנות את הקוד שלו.המפעל יכול לקשט את המאגר שהוא יוצר, שילוב שני דפוסים.
- (FLT:0) דפוסי התחדשות (ללא מפעל) ,(FLT:1) – באופן ישיר מזרקת מאגר בטון באמצעות DI עובד עבור יישומים פשוטים.המפעל מוסיף גמישות כאשר הסוג הבטון משתנה.
משאבים חיצוניים וקריאה נוספת
כדי להעמיק את ההבנה של מודל החרושת ו- .NET Core גישה לנתונים, מתייחס למקורות הסמכותיים הבאים:
- אדריכלות יישומים של Microsoft .NET Application Architecture - Data Access design PatternsFillo 1
- (ב) ,0) ,ASP.NET Core Costencyזרקה Documentationph 1
- (ב) ,0) אישור גורו - שיטת המפעל (עם דוגמאות קודים)
- (ב) ,0) Microsoft - DDD-Oriented Microservices: החלת תבנית פיסול עם המפעלים FLT:1
מסקנה: בניית שינוי
תבנית המפעל מספקת דרך ממושמעת לנהל וריאציות בשכבות גישה לנתונים, המאפשרת לך להחליף את החזרת האחסון, לאמץ טכנולוגיות חדשות, ולבחון את ההיגיון העסקי בבידוד.NET Core, שילוב של החרושת עם מיכל DI בנוי מניב ארכיטקטורת נתונים נקייה, שמירה על עקרונות SOLID.התחל קטן: להגדיר ממשק, ליישם שתי כיתות קונקרטיות, ליצור מפעל סטטי, חוט סטטי, ולהוביל אותו לתוך דרישות אחסון מרובות, כגון הפעלת קוד פתוח.