رمزنگاری و رمز گذاری مقادیر تنظیمات برنامه در ASP.NET CORE
پیش نیازها :
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایلهای config
ذخیره سازی تنظیمات برنامههای ASP.NET Core در بانک اطلاعاتی به کمک Entity Framework Core
رمزنگاری و رمز گذاری مقادیر تنظیمات برنامه در ASP.NET CORE
پیش نیازها :
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایلهای config
ذخیره سازی تنظیمات برنامههای ASP.NET Core در بانک اطلاعاتی به کمک Entity Framework Core
string city = "New York"; List<School> schools = db.Schools.ToList(); List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
در کد بالا ابتدا کلیه ردیفهای جدول از دیتابیس به حافظه منتقل میشود و سپس برروی آنها کوئری مورد نظر اعمال میگردد که بشدت میتواند برای یک برنامه - خصوصا برنامه وب - بهدلیل دریافت کلیهی ردیفهای جدول بسیار مخرب باشد. کوئری فوق را میتوان به صورت زیر اصلاح کرد:
List<School> newYorkSchools = db.Schools.Where(s => s.City == city).ToList(); یا IQueryable<School> schools = db.Schools; List<School> newYorkSchools = schools.Where(s => s.City == city).ToList();
کد زیر را در نظر بگیرید:
string city = "New York"; List<School> schools = db.Schools.Where(s => s.City == city).ToList(); var sb = new StringBuilder(); foreach(var school in schools) { sb.Append(school.Name); sb.Append(": "); sb.Append(school.Pupils.Count); sb.Append(Environment.NewLine); }
هدف تکه کد بالا این است که تعداد دانش آموزان مدرسههای واقع در شهر New York را بدست آورد.
توجه داشته باشید:
اگر کوئری بالا را بهوسیلهی یک پروفایلر بررسی نمایید، متوجه خواهید شد 1 + 200 رفت و برگشت به دیتابیس صورت گرفته است که به "N+1 select problem" معروف است. 1 مرتبه جهت دریافت لیست مدرسههای شهر نیویورک و 200 مرتبه جهت دریافت تعداد دانش آموزان هر مدرسه.
بدلیل فعال بودن Lazy Loading، زمانیکه موجودیتی فراخوانی میشود، سایر موجودیتهای وابسته به آن، زمانی از دیتابیس فراخوانی خواهند شد که به آنها دسترسی پیدا کنید. در حلقهی foreach هم به ازای هر مدرسه (200 مدرسه) شهر نیویورک یک رفت و برگشت انجام میشود.
اما راه حل در این مورد خاص استفاده از Eager Loading است. خط دوم کد را بصورت زیر تغییر دهید:
List<School> schools = db.Schools .Where(s => s.City == city) .Include(x => x.Pupils) .ToList();
حال با یک رفت و برگشت، همراه هر مدرسه اطلاعات مربوط به دانش آموزان وابستهی آن نیز در دسترس خواهد بود.
فرض کنید قصد دارید نام و نام خانوادگی دانش آموزان یک مدرسه را بدست آورید.
int schoolId = 1; List<Pupil> pupils = db.Pupils .Where(p => p.SchoolId == schoolId) .ToList(); foreach(var pupil in pupils) { textBox_Output.Text += pupil.FirstName + " " + pupil.LastName; textBox_Output.Text += Environment.NewLine; }
اما راه حل:
var pupils = db.Pupils .Where(p => p.SchoolId == schoolId) .Select(x => new { x.FirstName, x.LastName }) .ToList();
فرض کنید نوع ستون جدول دانش آموزان (VARCHAR(20 است و خصیصه کدپستی مدل دانش آموز مانند زیر تعریف شده است:
public string PostalZipCode { get; set; }
انتخاب نوع داده و تطابق نوع داده مدل با ستون جدول دارای اهمیت زیادی است و در صورت عدم رعایت، باعث کاهش کارآیی شدید میگردد. در کد زیر قصد دارید لیست نام و نام خانوادگی دانش آموزانی را که کدپستی آنها 90210 میباشد، بدست بیاورید.
string zipCode = "90210"; var pupils = db.Pupils .Where(p => p.PostalZipCode == zipCode) .Select(x => new {x.FirstName, x.LastName}) .ToList();
هنگامیکه کوئری بالا را اجرا نمایید، زمان زیادی جهت اجرای آن صرف خواهد شد. در صورتی که از یک پروفایلر استفاده نمایید، میتوانید عملیات پرهزینه را شناسایی نمایید و اقدام به کاهش هزینهها کنید.
همانطور که در شکل بالا مشخص است عملیات index scan از سایر عملیاتها پرهزینهتر است. حال به بررسی علت بهوجود آمدن این عملیات پرهزینه خواهیم پرداخت.
Index Scan زمانی رخ میدهد که اس کیو ال سرور مجبور است هر صفحهی از ایندکس را بخواند و شرایط را (کدپستی برابر 90210) اعمال نماید و نتیجه را برگرداند. Index Scan بسیار هزینه بر است، چون اس کیو ال سرور، کل ایندکس را بررسی مینماید. نقطهی مقابل و بهینهی آن، Index Seek است که اس کیو ال سرور به صفحهی مورد نظر ایندکسی که به شرایط نزدیکتر است، منتقل میگردد.
خب چرا اس کیو ال سرور Index Scan را بجای Index Seek انتخاب کرده است؟!
اشکالی در قسمت سمت چپ شکل بالا که به رنگ قرمز نمایش داده شده است، وجود دارد:
Type conversion: Seek Plan for CONVERT_IMPLICIT(nvarchar(20), [Extent1].[PostalZipCode],0)=[@p__linq__0]
[Column(TypeName = "varchar")] public string PostalZipCode { get; set; }
public class Account
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Category
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 130, Message = "طول نام باید بین 3 و 130 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Payee
{
public virtual int Id { get; set; }
[NotNullNotEmpty]
[Length(Min = 3, Max = 120, Message = "طول نام باید بین 3 و 120 کاراکتر باشد")]
public virtual string Name { get; set; }
}
public class Bill
{
public virtual int Id { get; set; }
[NotNull]
public virtual Account Account { get; set; }
[NotNull]
public virtual Category Category { get; set; }
[NotNull]
public virtual Payee Payee { get; set; }
[NotNull]
public virtual decimal Amount { set; get; }
[NotNull]
public virtual DateTime BillDate { set; get; }
[NotNullNotEmpty]
[Length(Min = 1, Max = 500, Message = "طول توضیحات باید بین 1 و 500 کاراکتر باشد")]
public virtual string Description { get; set; }
}
create table Accounts (
AccountId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (AccountId)
)
create table Bills (
BillId INT IDENTITY NOT NULL,
Amount DECIMAL(19,5) not null,
BillDate DATETIME not null,
Description NVARCHAR(500) not null,
AccountId INT not null,
CategoryId INT not null,
PayeeId INT not null,
primary key (BillId)
)
create table Categories (
CategoryId INT IDENTITY NOT NULL,
Name NVARCHAR(130) not null,
primary key (CategoryId)
)
create table Payees (
PayeeId INT IDENTITY NOT NULL,
Name NVARCHAR(120) not null,
primary key (PayeeId)
)
alter table Bills
add constraint fk_Account_Bill
foreign key (AccountId)
references Accounts
alter table Bills
add constraint fk_Category_Bill
foreign key (CategoryId)
references Categories
alter table Bills
add constraint fk_Payee_Bill
foreign key (PayeeId)
references Payees
var newBill = new Bill
{
Account = accountRepository.GetByKey(1),
Amount = 1,
BillDate = DateTime.Now,
Category = categoryRepository.GetByKey(1),
Description = "testestest...",
Payee = payeeRepository.GetByKey(1)
};
SELECT account0_.AccountId as AccountId0_0_, account0_.Name as Name0_0_
FROM Accounts account0_ WHERE account0_.AccountId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT category0_.CategoryId as CategoryId2_0_, category0_.Name as Name2_0_
FROM Categories category0_ WHERE category0_.CategoryId=@p0;@p0 = 1 [Type: Int32 (0)]
SELECT payee0_.PayeeId as PayeeId3_0_, payee0_.Name as Name3_0_
FROM Payees payee0_ WHERE payee0_.PayeeId=@p0;@p0 = 1 [Type: Int32 (0)]
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:48:33 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
INSERT INTO Bills (Amount, BillDate, Description, AccountId, CategoryId, PayeeId)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
select SCOPE_IDENTITY();
@p0 = 1 [Type: Decimal (0)],
@p1 = 2010/12/27 11:58:22 ق.ظ [Type: DateTime (0)],
@p2 = 'testestest...' [Type: String (500)],
@p3 = 1 [Type: Int32 (0)],
@p4 = 1 [Type: Int32 (0)],
@p5 = 1 [Type: Int32 (0)]
public partial class Bill : EntityObject
{
public global::System.Int32 BillId {set;get;}
public global::System.Decimal Amount {set;get;}
public global::System.DateTime BillDate {set;get;}
public global::System.String Description {set;get;}
public global::System.Int32 AccountId {set;get;}
public global::System.Int32 CategoryId {set;get;}
public global::System.Int32 PayeeId {set;get;}
public Account Account {set;get;}
public Category Category {set;get;}
}