مدیریت سفارشی سطوح دسترسی کاربران در MVC
زیباتر کد بنویسیم
داشتن آگاهی در مورد ساختارهای دادهها، الگوریتمها و یا عملگرهای بیتی بسیار عالی است و یا تسلط بر نحوهی کارکرد ابزارهایی مانند SharePoint و امثال آن این روزها ضروری است. اما باید در نظر داشت، کدی که امروز تهیه میشود شاید فردا یا ماه دیگر یا چند سال بعد نیاز به تغییر داشته باشد، بنابراین دانش زیبا نوشتن یک قطعه کد که خواندن آنرا سادهتر میکند و در آینده افرادی که از آن نگهداری خواهند کرد زیاد "زجر" نخواهند کشید، نیز ضروری میباشد. (اگر کامنتهای سایت را خوانده باشید یکی از دوستان پیغام گذاشته بود، اگر به من بگویند یک میلیون بگیرید و برنامه فعلی را توسعه دهید یا رفع اشکال کنید، حاضرم 10 هزارتومان بگیرم و آنرا از صفر بنویسم! متاسفانه این یک واقعیت تلخ است که ناشی از عدم خوانا بودن کدهای نوشته شده میباشد.)
در ادامه یک سری از اصول زیبا نویسی کدها را بررسی خواهیم کرد.
1- سعی کنید میزان تو در تو بودن کدهای خود را محدود کنید.
لطفا به مثال زیر دقت نمائید:
void SetA()
{
if(a == b)
{
foreach(C c in cs)
{
if(c == d)
{
a = c;
}
}
}
}
void SetA()
{
if(a != b)
return;
foreach(C c in cs)
a = GetValueOfA(c);
}
TypeOfA GetValueOfA(C c)
{
if(c == d)
return c;
return a;
}
افزونههای CodeRush و refactor pro مجموعهی DevExpress از لحاظ مباحث refactoring در ویژوال استودیو حرف اول را میزنند. فقط کافی است برای مثال قطعه کد if داخلی را انتخاب کنید، بلافاصله سه نقطه زیر آن ظاهر شده و با کلیک بر روی آن امکان استخراج یک تابع از آنرا برای شما به سرعت فراهم خواهد کرد.
مثالی دیگر:
if (foo) {
if (bar) {
// do something
}
}
if (foo && bar) {
// do something
}
و یا یک مثال دیگر:
میزان تو در تو بودن این تابع جاوا اسکریپتی را ملاحظه نمائید:
function findShape(flags, point, attribute, list) {
if(!findShapePoints(flags, point, attribute)) {
if(!doFindShapePoints(flags, point, attribute)) {
if(!findInShape(flags, point, attribute)) {
if(!findFromGuide(flags,point) {
if(list.count() > 0 && flags == 1) {
doSomething();
}
}
}
}
}
}
function findShape(flags, point, attribute, list) {
if(findShapePoints(flags, point, attribute)) {
return;
}
if(doFindShapePoints(flags, point, attribute)) {
return;
}
if(findInShape(flags, point, attribute)) {
return;
}
if(findFromGuide(flags,point) {
return;
}
if (!(list.count() > 0 && flags == 1)) {
return;
}
doSomething();
}
با وجود پیشرفتهای زیادی که در طراحی و پیاده سازی IDE ها صورت گرفته و با بودن ابزارهای تکمیل سازی خودکار متن تایپ شده در آنها، این روزها استفاده از نامهای بلند برای توابع یا متغیرها مشکل ساز نیست و وقت زیادی را تلف نخواهد کرد. برای مثال به نظر شما اگر پس از یک سال به کدهای زیر نگاه کنید کدامیک خود توضیح دهندهتر خواهند بود (بدون مراجعه به مستندات موجود)؟
void UpdateBankAccountTransactionListWithYesterdaysTransactions()
//or?
void UpdateTransactions()
اگر مورد 2 را رعایت کرده باشید، کمتر به نوشتن کامنت نیاز خواهد بود. از توضیح موارد بدیهی خودداری کنید، زیرا آنها بیشتر سبب اتلاف وقت خواهند شد تا کمک به افراد دیگر یا حتی خود شما. همچنین هیچگاه قطعه کدی را که به آن نیاز ندارید به صورت کامنت شده به مخزن کد در یک سیستم کنترل نگارش ارسال نکنید.
//function thisReallyHandyFunction() {
// someMagic();
// someMoreMagic();
// magicNumber = evenMoreMagic();
// return magicNumber;
//}
به صورت خلاصه جهت نگهداری سوابق کدهای قدیمی باید از سورس کنترل استفاده کرد و نه به صورت کامنت قرار دادن آنها.
از کامنتهای نوع زیر پرهیز کنید که بیشتر سبب رژه رفتن روی اعصاب خواننده میشود تا کمک به او! (خواننده را بیسواد فرض نکنید)
// Get the student's id
thisId = student.getId();
// TODO: This is too bad. FIX IT!
4- عدم استفاده از عبارات شرطی بیمورد هنگام بازگشت دادن یک مقدار bool:
مثال زیر را درنظر بگیرید:
if (foo>bar) {
return true;
} else {
return false;
}
return foo>bar;
برای مثال:
Something something = new Something(foo);
return something;
return new Something(foo);
6- در نگارشهای جدید دات نت فریم ورک استفاده از ArrayList منسوخ شده است. بجای آن بهتر است از لیستهای جنریک استفاده شود. کدی که در آن از ArrayList استفاده میشود طعم دات نت فریم ورک 1 را میدهد!
7- لطفا بین خطوط فاصله ایجاد کنید. ایجاد فواصل مجانی است!
دو تابع جاوا اسکریپتی زیر را (که در حقیقت یک تابع هستند) در نظر بگیرید:
function getSomeAngle() {
// Some code here then
radAngle1 = Math.atan(slope(center, point1));
radAngle2 = Math.atan(slope(center, point2));
firstAngle = getStartAngle(radAngle1, point1, center);
secondAngle = getStartAngle(radAngle2, point2, center);
radAngle1 = degreesToRadians(firstAngle);
radAngle2 = degreesToRadians(secondAngle);
baseRadius = distance(point, center);
radius = baseRadius + (lines * y);
p1["x"] = roundValue(radius * Math.cos(radAngle1) + center["x"]);
p1["y"] = roundValue(radius * Math.sin(radAngle1) + center["y"]);
pt2["x"] = roundValue(radius * Math.cos(radAngle2) + center["y"]);
pt2["y"] = roundValue(radius * Math.sin(radAngle2) + center["y");
// Now some more code
}
function getSomeAngle() {
// Some code here then
radAngle1 = Math.atan(slope(center, point1));
radAngle2 = Math.atan(slope(center, point2));
firstAngle = getStartAngle(radAngle1, point1, center);
secondAngle = getStartAngle(radAngle2, point2, center);
radAngle1 = degreesToRadians(firstAngle);
radAngle2 = degreesToRadians(secondAngle);
baseRadius = distance(point, center);
radius = baseRadius + (lines * y);
p1["x"] = roundValue(radius * Math.cos(radAngle1) + center["x"]);
p1["y"] = roundValue(radius * Math.sin(radAngle1) + center["y"]);
pt2["x"] = roundValue(radius * Math.cos(radAngle2) + center["y"]);
pt2["y"] = roundValue(radius * Math.sin(radAngle2) + center["y");
// Now some more code
}
استفاده از فاصله بین خطوط در تابع دوم باعث بالا رفتن خوانایی آن شده است و این طور به نظر میرسد که سطرهایی با عملکرد مشابه در یک گروه کنار هم قرار گرفتهاند.
8- توابع خود را کوتاه کنید.
یک تابع نباید بیشتر از 50 سطر باشد (البته در این مورد بین علما اختلاف هست!). اگر بیشتر شد بدون شک نیاز به refactoring داشته و باید به چند قسمت تقسیم شود تا خوانایی کد افزایش یابد.
به صورت خلاصه یک تابع فقط باید یک کار را انجام دهد و باید بتوان عملکرد آنرا در طی یک جمله توضیح داد.
9- از اعداد جادویی در کدهای خود استفاده نکنید!
کد زیر هیچ معنایی ندارد!
if(mode == 3){ ... }
else if(mode == 4) { ... }
if(mode == MyEnum.ShowAllUsers) { ... }
else if(mode == MyEnum.ShowOnlyActiveUsers) { ... }
اگر نیاز به تعداد زیادی پارامتر ورودی وجود داشت (بیش از 6 مورد) از struct و یا کلاس جهت معرفی آنها استفاده کنید.
.NET 8 Release Candidate 2 (RC2) is now available and includes many great new improvements to ASP.NET Core!
This is the last release candidate that we plan to share before the final .NET 8 release later this year. Most of the planned features and changes for .NET 8 are part of this release candidate and are ready for you to try out. You can find the full list of what’s new in ASP.NET Core in .NET 8 in the docs.
اگر به میزان مصرف حافظه اولیهی برنامههای دات نت دقت کنیم، نسبت به مثلا یک برنامهی MFC چند برابر به نظر میرسند و ... این علت دارد:
زمانیکه یک برنامهی مبتنی بر دات نت اجرا میشود، ابتدا JIT compiler شروع به کار کرده و شروع به کامپایل برنامه میکند. این بارگزاری هم در همان پروسهی اصلی برنامه انجام میشود. به همین جهت میزان مصرف حافظهی برنامههای دات نت عموما بالا به نظر میرسد.
اکنون سؤال اینجا است که آیا می توان این حافظهای را که دیگر مورد استفاده نیست (و توسط JIT compiler اخذ شده) به سیستم بازگرداند و محاسبهی مجددی را در این مورد انجام داد. پاسخ به این سؤال را در متد ReEvaluateWorkingSet زیر میتوان مشاهده کرد:
using System;
using System.Diagnostics;
namespace Toolkit
{
public static class Memory
{
public static void ReEvaluateWorkingSet()
{
try
{
Process loProcess = Process.GetCurrentProcess();
//it doesn't matter what you set maxWorkingSet to
//setting it to any value apparently causes the working set to be re-evaluated and excess discarded
loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet + 1);
}
catch
{
//The above code requires Admin privileges.
//So it's important to trap exceptions in case you're running without admin rights.
}
}
}
}
در این متد ابتدا پروسه جاری دریافت شده و سپس MaxWorkingSet به یک عدد دلخواه تنظیم میشود. مهم نیست که این عدد چه چیزی باشد، زیرا این تنظیم سبب میشود که در پشت صحنه به شکل حساب شدهای حافظهای که مورد استفاده نیست به سیستم بازگردانده شود و سپس عددی که در task manager نمایش داده میشود، مجددا محاسبه گردد. همچنین باید دقت داشت که این کد تنها با دسترسی مدیریتی قابل اجرا است و به همین دلیل وجود این try/catch ضروری است.
نحوه استفاده از متد ReEvaluateWorkingSet در برنامههای WinForms :
فایل Program.cs را یافته و سپس در روال رویداد گردان Idle برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید (مثلا هر زمان که برنامه minimized شد اجرا میشود):
//Program.cs
namespace MemUsage
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//...
Application.Idle += applicationIdle;
}
static void applicationIdle(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}
}
}
نحوه استفاده از متد ReEvaluateWorkingSet در برنامههای WPF :
فایل App.xaml.cs را یافته و سپس در روال رویدادگردان Deactivated برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید:
//App.xaml.cs
public App()
{
this.Deactivated += appDeactivated;
}
void appDeactivated(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}
تاثیر آن هم قابل ملاحظه است (حداقل از لحاظ روانی!). تست کنید!
اولین و اساسیترین قدم در نگهداری یک سیستم مبتنی بر داده، تهیه پشتیبانهای منظم و همچنین قابل اطمینان میباشد.
دستور T-SQL زیر بدون ریاستور کردن یک فایل بک آپ اس کیوال سرور، سعی در تعیین اعتبار آن میکند:
RESTORE VERIFYONLY
FROM DISK = 'C:\SQL_Backup\Test1'
WITH FILE = 1,
LOADHISTORY
The backup set on file 1 is valid.
SELECT DISTINCT physical_device_name
FROM msdb.dbo.backupmediafamily
ORDER BY
physical_device_name
DECLARE @path NVARCHAR(1000),
@msg NVARCHAR(MAX),
@NewLine CHAR(2),
@sql NVARCHAR(2000)
SET @NewLine = CHAR(13) + CHAR(10)
SET @msg = ''
DECLARE DATABASES_CURSOR CURSOR
FOR
SELECT DISTINCT physical_device_name
FROM msdb.dbo.backupmediafamily
ORDER BY
physical_device_name
OPEN DATABASES_CURSOR
FETCH NEXT FROM DATABASES_CURSOR INTO @path
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Verifying: ' + @path
SET @sql = 'RESTORE VERIFYONLY FROM DISK = ''' + @path
+ ''' WITH FILE = 1, LOADHISTORY'
EXEC sp_executesql @sql
IF @@ERROR <> 0
BEGIN
SET @msg = @msg + 'Failed to verify: ' + @path + @NewLine
END
FETCH NEXT FROM DATABASES_CURSOR INTO @path
END
CLOSE DATABASES_CURSOR
DEALLOCATE DATABASES_CURSOR
IF @msg <> ''
BEGIN
PRINT @msg
-- send email
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'Administrator@site.net', -- Change This
@Subject = 'backup verification info.',
@Body = @msg
,@importance = 'High'
END
اسکریپت فوق بر روی تمامی مسیرهای ثبت شده موجود که در آنها پیشتر پشتیبان تهیه شده است، دستور RESTORE VERIFYONLY را اجرا میکند و در آخر اگر پیغامی حاصل شد، یعنی مشکلی پدید آمده و ایمیلی را به اشخاص مورد نظر ارسال میکند.
میتوان بر روی این اسکریپت یک job تهیه کرد که هر روز پس از تهیه بک آپ خودکار، کار بررسی صحت عملیات را نیز انجام دهد.
Full-Text Search در MongoDB
MongoDB, one of the leading NoSQL databases, is well known for its fast performance, flexible schema, scalability and great indexing capabilities. At the core of this fast performance lies MongoDB indexes, which support efficient execution of queries by avoiding full-collection scans and hence limiting the number of documents MongoDB searches.
Starting from version 2.4, MongoDB began with an experimental feature supporting Full-Text Search using Text Indexes .همانگونه که میدانید مقدار Identity پس از درج به آن تخصیص مییابد چنانچه بخواهید به این مقدار دسترسی پیدا کنید چندین روش به ازای اینکار وجود دارد که ما در این مقاله سه روش معمول را بررسی خواهیم نمود.
1- استفاده از متغییر سیستمی Identity@@
2- استفاده از تابع () Scope_Identity
3- استفاده از تابع Ident_Current
هر سه این توابع مقدار Identity ایجاد شده برای جداول را نمایش میدهند. اما تفاوت هایی باهم دارند که در ادامه مقاله این تفاوتها بررسی شده است.
1- متغییر سیستمی Identity@@ : این متغییر سیستمی حاوی آخرین Identity ایجاد شده به ازای Session جاری شما است. لازم به ذکر است اگر به واسته Insert شما، Identity دیگری در یک حوزه دیگر (مانند یک Trigger) ایجاد شود مقدار موجود در این متغییر حاوی آخرین Identity ایجاد شده است. (یعنی Identity ایجاد شده توسط آن تریگر و نه خود جدول). لازم به ذکر است این موضوع به طور کامل در ادامه مقاله شرح داده شده است.
2- استفاده از تابع()Scope_Identity : با استفاده از این تابع میتوانیم آخرین Identify ایجا دشده به ازای Session جاری را بدست آوریم. لازم به ذکر است مقادیر Identity ایجاد شده توسط سایر حوزهها تاثیر در مقدار بازگشتی توسط این تابع ندارد. در ادامه مقاله این موضوع به طور کامل بررسی شده است.
3- استفاده از تابع ident_Current : این تابع آخرین مقدار Identity موجود در یک جدول را نمایش میدهد. ذکر این نکته ضروری است که Identity ایجاد شده توسط سایر Sessionها هم روی خروجی این تابع تاثیرگذار است. چون این تابع آخرین Identity موجود در جدول را به شما نمایش میدهد و نه Identity ایجاد شده به ازای یکSession را.
برای بدست آوردن یک Identity کافی است که پس از درج رکورد در جدول مورد نظر متغییر سیستمی @@Identity و یا توابع Scope_Identity و یا Ident_Current را همانند مثال زیر Select کنید.
USE TEMPDB GO IF OBJECT_ID(N'Employees', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees ( ID int IDENTITY, FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO INSERT INTO Employees (FirstName,LastName) VALUES (N'مسعود',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
خروجی دستورات بالا پس از درج رکورد مورد نظر به صورت زیر است.
اما ممکن است از خودتان این سوال را بپرسید که آیا این توابع در سطح شبکه آخرین مقدار Identity درج شده توسط سایر Sessionها را نمایش میدهند و یا Session جاری را؟ (منظور Sessionی که درخواست مقدار موجود در identity را نموده است).
برای دریافت پاسخ این سوال مطابق مراحل اسکریپهای زیر را اجرا نمایید.
1-ایجاد جدول Employees1
USE TEMPDB GO IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees1 ( ID int IDENTITY(1,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
GO INSERT INTO Employees1(FirstName,LastName) VALUES (N'فرید',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
همانگونه که ملاحضه میکنید @@Identity، Scope_Identity() و Ident_Current هر سه مقدار Identity (عدد 1) ایجاد شده بوسیله دستور Insert را به شما نمایش میدهند.
1- و در انتها در یک Session دیگر دستورات زیر را اجرا نمایید.(واکشی مقدار Identity)
USE tempdb GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] GO
همانطور که مشاهده میکنید در این Seesion ما از SQL خواستهایم آخرین مقدار Identity را به ما نشان داده شود. باید به این نکته توجه کنید با توجه به اینکه در این Session عملیات درجی هنوز انجام نگرفته است که ما Identity ایجاد شده را مشاهده نماییم. بنابراین صرفاً تابع Iden_Current مقدار Identity موجود در جدول را به ما نمایش میدهد.
پس میتوان به این نکته رسید که
@@Idnetity و Scope_Identity : Identity ایجاد به ازای Session جاری را نمایش داده و به مقادیر تولید شده توسط سایر Sessionهای دیگر دسترسی ندارد.
Ident_Current : آخرین Identity موجود در جدول را به شما نمایش میدهد. بنابراین باید این نکته را در نظر داشته باشید که Identityها ایجاد شده توسط سایر Sessionها روی مقدار بازگشتی این تابع تاثیرگدار است.
اما یکی دیگر از مباحث مهم درباره Identity تاثیر Scope بر مقدار Identity است (یعنی چه!) . برای اینکه با مفهوم این موضوع آشنا شوید اسکریپتهای مربوط به مثال زیر را بدقت اجرا کنید.
1- ایجاد جدول Employees1
USE TEMPDB GO IF OBJECT_ID(N'Employees1', N'U') IS NOT NULL DROP TABLE Employees1; GO CREATE TABLE Employees1 ( ID int IDENTITY(1,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
همانطور که مشاهده میکنید مقدار شروع برای Identity برابر 1 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(1,1)) .
2- ایجاد جدول Employees2
USE TEMPDB GO IF OBJECT_ID(N'Employees2', N'U') IS NOT NULL DROP TABLE Employees2; GO CREATE TABLE Employees2 ( ID int IDENTITY(100,1), FirstName NVARCHAR(50), LastName NVARCHAR(50) ) GO
همانطور که مشاهده میکنید مقدار شروع برای Identity برابر 100 و گام افزایش هم برابر 1 در نظر گرفته شده است(Identity(100,1)).
3- ایجاد یک Trigger به ازای جدول Employees1
USE tempdb GO CREATE TRIGGER Employees1_Insert ON Employees1 FOR INSERT AS BEGIN INSERT Employees2(FirstName,LastName) SELECT FirstName,LastName FROM INSERTED END; GO
Trigger ایجاد شده به ازای جدول Employees1 به ازای عملیات Insert اجرا میشود. همچنین مقادیر درج شده در جدول Employees1 بوسیله جدول Inserted در دسترس است. لازم به ذکر است جدول Inserted یک جدول موقت بوده که توسط Trigger ایجاد شده و داخل خود آن معتبر است.
هدف ما از ایجاد این Trigger تهیه یک کپی از رکوردهایی که در جدول Employees1 درج میشوند است. این کپی قرار است با استفاده از دستور Insert…Select در جدول Employees2 ایجاد گردد.
4- درج یک رکورد در جدول Employees1 و واکشی مقدار Identity
USE tempdb GO INSERT INTO Employees1(FirstName,LastName) VALUES (N'مسعود',N'طاهری') GO SELECT @@IDENTITY AS [@@IDENTITY] SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY()] SELECT IDENT_CURRENT('Employees1') AS [IDENT_CURRENT('Employees1')] SELECT IDENT_CURRENT('Employees2') AS [IDENT_CURRENT('Employees2')] GO
مقادیر استخراج شده به ازای Identity به شرح زیر است
1- @@Identity : پس از درج رکورد در جدول Employees1 متغییر سیستمی @@Identity مقدار 100 را نمایش داده است دلیل این موضوع بر میگردد به Trigger موجود در جدول Employees1.
با توجه به اینکه جدول Employees1 دارای یک فیلد Identity بوده است هنگام درج رکورد در جدول مقدار @@Identity=1 است اما چون این جدول دارای Triggerی است که این Trigger خود با جدولی دیگری درگیر است که دارای Identity است مقدار متغییر @@identity=100 خواهد شد.
2- Scope_Identity() : مقدار نمایش داده شده توسط تابع Scope_Identity() برابر با مقدار Identity تخصیص (عدد 1) داده شده به ازای رکورد شما میباشد که این موضوع در اغلب موارد مد نظر برنامهنویسان میباشد.
3- Ident_Current(‘Employees1’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 1) موجود در جدول Employees1 است.
4- Ident_Current(‘Employees2’) : مقدار نمایش شده توسط تابع Ident_Current آخرین مقدار Identity (عدد 100) موجود در جدول Employees2 است.
چند نکته مهم
1- مقدار بازگردانده شده توسط تابع Ident_Current آخرین مقدار Identity موجود در جدول مورد نظر شما بوده است و عملیات درج سایر کاربران در این مقدار تاثیر گذار است.
2- برای بدست آوردن مقدار Identity درست بهتر است از تابع Scope_Identity() استفاده نماییم. معمولاً در بیشتر مواقع مقدار بازگردانده شده توسط این تابع مد نظر برنامه نویسان است.
3- EntityFramework و Nhibernate هم برای بدست آوردن Identity از تابع Scope_Identity استفاده میکند.