تغییر پویای رشتهی اتصالی به بانک اطلاعاتی در نگارشهای پیشین EF، مشکل بودند که نمونههایی از آن را پیشتر در مطالب زیر مشاهده کردهاید:
- «
تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد»
- «
استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First»
اما EF Core نه تنها این مشکل را پوشش را دادهاست، بلکه امکان تزریق وابستگیها و استفادهی از سرویسهای مختلف را نیز در این حین، پیش بینی کردهاست که در ادامه جزئیات آنرا مرور میکنیم.
نیاز به تغییر رشتهی اتصالی به بانک اطلاعاتی در زمان اجرا
دلایل نیاز به امکان تغییر رشتهی اتصالی در زمان اجرا شامل موارد زیر هستند:
- در برنامههایی کمی پیچیدهتر و سابقه دار، ممکن است عملیات تجاری یکسال را در بانک اطلاعاتی سال 98 و دیگری را در بانک اطلاعاتی سال 99 ثبت کنید. در این حالت کاربران باید بتوانند در زمان اجرا به هر بانک اطلاعاتی که پیشتر با آن کار کردهاند، متصل شده و از آن استفاده کنند.
- یکی از روشهای پیاده سازی برنامههای چند مستاجری، داشتن یک بانک اطلاعاتی مجزا، به ازای هر مستاجر است. در این حالت نیز تک برنامهی ما باید بتواند بر اساس Id مشتری، بانک اطلاعاتی متناظری را در زمان اجرا انتخاب کند.
- نیاز به داشتن چندین context در برنامه و کار با بانکهای اطلاعاتی متفاوت در زمان اجرا؛ مانند کار با SQL Server، اوراکل و یا SQLite
روش تغییر رشتهی اتصالی به بانک اطلاعاتی در EF Core در زمان اجرای برنامه
اگر به روش ثبت متداول سرویس DbContext برنامه و پروایدر آن دقت کنیم:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
));
یک action delegate قابل مشاهدهاست. کار این اکشن، تنظیم پروایدر و تمام نیازهای یک رشتهی اتصالی به بانک اطلاعاتی، جهت شروع به کار با Context برنامه است. نکتهی مهمی که در اینجا وجود دارد، فراخوانی هربارهی این action، به ازای هر اتصال تشکیل شدهاست. یعنی کدهای داخل این action delegate کش نمیشوند و همین مساله امکان تغییر پویای آنها را میسر میکند.
یک نکته: چون این اطلاعات کش نمیشوند، اگر رشتهی اتصالی شما ثابت است (و نیازی به تغییر آن در زمان اجرای برنامه نیست)، محل تامین آنرا به پیش از سطر services.AddDbContext انتقال دهید و فقط نتیجهی محاسبه شدهی نهایی را استفاده کنید تا کارآیی برنامه افزایش یابد؛ در غیراینصورت فراخوانی Configuration.GetConnectionString مدام تکرار خواهد شد.
دریافت یک قالب قابل تغییر از تنظیمات برنامه و تغییر آن با هدرهای درخواست رسیدهی به آن
فرض کنید قالب رشتهی اتصالی برنامه در فایل appsettings.json به صورت زیر است:
"ConnectionStrings": {
"ConnectionTemplate": "Data Source=.;Initial Catalog={db_Name};Integrated Security=True",
}
و db_Name آن قرار است برای مثال از یک query string، سشن، کوکی و یا فیلد خاصی در هدر HTTP رسیده تامین شود. برای مثال سال مالی انتخابی و یا شماره مستاجر انتخابی به صورت یک فیلد خاص HTTP به سمت برنامه ارسال میشوند.
بنابراین اکنون نیاز است به ازای هر درخواست رسیده بتوان به سرویس IHttpContextAccessor و شیء HttpContext.Request جاری دسترسی یافت و سپس از هدرهای رسیده، برای مثال هدر ویژهی tenantId و یا year را پردازش کرد؛ اما در تعریف services.AddDbContext فوق چگونه میتوان اینکار را انجام داد؟
خوشبختانه متد services.AddDbContext، دارای یک overload دیگر نیز هست که امکان دسترسی به تمام سرویسهای جاری سیستم را میسر میکند:
services.AddDbContext<ApplicationDbContext>((serviceProvider, dbContextBuilder) =>
{
var connectionStringTemplate = Configuration.GetConnectionString("ConnectionTemplate");
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
var dbName = httpContextAccessor.HttpContext.Request.Headers["tenantId"].First();
var connectionString = connectionStringTemplate.Replace("{db_Name}", dbName);
dbContextBuilder.UseSqlServer(connectionString);
});
همانطور که مشاهده میکنید، overload دوم متد services.AddDbContext، امکان ارسال serviceProvider را نیز به این action delegate دارد. پس از آن میتوان توسط متد GetRequiredService آن به هر سرویس مدنظری که در سیستم ثبت شدهاست، دسترسی یافت و برای مثال در اینجا فیلد هدر سفارشی tenantId را از آن استخراج نمود و در قالب رشتهی اتصالی به بانک اطلاعاتی، در زمان اجرا به صورت پویایی جایگزین کرد.
همچنین در صورت نیاز میتوان UseSqlServer آنرا نیز در این action delegate به هر پروایدر دیگری در زمان اجرا تغییر داد و از این لحاظ محدودیتی وجود ندارد.
یک نکته: البته برنامه نباید هر tenantId ای را پردازش کند و این خودش میتواند تبدیل به یک نقیصهی امنیتی شود. به همین جهت برای مثال میتوان tenantId را در یک JWT قرار داد و در حین تعیین اعتبار آن و کاربر جاری، این مقدار را نیز بررسی کرد.