مطالب
اگر نصب سرویس پک اس کیوال سرور Fail شد ...

همانطور که مطلع هستید سرویس پک سه SQL Server چند روزی است که منتشر شده. این به روز رسانی بر روی یک سرور بدون مشکل نصب شد؛ در سرور دیگر به علت داشتن یک سری برنامه امنیتی مزاحم (که مثلا دسترسی به رجیستری را مونیتور و سد می‌کنند) با شکست مواجه و در آخر پیغام Fail نمایش داده شد. مجددا آنرا اجرا کردم، سریع تمام مراحل را تمام کرد باز هم Fail را نمایش داد.
خوب؛ گفتم احتمالا مشکلی نیست. سعی کردم به سرور وصل شوم ... پیغام «این سرور دسترسی از راه دور را نمی‌پذیرد» و از این حرف‌های متداول ظاهر شد. به لاگ موجود در Event log ویندوز که مراجعه کردم پیغام خطای زیر نمایان بود:

Script level upgrade for database 'master' failed because upgrade step 'sqlagent100_msdb_upgrade.sql' encountered error 5597, state 1, severity 16. This is a serious error condition which might interfere with regular operation and the database will be taken offline. If the error happened during upgrade of the 'master' database, it will prevent the entire SQL Server instance from starting. Examine the previous errorlog entries for errors, take the appropriate corrective actions and re-start the database so that the script upgrade steps run to completion.

اوه! اوه! اوه! در این لحظه‌ی عرفانی، دیتابیس master نابود شده! نمی‌شود وصل شد. سروری که داشت تا مدتی قبل بدون هیچ مشکلی کار می‌کرد، الان دیگر حتی نمی‌شود به آن وصل شد. به کنسول سرویس‌های ویندوز مراجعه کردم (services.msc)، سعی کردم سرویس اس کیوال را که از کار افتاده دستی اجرا کنم، پیغام زیر مجددا در event log ظاهر شد:

FILESTREAM feature could not be initialized. The Windows Administrator must enable FILESTREAM on the instance using Configuration Manager before enabling through sp_configure.

قابلیت FILESTREAM را نمی‌تواند آغاز کند. پس از مدتی جستجو مشخص شد که این مورد را می‌شود در رجیستری ویندوز غیرفعال کرد؛ به صورت زیر:

1) Open up Registry Editor
2) Go To HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10.MSSQLServer\MSSQLSERVER\FileStream
3) Edit the value "EnableLevel" and set it to 0
4) Restart SQL Server.

پس از انجام اینکار، سرویس اس کیوال استارت شد (از طریق کنسول سرویس‌های ویندوز). در ادامه، امکان اتصال به آن نبود (حتی با اکانت sa):

Login failed for user 'sa'. Reason: Server is in script upgrade mode. Only administrator can connect at this time. (Microsoft SQL Server, Error: 18401)


باز هم پس از مدتی جستجو معلوم گردید که «کمی باید صبر کرد». آن پیغام اول کار مبتنی بر تخریب دیتابیس master هم بی‌مورد است. پس از fail شدن نصب سرویس پک، هنوز برنامه نصاب آن در پشت صحنه مشغول به کار است. این مورد به وضوح در task manager ویندوز مشخص است. سرور به مدت 15 دقیقه به حال خود رها شد. پس از آن بدون مشکل اتصال برقرار گردید و همه چیز مجددا شروع به کار کرد.

بنابراین اگر در حین نصب سرویس پک SQL Server مشکلی پیش آمد، نگران نباشید. باید به نصاب آن زمان داد (برنامه mscorsw.exe در پشت صحنه مشغول به کار است). برنامه نصاب آن هم هیچ نوع خطای مفهومی را گزارش نمی‌دهد. تمام مراحل، بجای نمایش در برنامه تمام صفحه نصاب آن، در event log ویندوز ثبت می‌شود. این برنامه تمام صفحه فقط کارش نمایش یک progress bar است!


اگر ... هیچکدام از این موارد جواب نداد، امکان بازسازی دیتابیس master نیز وجود دارد: [^ , ^]
ولی دست نگه دارید و سریع اقدام نکنید. ابتدا به task manager مراجعه کنید. آیا برنامه mscorsw.exe در حال اجرا است؟ اگر بله، یعنی هنوز کار نصب تمام نشده. حداقل یک ربع باید صبر کنید.

مطالب
تعیین اعتبار کردن یک عبارت SQL - قسمت دوم

مطلبی را روز قبل نوشتم در مورد تعیین اعتبار یک کوئری. این مورد از آنجایی حائز اهمیت می‌شود که برای مثال تغییری در ساختار یکی از جداول حاصل شود. اکنون می‌خواهیم بررسی کنیم آیا سیستم از کار افتاده یا نه!؟
شما می‌توانید نام یک فیلد را تغییر دهید (حتی اگر این فیلد در یک رویه ذخیره شده استفاده شده باشد) و هیچ خطایی هم نخواهید گرفت و این منشاء دردسرهای زیادی خواهد بود.
در حالت استفاده از SET NOEXEC ON ، کوئری مورد نظر فقط کامپایل می‌شود و همچنین از لحاظ نحوی بررسی خواهد شد، اما این کافی نیست.
مثال زیر را در نظر بگیرید:

Create PROCEDURE Test1
AS
SELECT * FROM tblPIDs1
جدول tblPIDs1 در دیتابیس مورد نظر وجود ندارد.
این کوئری قابل اجرا است. دکمه‌ی F5 را فشار دهید، بلافاصله رویه ذخیره شده‌ی Test1 برای شما ایجاد خواهد شد.
سپس کوئری زیر را اجرا کنید:

USE testdb
SET NOEXEC ON;
exec test1 ;
SET NOEXEC OFF;
بدون مشکل و بروز خطایی، پیغام زیر را نشان می‌دهد:
Command(s) completed successfully

ایرادی هم وارد نیست چون فقط عملیات parsing و compile صورت گرفته و نه اجرای واقعی رویه ذخیره شده. اینجا از لحاظ دستوری مشکلی وجود ندارد.

در این نوع موارد می‌توان از SET FMTONLY ON استفاده کرد. این مورد اجرای غیر واقعی یک کوئری را سبب می‌شود (تاثیری روی دیتابیس موجود نخواهد داشت، برای مثال اگر در رویه ذخیره شما عبارت insert وجود داشت، دیتایی insert نخواهد شد) و تنها متادیتای حاصل را بازگشت می‌دهد. مثلا نام ستون‌های یک کوئری را و همچنین در این حین اگر خطایی رخ داده باشد، آن‌را نیز ارائه خواهد داد.

USE testdb
SET FMTONLY ON;
exec test1 ;
SET FMTONLY OFF;
با اجرای کوئری فوق خطای زیر ظاهر می‌شود:
Msg 208, Level 16, State 1, Procedure test1, Line 3
Invalid object name 'tblPIDs1'.
برای اتوماسیون این توانایی می‌توان از کوئری زیر استفاده کرد:

USE testdb;

SET NOCOUNT ON;

DECLARE @name NVARCHAR(MAX),
@sql NVARCHAR(MAX),
@type CHAR(2), -- object type
@type_desc NVARCHAR(60), -- object type description
@params NVARCHAR(MAX) -- parameters

DECLARE @tblInvalid TABLE (
-- invalid objects
[type_desc] NVARCHAR(60),
[name] NVARCHAR(MAX),
[error_number] INT,
[error_message] NVARCHAR(MAX),
[type] CHAR(2)
);

DECLARE testSPs CURSOR FAST_FORWARD
FOR
SELECT [name] = OBJECT_NAME(SM.[object_id]),
[type] = SO.[type],
SO.[type_desc],
[params] = (
SELECT (
SELECT CONVERT(
XML,
(
SELECT STUFF(
(
SELECT ', ' + [name] +
'=NULL' AS
[text()]
FROM sys.parameters
WHERE [object_id] = SM.[object_id]
FOR XML PATH('')
),
1,
1,
''
)
)
)
FOR XML RAW,
TYPE
).value('/row[1]', 'varchar(max)')
)
FROM sys.sql_modules SM
JOIN sys.objects SO
ON SO.[object_id] = SM.[object_id]
WHERE SO.[is_ms_shipped] = 0
AND SO.[type] = 'P'


OPEN testSPs
FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params

WHILE (@@FETCH_STATUS = 0)
BEGIN
BEGIN TRY
SET @sql = 'SET FMTONLY ON; exec ' + @name + ' ' + @params +
'; SET FMTONLY OFF;'
--PRINT @sql;
EXEC (@sql) ;
END TRY
BEGIN CATCH
PRINT @type_desc + ', ' + @name + ', Error: ' + CAST(ERROR_NUMBER() AS VARCHAR)
+ ', ' + ERROR_MESSAGE();
INSERT INTO @tblInvalid
SELECT @type_desc,
@name,
ERROR_NUMBER(),
ERROR_MESSAGE(),
@type

;
END CATCH


FETCH NEXT FROM testSPs INTO @name, @type, @type_desc, @params
END
CLOSE testSPs
DEALLOCATE testSPs


SELECT [type_desc],
[name],
[error_number],
[error_message]
FROM @tblInvalid
ORDER BY
CHARINDEX([type], ' U V PK UQ F TR FN TF P SQ '),
[name];

توضیحات:
این کوئری، در دیتابیس جاری که در قسمت use dbname مشخص می‌شود، تمامی رویه‌های ذخیره شده را به صورت خودکار پیدا می‌کند. سپس لیست آرگومان‌های آن‌ها را نیز یافته و عبارت exec مربوطه را تشکیل می‌دهد. سپس با استفاده از SET FMTONLY ON سعی در شبیه سازی اجرای تک تک رویه‌های ذخیره شده می‌کند. اگر خطایی در این بین رخ داد، آن‌ها را در یک جدول موقتی ذخیره کرده و در آخر نتیجه را نمایش می‌دهد.

ارزش این کوئری زمانی مشخص می‌شود که تعداد زیادی رویه ذخیره شده داشته باشید اما نمی‌دانید کدامیک از آن‌ها بر اساس آخرین تغییرات صورت گرفته، هنوز معتبر هستند یا نه. آیا به قول معروف، سیستم اومد پایین یا خیر!؟

نکته:
قسمتی که از XML استفاده شده جهت concatenating نتیجه حاصل از کوئری، مورد استفاده قرار گرفته و این روزها بحث رایجی است که در بسیاری از سایت‌ها در مورد آن می‌توان مطالب مفیدی را یافت. راه دیگر انجام آن استفاده از COALESCE می‌باشد.


مآخذ:
Check Validity of SQL Server Stored Procedures
Which of your Stored Procedures are no longer Valid
SET FMTONLY ON

مطالب
تبدیل پلاگین‌های jQuery‌ به کنترل‌های ASP.Net

امروز داشتم یک سری از پلاگین‌های jQuery را مرور می‌کردم، مورد زیر به نظرم واقعا حرفه‌ای اومد و کمبود آن هم در بین کنترل‌های استاندارد ASP.Net محسوس است:
Masked Input Plugin
استفاده از آن به صورت معمولی بسیار ساده است. فقط کافی است اسکریپت‌های jQuery و سپس این افزونه به هدر صفحه اضافه شوند و بعد هم مطابق صفحه usage آن عمل کرد.
خیلی هم عالی! ولی این شیوه‌ی متداول کار در ASP.Net نیست. آیا بهتر نیست این مجموعه را تبدیل به یک کنترل کنیم و از این پس به سادگی با استفاده از Toolbox ویژوال استودیو آن‌را به صفحات اضافه کرده و بدون درگیر شدن با دستکاری سورس html صفحه، از آن استفاده کنیم؟ به‌عبارتی دیگر یکبار باید با جزئیات درگیر شد، آنرا بسته بندی کرد و سپس بارها از آن استفاده نمود. (مفاهیم شیءگرایی)

برای این‌کار، یک پروژه جدید ایجاد ASP.Net server control را آغاز نمائید (به نام MaskedEditCtrl).



به صورت پیش فرض یک قالب استاندارد ایجاد خواهد شد که کمی نیاز به اصلاح دارد. نام کلاس را به MaskedEdit تغییر خواهیم داد و همچنین در قسمت ToolboxData نیز نام کنترل را به MaskedEdit ویرایش می‌کنیم.
برای اینکه مجبور نشویم یک کنترل کاملا جدید را از صفر ایجاد کنیم، خواص و توانایی‌های اصلی این کنترل را از TextBox استاندارد به ارث خواهیم برد. بنابراین تا اینجای کار داریم:
namespace MaskedEditCtrl
{
[DefaultProperty("MaskFormula")]
[ToolboxData("<{0}:MaskedEdit runat=server></{0}:MaskedEdit>")]
[Description("MaskedEdit Control")]
public class MaskedEdit : TextBox

{

سپس باید رویداد OnPreRender را تحریف (override) کرده و در آن همان اعمالی را که هنگام افزودن اسکریپت‌ها به صورت دستی انجام می‌دادیم با برنامه نویسی پیاده سازی کنیم. چند نکته ریز در اینجا وجود دارد که در ادامه به آن‌ها اشاره خواهد شد.
از ASP.Net 2.0 به بعد، امکان قرار دادن فایل‌های اسکریپت و یا تصاویر همراه یک کنترل، درون فایل dll آن بدون نیاز به توزیع مجزای آنها به صورت WebResource مهیا شده است. برای این منظور اسکریپت‌های jQuery و افزونه mask edit را به پروژه اضافه نمائید. سپس به قسمت خواص آنها (هر دو اسکریپت) مراجعه کرده و build action آنها را به Embedded Resource تغییر دهید (شکل زیر):



از این پس با کامپایل پروژه، این فایل‌ها به صورت resource به dll ما اضافه خواهند شد. برای تست این مورد می‌توان به برنامه reflector مراجعه کرد (تصویر زیر):



پس از افزودن مقدماتی اسکریپت‌ها و تعریف آنها به صورت resource ، باید آنها را در فایل AssemblyInfo.cs پروژه نیز تعریف کرد (به صورت زیر).

[assembly: WebResource("MaskedEditCtrl.jquery.min.js", "text/javascript")]
[assembly: WebResource("MaskedEditCtrl.jquery.maskedinput-1.1.4.pack.js", "text/javascript")]

نکته مهم: همانطور که ملاحظه می‌کنید نام فضای نام پروژه (namespace) باید به ابتدای اسکریپت‌های معرفی شده اضافه شود.

پس از آن نوبت به افزودن این اسکریپت‌ها به صورت خودکار در هنگام نمایش کنترل است. برای این منظور داریم:
        protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

//adding .js files
if (!Page.ClientScript.IsClientScriptIncludeRegistered("jquery_base"))
{
string scriptUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),
"MaskedEditCtrl.jquery.min.js");
Page.ClientScript.RegisterClientScriptInclude("jquery_base", scriptUrl);
}

if (!Page.ClientScript.IsClientScriptIncludeRegistered("edit_ctrl"))
{
string scriptUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),
"MaskedEditCtrl.jquery.maskedinput-1.1.4.pack.js");
Page.ClientScript.RegisterClientScriptInclude("edit_ctrl", scriptUrl);
}

if (!Page.ClientScript.IsStartupScriptRegistered("MaskStartup" + this.ID))
{
// Form the script to be registered at client side.
StringBuilder sbStartupScript = new StringBuilder();
sbStartupScript.AppendLine("jQuery(function($){");
sbStartupScript.AppendLine("$(\"#" + this.ClientID + "\").mask(\"" + MaskFormula + "\");");
sbStartupScript.AppendLine("});");
Page.ClientScript.RegisterStartupScript(typeof(Page),
"MaskStartup" + this.ID, sbStartupScript.ToString(), true);
}

}

همانطور که ملاحظه می‌کنید، ابتدا WebResource دریافت شده و سپس به صفحه اضافه می‌شود. در آخر مطابق راهنمای افزونه mask edit عمل شد. یعنی اسکریپت مورد نظر را ساخته و به صفحه اضافه کردیم.

نکته جاوا اسکریپتی: علت استفاده از this.ClientID جهت معرفی نام کنترل جاری این است که هنگامیکه کنترل توسط یک master page رندر شود، ID آن توسط موتور ASP.Net کمی تغییر خواهد کرد. برای مثال myTextBox‌ به ctl00_ContentPlaceHolder1_myTextBox تبدیل خواهد شد و اگر صرفا this.ID ذکر شده باشد دیگر دسترسی به آن توسط کدهای جاوا اسکریپت مقدور نخواهد بود. بنابراین از ClientID جهت دریافت ID نهایی رندر شده توسط ASP.Net کمک می‌گیریم.

در اینجا MaskFormula مقداری است که هنگام افزودن کنترل به صفحه می‌توان تعریف کرد.

[Description("MaskedEdit Formula such as 99/99/9999")]
[Bindable(true), Category("MaskedEdit"), DefaultValue(0)]
public string MaskFormula
{
get
{
if (ViewState["MaskFormula"] == null) ViewState["MaskFormula"] = "99/99/9999";
return (string)ViewState["MaskFormula"];
}
set { ViewState["MaskFormula"] = value; }

}

این خاصیت public هنگام نمایش در Visual studio به شکل زیر درخواهد آمد:



نکته مهم: در اینجا حتما باید از view state جهت نگهداری مقدار این خاصیت استفاده کرد تا در حین post back ها مقادیر انتساب داده شده حفظ شوند.

اکنون پروژه را کامپایل کنید. برای افزودن کنترل ایجاد شده به toolbox می‌توان مطابق تصویر عمل کرد:



نکته: برای افزودن آیکون به کنترل (جهت نمایش در نوار ابزار) باید: الف) تصویر مورد نظر از نوع bmp باشد با اندازه 16 در16 pixel . ب) باید آنرا به پروژه افزود و build action آن را به Embedded Resource تغییر داد. سپس آنرا در فایل AssemblyInfo.cs پروژه نیز تعریف کرد (به صورت زیر).

[assembly: System.Web.UI.WebResource("MaskedEditCtrl.MaskedEdit.bmp", "img/bmp")]

کنترل ما پس از افزوده شدن، شکل زیر را خواهد داشت:


جهت دریافت سورس کامل و فایل بایناری این کنترل، اینجا کلیک کنید.


بازخوردهای پروژه‌ها
خطا در اجرای پروژه
سلام و عرض ادب
ممنون از پاسخ
من همونطور که فرمودید دستور رو در کنسول ناگت اجرا کردم در حالیکه پروژه Decision.DataLayer انتخاب بود. نتیجه این شد:
PM> Update-Database
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201510160239047_Initial, 201510182128076_AddCodeToArticle, 201510221124200_AddedScoreProperty, 201512220940192_RemoveUniqueOfTeacherTable, 201512221245065_RemoveConstraint, 201512221428430_removeNotification].
Applying explicit migration: 201510160239047_Initial.
Configuration option 'filestream access level' changed from 2 to 2. Run the RECONFIGURE statement to install.
System.Data.SqlClient.SqlException (0x80131904): FILESTREAM feature is disabled.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.<NonQuery>b__0(DbCommand t, DbCommandInterceptionContext`1 c)
   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
   at System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.NonQuery(DbCommand command, DbCommandInterceptionContext interceptionContext)
   at System.Data.Entity.Internal.InterceptableDbCommand.ExecuteNonQuery()
   at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(MigrationStatement migrationStatement, DbConnection connection, DbTransaction transaction, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(MigrationStatement migrationStatement, DbConnection connection, DbTransaction transaction, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsInternal(IEnumerable`1 migrationStatements, DbConnection connection, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatementsInternal(IEnumerable`1 migrationStatements, DbConnection connection)
   at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClass30.<ExecuteStatements>b__2e()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.<>c__DisplayClass1.<Execute>b__0()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute(Action operation)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements, DbTransaction existingTransaction)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, VersionedModel targetModel, IEnumerable`1 operations, IEnumerable`1 systemOperations, Boolean downgrading, Boolean auto)
   at System.Data.Entity.Migrations.DbMigrator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
   at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.DbMigrator.UpdateInternal(String targetMigration)
   at System.Data.Entity.Migrations.DbMigrator.<>c__DisplayClassc.<Update>b__b()
   at System.Data.Entity.Migrations.DbMigrator.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.EnsureDatabaseExists(Action mustSucceedToKeepDatabase)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.Run()
   at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
   at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
   at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
   at System.Data.Entity.Migrations.Design.ToolingFacade.Update(String targetMigration, Boolean force)
   at System.Data.Entity.Migrations.UpdateDatabaseCommand.<>c__DisplayClass2.<.ctor>b__0()
   at System.Data.Entity.Migrations.MigrationsDomainCommand.Execute(Action command)
ClientConnectionId:ada71343-c349-41c6-8ac0-e9f983f57e85
Error Number:5591,State:3,Class:16
FILESTREAM feature is disabled.
همونطور که در خط آخر می‌بینید اروری وجود داره. و پروژه هم همچنان همون خطای قبل رو در اجرا داره.
مسیرراه‌ها
React 16x
پیش نیاز ها
کامپوننت ها
ترکیب کامپوننت ها
طراحی یک گرید
مسیریابی 
کار با فرم ها
ارتباط با سرور
احراز هویت و اعتبارسنجی کاربران 
React Hooks  
توزیع برنامه

مدیریت پیشرفته‌ی حالت در React با Redux و Mobx   

       Redux
       MobX  

مطالب تکمیلی 
    مطالب
    روش نصب NET SDK. بر روی لینوکس Ubuntu

    اگر از آخرین نگارش Ubuntu استفاده می‌کنید، با توجه به همکاری مایکروسافت و شرکت پشتیبان آن، نصب دات‌نت به سادگی اجرای دستور زیر است:

    $ sudo apt update && sudo apt install -y dotnet-sdk-8.0
    $ dotnet --version

    و اگر می‌خواهید بدانید که چه ‌نگارشی از دات‌نت، به‌همراه مخازن استاندارد Ubuntu است، دستور زیر را صادر کنید:

    $ apt search dotnet-sdk*

    که یک نمونه خروجی آن به صورت زیر است:

    $ apt search dotnet-sdk*
    Sorting... Done
    Full Text Search... Done
    dotnet-sdk-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      .NET 8.0 Software Development Kit
    
    dotnet-sdk-8.0-source-built-artifacts/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      Internal package for building the .NET 8.0 Software Development Kit
    
    dotnet-sdk-dbg-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      .NET SDK debug symbols.

    نصب اسکریپتی آخرین نگارش دات‌نت بر روی تمام توزیع‌های لینوکسی

    مایکروسافت، اسکریپتی را برای دریافت و نصب خودکار آخرین نگارش دات‌نت، تهیه کرده‌است که کار با آن بسیار ساده‌است و با تمام توزیع‌های لینوکسی و نه فقط Ubuntu سازگار است.

    پیش از هرکاری ابتدا مخزن‌های بسته‌ها و برنامه‌های مرتبط را یکبار به‌روز کرده و سیستم را ری‌استارت می‌کنیم:

    sudo apt update -q && sudo apt upgrade -y && reboot

    سپس دستور زیر را صادر می‌کنیم تا اسکریپت نصاب مخصوص دات‌نت خود مایکروسافت را دریافت کنیم :

    wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
    chmod +x ./dotnet-install.sh

    توسط این دو دستور، فایل dotnet-install.sh دریافت شده و همچنین دسترسی اجرایی بودن آن نیز تنظیم می‌شود.

    پس از آن، برای نصب آخرین نگارش دات‌نت SDK موجود، تنها کافی است دستور زیر را صادر کنیم:

    ./dotnet-install.sh --version latest

    و یا اگر فقط می‌خواهید runtime آن‌را نصب کنید، پارامترهای نصب، به صورت زیر تغییر می‌کنند:

    ./dotnet-install.sh --version latest --runtime aspnetcore

    همچنین اگر نگارش‌های پایین‌تر مدنظر شما هستند، می‌توانید کانال نصب را هم مشخص کنید:

    ./dotnet-install.sh --channel 7.0

    که یک نمونه خروجی اجرای دستور dotnet-install.sh --version latest/. آن به صورت زیر است:

    $ ./dotnet-install.sh --version latest
    dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz
    dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz size is 223236164 bytes.
    dotnet-install: Extracting archive from https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz
    dotnet-install: Downloaded file size is 223236164 bytes.
    dotnet-install: The remote and local file sizes are equal.
    dotnet-install: Installed version is 8.0.303
    dotnet-install: Adding to current process PATH: `/home/vahid/.dotnet`. Note: This change will be visible only when sourcing script.
    dotnet-install: Note that the script does not resolve dependencies during installation.
    dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.
    dotnet-install: Installation finished successfully.

    همانطور که مشاهده می‌کنید، دات‌نت 8.0.303، نصب شده‌است.

    پس از پایان نصب، دو دستور زیر را هم باید اجرا کنید تا بتوان در خط فرمان به NET CLI. دسترسی یافت:

    $ export DOTNET_ROOT=$HOME/.dotnet
    $ export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools

    پس از این تنظیمات، امکان اجرای دو دستور آزمایشی زیر میسر می‌شوند:

    $ dotnet --version
    $ dotnet --list-sdks

    نصب دات‌نت بر روی نگارش‌های قدیمی‌تر Ubuntu

    برای اینکه بتوان به روش متداولی (بدون دریافت اسکریپت نصاب فوق) به آخرین تغییرات انجام شده و آخرین به‌روز رسانی‌ها و همچنین تمام نگارش‌های دات‌نت دسترسی داشت، می‌توان از مخازن بسته‌های خود مایکروسافت استفاده کنیم که باید آن‌ها را به سیستم عامل اضافه کرد. برای اینکار باید مراحل زیر طی شوند:

    ابتدا تمام وابستگی‌های احتمالی موجود را حذف می‌کنیم:

    $ sudo apt remove 'dotnet*' 'aspnet*' 'netstandard*'

    سپس فایل زیر را ایجاد کرده:

    sudo nano touch /etc/apt/preferences

    و آن‌را با محتوای زیر تکمیل می‌کنیم؛ تا فقط از مخازن خود مایکروسافت استفاده شود و سایر مخازن مرتبط در این حالت، اولویت کمتری داشته باشند:

    Package: dotnet* aspnet* netstandard*
    Pin: origin "archive.ubuntu.com"
    Pin-Priority: -10
    
    Package: dotnet* aspnet* netstandard*
    Pin: origin "security.ubuntu.com"
    Pin-Priority: -10

    پس از آن، دستورات زیر، کار افزودن مخازن بسته‌های مایکروسافت را انجام می‌دهند:

    # Get OS version info which adds the $ID and $VERSION_ID variables
    source /etc/os-release
    
    # Download Microsoft signing key and repository
    wget https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
    
    # Install Microsoft signing key and repository
    sudo dpkg -i packages-microsoft-prod.deb
    
    # Clean up
    rm packages-microsoft-prod.deb
    
    # Update packages
    sudo apt update
    sudo apt upgrade -y

    بعد از این به‌روز رسانی‌ها، دستور متداول زیر، کار نصب آخرین نگارش NET SDK. را انجام می‌دهد:

    sudo apt-get install -y dotnet-sdk-8.0

    و همچنین هربار هم که سیستم را با دستورات sudo apt update -q && sudo apt upgrade -y به روز کنیم، در صورت وجود به‌روز رسانی دات‌نتی جدیدی، آن‌را به صورت خودکار دریافت و نصب می‌کند.

    مطالب
    جلوگیری از ورود همزمان کاربران با نام کاربری و رمز عبور یکسان
    در اکثر برنامه‌های وب، کاربر قادر است با یک نام کاربری و رمز عبور در چند Session همزمان لاگین کند. ممکن است سیاست برخی مدیران محصول این باشد که جلوی این مورد را بگیرند تا به عنوان مثال کاربران را به جای استفاده‌ی همزمان از یک نام کاربری و رمز عبور، مجبور به خرید مجوز‌های بیشتری کنند. ASP.NET Identity به صورت پیش فرض این مورد را پشتیبانی نمی‌کند؛ اما به کمک استفاده از امکانات درونی آن می‌توان این پشتیبانی را اضافه کرد.
    یکی از فیلد‌های جدول AspNetUsers فیلد SecurityStamp می‌باشد. SecurityStamp یک مقدار تصادفی است:


    Security Stamp باید با هربار تغییر اطلاعات احراز هویت (مانند رمز عبور) و اختیارات کاربر(Role) تغییر کند. به عنوان مثال کاربری در چند مرورگر لاگین کرده و گزینه‌ی مرا به خاطر داشته باش را انتخاب کرده است. اگر این کاربر رمز عبورش از هر جایی عوض شود، باید لاگین او در همه‌ی Session‌ها غیر معتبر شود. این مورد با تغییر کردن SecurityStamp بعد از تغییر رمز عبور صورت می‌گیرد. ASP.NET مقدار SecurityStamp را در کوکی کاربر نگه می‌دارد و در بازه‌های زمانی، این مقدار را با مقدار درون دیتابیس مقایسه می‌کند و در صورت عدم برابری، کاربر را احراز هویت نمی‌کند. بازه‌ی زمانی این بررسی در متد ConfigureAuth قابل تنظیم است که در ادامه شرح داده خواهد شد.
    صورت مساله یافتن راه حلی جهت جلوگیری از ورود همزمان چند کاربر با یک نام کاربری و رمز عبور به سیستم می‌باشد. یکی از راه‌حل هایی که در ابتدا به ذهن می‌آید استفاده از Session و نگهداری کاربران لاگین کرده در حافظه می‌باشد. پیاده سازی این راه حل می‌تواند به کمک یک کلاس Static صورت پذیرد، اما قسمت چالشی این موضوع این است که چه زمانی باید کاربر از لیست حذف گردد؟ اگر اتصال کاربر قطع شود چه عملی باید صورت گیرد؟
    راه حل دیگر استفاده از SecurityStamp هست؛ به این صورت که با هربار لاگین کاربر این مقدار تصادفی به‌روز گردد و ASP.NET Identity به گونه‌ای تنظیم شود که با هر درخواست HTTP، صحت SecurityStamp بررسی گردد. مقدار پیش فرض بازه‌ی زمانی بررسی، هر 30 دقیقه یک بار است.
    در مثال‌های رسمی ASP.NET Identity لاگین به صورت ذیل پیاده سازی شده است:
       
            [HttpPost]
            [AllowAnonymous]
            [ValidateAntiForgeryToken]
            public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
            {
                if (!ModelState.IsValid)
                {
                    return View(model);
                }
                // This doesn't count login failures towards account lockout
                // To enable password failures to trigger account lockout, change to shouldLockout: true
                var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
                switch (result)
                {
                    case SignInStatus.Success:
                        return RedirectToLocal(returnUrl);
                    case SignInStatus.LockedOut:
                        return View("Lockout");
                    case SignInStatus.RequiresVerification:
                        return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
                    case SignInStatus.Failure:
                    default:
                        ModelState.AddModelError("", "Invalid login attempt.");
                        return View(model);
                }
            }
    این کد را باید به گونه‌ای تغییر داد که اگر نام کاربری و رمز عبور معتبر بودند، مقدار SeucrityStamp به‌روز گردد. به همین منظور قبل از فراخوانی PasswordSignInAsync کد ذیل اضافه می‌گردد:
           var loggedinUser = await UserManager.FindAsync(model.Email, model.Password);
                if (loggedinUser != null)
                {
                    await UserManager.UpdateSecurityStampAsync(loggedinUser.Id);
                }
    همانطور که مشاهده می‌شود، جهت بروز رسانی SecurityStamp از سازوکار درونی ASP.NET Identity، در واقع متد UpdateSecurityStampAsync بهره گرفته شده است.
    اکنون باید تنظیمات پیش‌فرض بازه‌ی زمانی بررسی صحت SecurityStamp تغییر داده شود. این تنظیمات در فایل Startup.Auth.cs در پوشه‌ی App_Start قرار دارند:
         app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    LoginPath = new PathString("/Account/Login"),
                    Provider = new CookieAuthenticationProvider
                    {
                        // Enables the application to validate the security stamp when the user logs in.
                        // This is a security feature which is used when you change a password or add an external login to your account.  
                        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                    }
                });
    در کد بالا OnValidateIdentity باید مقدار ذیل را بگیرد:
    OnValidateIdentity =
                            SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                                TimeSpan.FromMinutes(0), // <-- Note the timer is set for zero
                                (manager, user) => user.GenerateUserIdentityAsync(manager))
    اکنون Framework موظف است در هر درخواست HTTP، صحت SeucirtyStamp را بررسی کند. بنابراین اگر کاربری در سیستم لاگین باشد و کاربر دیگری با همان نام کاربری و رمز عبور لاگین کند، کاربر اول از سیستم لاگ اوت می‌شود؛ چرا که مقدار SecurityStamp او دیگر معتبر نیست. باید در نظر گرفته شود این عمل در سیستم‌های با تعداد کاربر زیاد باعث افزایش درخواست‌های دیتابیس می‌شود. 
    اکنون جهت تست، اگر با مرورگر اول در سیستم لاگین صورت گیرد، سپس با همان اطلاعات در مرورگری دیگر، لاگین صورت گیرد، کاربر اول پس از درخواست بعدی، از سیستم لاگ اوت می‌شود. در مثال انتهای مطلب، صفحه‌ی About به صورت غیر عمومی درآمده که می‌توان بررسی راه حل جاری را در آن صفحه صورت داد. 
    اگر بخواهیم لاگ اوت شدن کاربر را آنی کنیم، می‌توان در فواصل زمانی مشخصی، یک درخواست Ajax از سمت کلاینت به سرور ارسال کرد و تصدیق هویت کاربر را بررسی کرد:
            window.setInterval(function() {
                $.ajax({
                    url:,
                    type: "Post",
                    dataType: "json",
                    success: function(data) {
                        if (!data) {
                            alert("Someone has logged in using your username and password!");
                            location.reload();
                        }
                    }
                });
            }, 60000);
    در کد بالا به کمک window.setInterval، هر یک دقیقه یک بار لاگین بودن کاربر بررسی می‌گردد و در صورت لاگین نبودن، پیغام لازم به کاربر نمایش داده می‌شود. در نظر داشته باشید، این کد تنها باید در صفحات غیر عمومی قرار داده شود.
    کد اکشن بررسی لاگین بودن به سادگی ذیل است:
            [AllowAnonymous]
            public virtual ActionResult Authenticated()
            {
                return Json(User.Identity.IsAuthenticated);
            }

    نکته‌ی مهم این است که خصیصه‌ی AllowAnonymous بالای اکشن قرار گرفته باشد تا در صورتیکه Controller به صورت عمومی در دسترس نیست، اکشن همیشه و حتی وقتی کاربر لاگ اوت شده در دسترس باشد. در مثال انتهای مطلب صفحه‌ی About تنها در اختیار کاربران احراز هویت شده قرار گرفته است، بنابراین اگر دو کاربر با اطلاعات یکسانی به سیستم لاگین کنند، کاربر اول پیغام خطای ذیل را گرفته و به صفحه‌ی لاگین می‌رود. این کد در صفحه‌ی About در مثال انتهای مطلب قرار گرفته است:

    مطالب
    اجزاء معماری سیستم عامل اندروید :: بخش دوم
    در مطلب قبلی در مورد سه ویژگی اصلی معماری اندروید توضیحاتی ارائه شد و در این مطلب ویژگی آخر از این معماری را توضیح خواهم داد:

    Applications در معماری اندروید چه کاربردی دارد؟
    اجزای یک اپلیکیشن در پلتفرم اندروید جزء اصلی ارائه به کاربر نهایی می‌باشد؛ بدین معنا که کاربر تنها با برنامه در ارتباط است و سیستم عامل، میزبان آن برنامه یا اپلیکیشن خواهد بود. اپلیکیشن جاییست که لیست تماس‌ها، شماره تلفن‌ها، پیام‌های کاربر و ... در آنجا قرار می‌گیرند و با دیگر اجزای نرم افزاری در ارتباط هستند!
    بعنوان یک توسعه دهنده اندروید، محصول نهایی، در قالب یک اپلیکیشن با استفاده از API‌ها و کتابخانه‌ها و همچنین ماشین مجازی دال‌ویک اجرا می‌شوند. اگر شما بتوانید تغییراتی را در سیستم عامل اصلاح و ویرایش کنید، تنها در سطوح لایه‌های نرم افزاری اعمال میشود و شما بر روی امنیت برنامه یا اپلیکیشن درون هسته دسترسی لازم را ندارید و این یک معضل است و باید در لایه‌های اولیه برنامه، امنیت را بر روی برنامه اعمال کنید.
    با این وجود اگر هسته یا دستگاه آسیب ببیند و مورد سوءاستفاده قرار بگیرد کاری از دست شما برنمی آید!


    Security به معنای ایمنی یا امنیت در اندروید به چه معناست؟
    ایمنی یا امنیت در اندروید، موضوع بسیار وسیعی است که در ابعاد مختلف این پلتفرم قابل بحث است. اول اجازه دهید هویت شما را تشخیص دهیم. آیا شما یک توسعه دهنده هستید؟ یا شاید شما یک کاربر عادی هستید که به حفاظت از خودتان از یک حمله اینترنتی علاقمندید! در هر صورت، شما در حال نوشتن یک برنامه هستید که به وسیله یک نفر دیگر و یا احتمالا هزاران نفر در هزاران مایل دورتر نصب خواهد شد.


    از کاربر خود در یک برنامه محافظت کنید!
    برنامه شما باید تلاش کند تا بهترین عملکرد ممکن را در زمان ارائه محصول نهایی داشته باشد. از داده‌های کاربران خود محافظت کنید، یعنی قبل از اینکه شروع به توسعه کنید، درباره امنیت محصول خود فکر کنید. ممکن است کاربری که در هزاران مایل دورتر از شما قرار دارد در خصوص امنیتی که شما بر روی برنامه خود اعمال کرده‌اید اطلاعاتی نداشته باشد و شما شاید امنیت داده‌های او را نقض کنید که این معادل عدم اطمینان همان شخص نسبت به شما خواهد بود! برنامه‌ریزی درباره امنیت، پیش از توسعه یک محصول می‌تواند باعث شود که شما از بررسی‌های بد و از دست دادن ضررهای بعد از آن جلوگیری کنید. پس یک برنامه برای حفاظت از داده‌های درون اپلیکیشن خود ایجاد کنید! برنامه‌ای که هم کاربردی باشد، هم از اطلاعات کاربران محافظت نماید.


    خطرات امنیتی (Security Risks)  
    کاربران دستگاه‌های تلفن همراه در مقایسه با کاربران دسکتاپ، با برخی مخاطرات منحصر به فردی مواجه هستند که نباید نادیده گرفته شود. صرف‌نظر از امکان از دست دادن تجهیزات سخت افزاری، اطلاعات، حریم خصوصی و داده‌های محرمانه شخصی کاربران دستگاه تلفن همراه را به خطر می‌اندازند. چرا این موضوع در پلتفرم جامع اندروید تا این حد پر اهمیت است؟ آیا شما بعنوان توسعه دهنده به این نکات دقت داشته‌اید؟
    اول اینکه، کیفیت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه کاربر بیشتر شخصی می‌شود تا مواردی دیگر! به غیر از ایمیل، پیام‌های فوری، SMS / MMS ، لاگ تماس‌ها، عکس‌ها و پست صوتی وجود دارند که عموما توسعه دهندگان را دچار مشکل می‌کند. 

    برخی از گزینه‌های فوق بر روی یک کامپیوتر رومیزی هم وجود دارند، ولی اهمیت این داده‌ها بر روی اندروید و اجزای آن اهمیت فوق العاده‌ای دارد. اطلاعات روی دستگاه موبایل شما به احتمال زیاد از ارزش بیشتری برخوردار خواهد بود، چرا که آن‌ها را در یک صفحه 4 - 5 اینچی به همراه خود حمل می‌کنید و با خود هر کجا می‌برید! این حالت، یک پلتفرم همگرا را بوجود می‌آورد؛ به این دلیل که سیستم رومیزی شما و تلفن همراه یک مجموعه غنی و کامل از اطلاعات حساس هستند که هردوی آنها شامل اطلاعات شخصی می‌باشند و برای شما اهمیت زیادی خواهند داشت. تصور کنید زمانیکه برای جلوگیری از نفوذ یا به سرقت رفتن شماره تلفن‌های خود، یک پشتیبان بر روی سیستم رو میزی خود تهیه می‌کنید و فایل پشتیبان شماره‌های تماس را بر روی سیستم شخصی نگه داری می‌کنید! آیا این همان پلتفرم همگرا نیست؟ آیا این دو سیستم مکمل هم نیستند؟حتی اگر همگام‌سازی را با یک مکان دوردست (Google Drive) انجام دهید، با این حال شما فقط در مقابل از دست دادن داده‌ها محافظت کرده‌اید و نه از دست دادن حریم خصوصی! 

    همچنین در نظر بگیرید که فرمت داده‌های ذخیره‌شده در دستگاه‌های تلفن همراه، تعیین و مشخص شوند! این کار اطلاعات حساس شما را به مرز سرقت نزدیکتر می‌کند. هر تلفن همراه SMS / MMS ، تماس‌ها، و پست صوتی خواهد داشت. مکان‌های ذخیره شده از روی GPS و مواردی دیگر که قطعا اطلاع دارید، تمامی اینها جزء مواردی هستند که خطرات امنیتی را در سیستم عامل اندروید شامل می‌شود. حالا در نظر بگیرید که این اطلاعات تا چه حد مهم است؟ برای کاربرانی که هیچ گونه پشتیبانی از اطلاعاتی از خود ندارند، از دست دادن داده‌ها قابل تصور نیست!

    خطرناکترین نوع حملات بر روی پلتفرم اندروید انجام می‌شوند، در سکوت کامل و چندین هزار مایل دروتر از شما و فرد مهاجم نیازی به دسترسی فیزیکی و لمس تلفن همراه شما نخواهد داشت! این نوع حملات در هر زمانی ممکن است رخ دهد و اغلب می‌تواند به دلیل امنیت ضعیف در جای دیگری بر روی دستگاه رخ دهد.

    در مطلب بعدی پیرامون امنیت معماری اندروید صبحت خواهیم کرد...

    مطالب
    تنظیم رشته اتصالی Entity Framework به بانک اطلاعاتی به وسیله کد
    در زمان ساخت مدل از بانک اطلاعاتی در روش Database First به صورت پیش فرض تنظیمات مربوط به اتصال (Connection String) مدل به بانک اطلاعاتی در فایل config برنامه ذخیره می‌شود. مشکل این روش آن است که در سیستم‌های مختلف، بسته به بستری که نرم افزار قرار است بر روی آن اجرا شود، باید تنظیمات مربوط به بانک اطلاعاتی صورت گیرد.
    مثلا فرض کنید شما در زمان توسعه نرم افزار، SQL Server را به صورت Local بر روی سیستم خود نصب کرده اید و Connection String ساخته شده توسط ویزارد Entity Framework بر همین اساس ساخته و ذخیره شده‌است. حال بعد از انتشار برنامه، شخصی تصمیم دارد برنامه را بر روی سیستمی نصب کند که بانک اطلاعاتی Local نداشته و تصمیم به اتصال به یک بانک اطلاعاتی بر روی سرور دیگر یا با مشخصات (Login و Password و ...) دیگر را دارد. برای این مواقع نیاز به پیاده سازی روشی است تا کاربر نهایی بتواند تنظیمات مربوط به اتصال به بانک اطلاعاتی را تغییر دهد.
    روش‌های مختلفی مثل تغییر فایل app.config به صورت Runtime یا ... در سایت‌های مختلف ارائه شده که اکثرا روش‌های غیر اصولی و زمانبری جهت پیاده سازی هستند.
    ساده‌ترین روش جهت انجام این کار، اعمال تغییری کوچک در Constructor کلاس مدل مشتق شده از DBContext می‌باشد. فرض کنید مدلی از بانک اطلاعاتی Personnely با نام PersonallyEntities ساخته اید که حاصل آن کلاس زیر خواهد بود:
        public partial class PersonallyEntities : DbContext
        {
            public PersonallyEntities()
                : base("name=PersonallyEntities")
            {
            }
        }
    همانطور که مشاهده می‌کنید، در Constructor این کلاس، نام Connection String مورد استفاده جهت اتصال به بانک اطلاعاتی به صورت زیر آورده شده که به Connection String ذخیره شده در فایل Config اشاره می‌کند:
    "name=PersonallyEntities"
    اگر به Connection String ذخیره شده در فایل Config دقت کنید متوجه می‌شوید که Connection String ذخیره شده، دارای فرمتی خاص و متفاوتی نسبت به Connection String معمولی ADO.NET است. متن ذخیره شده شامل تنظیمات و Metadata مدل ساخته شده جهت ارتباط با بانک اطلاعاتی نیز می‌باشد:
     metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=Personally;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
    جهت تولید پویای Connection String، بسته به تنظیمات کاربر، نیاز است تا در آخر Connection String ی با فرمت بالا در اختیار Entity Framework قرار دهیم تا امکان اتصال به بانک فراهم شود. جهت تبدیل Connection String معمول ADO.NET به Connection String قابل فهم EF میتوان از کلاس EntityConnectionStringBuilder به صورت زیر استفاده کرد:
            public static string BuildEntityConnection(string connectionString)
            {
                var entityConnection = new EntityConnectionStringBuilder
                {
                    Provider = "System.Data.SqlClient",
                    ProviderConnectionString = connectionString,
                    Metadata = "res://*"
                };
    
                return entityConnection.ToString();
            }
    همانطور که مشاهده می‌کنید، متد بالا با دریافت یک connectionString که همان ADO.NET ConnectionString ما می‌باشد، تنظیمات و Metadata مورد نیاز Entity Framework را به آن اضافه کرده و یک EF ConnectionString برمی‌گرداند.
    برای اینکه بتوان EF ConnectionString تولید شده را در هنگام اجرای برنامه به صورت Runtime اعمال کرد، نیاز است تا تغییر کوچکی در Constructor کلاس مدل تولید شده توسط Entity Framework ایجاد کرد. کلاس PersonnelyEntities به صورت زیر تغییر پیدا می‌کند:

        public partial class PersonallyEntities : DbContext
        {
            public PersonallyEntities(string connectionString)
                : base(connectionString)
            {
    
            }
        }
    با اضافه شدن پارامتر connectionString به سازنده کلاس PersonnelyEntities برای ساخت یک نمونه از مدل ساخته شده در کد نیاز است تا Connection String مورد نظر جهت برقراری ارتباط با بانک را به عنوان پارامتر، به متد سازنده پاس دهیم. سپس مقدار این پارامتر به کلاس والد ( DbContext ) جهت برقراری ارتباط با بانک اطلاعاتی ارجاع داده شده: 
    : base(connectionString)
    در آخر به صورت زیر میتوان توسط EF به بانک اطلاعاتی مورد نظر متصل شد :
    var entityConnectionString = BuildeEntityConnection("Data Source=localhost;Initial Catalog=Personally; Integrated Security=True");
    var PersonallyDb = new PersonallyEntities(entityConnectionString);
    با این روش میتوان ADO Connection String مربوط به اتصال بانک اطلاعاتی را به راحتی به صورت داینامیک به وسیله اطلاعات وارد شده توسط کاربر و کلاس‌های تولید Connection String نظیر SQLConnectionStringBuilder تولید کرد و بدون تغییر در کد‌های برنامه، به بانک‌های مختلفی متصل شد. همچنین با داینامیک کردن متد Provider کلاس EntityConnectionStringBuilder که در کد بالا با "System.Data.SqlClient" مقدار دهی شده، می‌توان وابستگی برنامه بانک اطلاعی خاص را از بین برد و بسته به تنظیمات مورد نظر کاربر، به موتورهای مختلف بانک اطلاعاتی متصل شد که البته لازمه این کار رعایت یکسری نکات فنی در پیاده سازی پروژه است که از حوصله این مقاله خارج است.
    موفق باشید
    مطالب
    برنامه نویسی پیشرفته JavaScript - قسمت 2 - ماهیت انواع داده‌ای و حوزه دسترسی به متغیرها

    مقادیر پایه (Primitive Values) و ارجاعی (Reference Values)

    در جاوا اسکریپت، متغیرها شامل داده‌هایی از نوع پایه و یا ارجاعی می‌باشند. مقادیر String ، Number ، Boolean ، Null و Undefined به عنوان مقادیر پایه محسوب می‌گردند. در این نوع متغیرها تغییرات، مستقیما بر روی مقدار ذخیره شده در متغیر اعمال می‌شوند. اشیاء نیز که از مجموعه‌ای از مقادیر پایه ساخته شده‌اند، مقادیر ارجاعی نامیده می‌شوند. در این نوع مقادیر، شما به اشاره‌گری از شیء دسترسی دارید. بنابراین تغییرات مستقیما بر روی داده‌های ذخیره شده اعمال نمی‌شوند. به مثال‌های زیر توجه کنید:

    var num1 = 10;
    var num2 = num1;
    alert("Num1=" + num1 + ", Num2=" + num2);
    
    num2 = 20;
    alert("Num1=" + num1 + ", Num2=" + num2);
    
    num1 = 30;
    alert("Num1=" + num1 + ", Num2=" + num2);

    خروجی :

    "Num1=10, Num2=10"

    "Num1=10, Num2=20"

    "Num1=30, Num2=20"


    همانطور که از خروجی مثال فوق پیداست، در انتساب num1 به num2 ، مقدار num1 در num2 کپی شده‌است. بنابراین تغییراتی که بر روی num1 یا num2 صورت می‌گیرد، مستقیما بر روی مقدار ذخیره شده در هر یک از این متغیرها تاثیر می‌گذارد. رفتار مقادیر پایه همیشه به همین صورت می‌باشد.

    var obj1 = new Object();
    obj1.num = 10;
    var obj2 = obj1;
    alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num);
    
    obj2.num = 20;
    alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num);
    
    obj1.num = 30;
    alert("Obj1.Num=" + obj1.num + ", Obj2.Num=" + obj2.num);

    خروجی :

    "Obj1.Num=10, Obj2.Num=10"

    "Obj1.Num=20, Obj2.Num=20"

    "Obj1.Num=30, Obj2.Num=30"


    با استفاده از نوع ارجاعی Object می‌توانیم اشیاء جدیدی را ایجاد کنیم و ویژگی‌هایی را به صورت پویا به آن‌ها اختصاص دهیم. همانطور که قبلا گفته شد، اشیاء از نوع ارجاعی می‌باشند و حاوی اشاره‌گری به مقادیر ذخیره شده می‌باشند. بنابراین انتساب obj1 به obj2 به معنای انتساب اشاره‌گر obj1 به obj2 می‌باشد. به عبارتی دیگر obj2 به همانجایی اشاره می‌کند که obj1 نیز اشاره می‌نماید. پس هر تغییری که بر روی ویژگی‌های obj1 رخ دهد، obj2 نیز تاثیرات آن را می‌بیند و بالعکس. همانطور که در خروجی مشاهده می‌نمایید، در مرحله‌ی اول obj1 به obj2 نسبت داده شد، پس مقدار ویژگی num برای هر دو آنها یکسان میباشد. در مرحله‌ی دوم، مقدار ویژگی num را در obj2 تغییر دادیم؛ ولی مقدار این ویژگی، در obj1 نیز تغییر نمود. در مرحله‌ی سوم نیز همان اتفاقات مرحله‌ی دوم تکرار شد.

    با توجه به مثالهای فوق قطعا به تفاوت‌های مقادیر پایه و ارجاعی پی بردید. همچنین در یک نمونه‌ی کوچک و ساده نیز، یکی از روش‌های ایجاد شیء را که استفاده از نوع ارجاعی Object می‌باشد، مشاهده نمودید. این دانسته‌ها مقدمه ای بر شروع برنامه نویسی شیء گرا می‌باشند. ولی قبل از شروع برنامه نویسی شیء گرا در جاوا اسکریپت، به بررسی نکات و تکنیک‌های دیگری می‌پردازیم.

    توجه:

    به انواع پایه، انواع داده‌ای مقداری یا اولیه نیز گفته می‌شود.


    فراخوانی با مقدار (Call by Value)

    در این نوع فراخوانی، آرگومان‌ها از نوع مقادیر پایه هستند. بنابراین هر تغییری که بر روی آرگومان‌ها در تابع رخ دهد، هیچ تاثیری بر روی آرگومان‌های ارسالی در زمان فراخوانی تابع ندارند. به مثال زیر توجه کنید:

    function primitive(a, b) {
    a += 100;
    b += 200;
    alert("a=" + a + ", b=" + b);
    }
    
    var x = 300, y = 400;
    primitive(x, y);
    alert("x=" + x + ", y=" + y);

    خروجی :

    "a=400, b=600"

    "x=300, y=400"


    x و y دو متغیر پایه می‌باشند، بنابراین تابع فوق به صورت مقداری فراخوانی شده‌است. یعنی مقدار آرگومان‌های x و y در آرگومان‌های a و b کپی می‌شوند. پس هر تغییری که بر روی a و b رخ دهد، هیچ تاثیری بر روی x و y ندارد. همچنین با توجه به توضیحی که در مورد مقادیر پایه داده شد، تغییرات مستقیما بر روی داده‌ی ذخیره شده در متغیر اعمال میشود. بنابراین تغییراتی که در تابع فوق بر روی a و b رخ داد، مستقیما مقادیر a و b را تغییر داده‌است وهیچ ارتباطی به x و y ندارد. البته توجه داشته باشید که حتی اگر نام آرگومان‌های تابع با آرگومان‌های ارسالی یکسان بود و یا حتی اگر تابع مقداری را به عنوان خروجی بر می‌گرداند، هیچ تفاوتی را در خروجی برنامه فوق مشاهده نمی‌کردید.


    فراخوانی با ارجاع (Call by Reference)

    در این نوع فراخوانی، آرگومان‌ها از نوع مقادیر ارجاعی هستند. بنابراین هر تغییری که بر روی آرگومان‌ها در تابع رخ دهد، بر روی آرگومان‌های ارسالی در زمان فراخوانی تابع نیز تاثیر می‌گذارند. به مثال زیر توجه کنید:

    function reference(obj) {
    obj.a += 100;
    obj.b += 200;
    alert("obj.a=" + obj.a + ", obj.b=" + obj.b);
    }
    
    var calc = new Object();
    calc.a = 300;
    calc.b = 400;
    
    reference(calc);
    alert("calc.a=" + calc.a + ", calc.b=" + calc.b);

    خروجی :

    "obj.a=400, obj.b=600"

    "calc.a=400, calc.b=600"


    calc یک مقدار ارجاعی است که به عنوان آرگومان ورودی به تابع ارسال می‌شود و اشاره‌گر خود را به obj اختصاص می‌دهد. بنابراین obj به همان آدرسی اشاره می‌کند که calc اشاره می‌نماید. پس هر تغییری که بر روی obj رخ دهد، calc نیز تاثیرات آن را مشاهده می‌نماید. همانطور که در خروجی نیز مشاهده می‌نمایید، تغییرات صورت گرفته در تابع به calc نیز منعکس شده است.


    حوزه دسترسی به متغیرها (Variable Scope)

    متغیر‌های محلی (Local Variables)

    در اکثر زبان‌های برنامه نویسی، متغیرهایی که در یک بلاک کد تعریف می‌شوند، به صورت محلی و فقط در همان بلاک کد قابل دسترسی  می‌باشند. به این متغیرها، متغیرهای محلی می‌گویند که با خاتمه‌ی اجرای بلاک کد، از بین خواهند رفت و دیگر قابل دسترسی نمی‌باشند. اما در جاوا اسکریپت حوزه‌ی اجرایی متغیرها کمی متفاوت می‌باشد. به مثال زیر توجه کنید:

    for (var i = 1; i <= 5; i++) {
    var sqr = i * i;
    alert(sqr);
    }
    
    alert(i);
    alert(sqr);

    خروجی :

    1
    4
    9
    16
    25
    6   // alert(i) out of for
    25   // alert(sqr) out of for


    متغیرهای i و sqr داخل حلقه‌ی for تعریف شده‌اند و منطقا نباید خارج از این حلقه قابل دسترسی باشند. ولی با توجه به خروجی فوق، مشاهده نمودید که متغیرهای i و sqr، نه تنها خارج از این حلقه قابل شناسایی می‌باشند، بلکه آخرین مقدار خود را نیز حفظ نموده‌اند. در جاوا اسکریپت، یک متغیر محلی زمانی مفهوم پیدا می‌کند که در داخل یک تابع تعریف شود. به مثال زیر توجه کنید:

    function sqr(num) {
    var sum = num * num;
    return sum;
    }
    
    var n = 4;
    alert(sqr(n));
    alert(num); // Error: num is not defined
    alert(sum); // Error: sum is not defined

    خروجی :

    16

    Error: num is not defined

    Error: sum is not defined

    همانطور که مشاهده می‌کنید، متغیرهای num و sum به صورت محلی در تابع فوق تعریف شده‌اند؛ بنابراین خارج از تابع قابل دسترسی نمی‌باشند و موجب بروز خطا می‌گردند.


    متغیرهای عمومی (Global Variables)

    در جاوا اسکریپت، متغیرهایی که خارج از تابع تعریف می‌گردند، به شیء window نسبت داده می‌شوند و عمومی می‌باشند. به عبارتی دیگر این متغیرها به عنوان یک ویژگی از شیء window تعریف می‌شوند و در تمامی توابع و بلاک‌های کد قابل دسترسی می‌باشند. به مثال زیر توجه کنید:

    var color = "Red";
    
    function setColor() {
    color = "Blue";
    }
    
    alert(color);
    setColor();
    alert(color);

    خروجی :

    "Red"

    "Blue"


    در مثال فوق، متغیر color به صورت عمومی تعریف شده‌است. بنابراین در کل برنامه قابل دسترسی می‌باشد. در alert اول مقدار فعلی متغیر color یعنی “Red” نمایش می‌یابد. سپس با فراخوانی تابع، مقدار این متغیر تغییر می‌کند. در alert دوم مقدار تغییر یافته‌ی متغیر color نمایش خواهد یافت. حال به مثال زیر توجه کنید:

    var color = "Red";
    
    function getColor() {
    var color = "Blue";
    return color;
    }
    
    alert(color);
    alert(getColor());
    alert(color);

    خروجی :

    "Red"

    "Blue"

    "Red"


    در مثال فوق، ابتدا یک متغیر color به صورت عمومی یا Global تعریف شده است. در تابع getColor نیز یک متغیر color به صورت local یا محلی تعریف شده است. زمانی که در alert تابع getColor فراخوانی می‌شود، متغیر color مقداردهی می‌گردد. این مقداردهی برای متغیر محلی صورت گرفته است و هیچ ربطی به متغیر color که به صورت عمومی تعریف شده است ندارد.

    جهت تعریف متغیر در جاوا اسکریپت، از کلمه‌ی کلیدی var استفاده می‌شود. اما تعریف متغیر در جاوا اسکریپت اجباری نمی‌باشد و می‌توان یک متغیر را مقداردهی نمود بدون آنکه تعریف شده باشد. در صورتی که متغیر با var اعلان نشود، آن متغیر به شیء window نسبت داده می‌شود و ماهیت عمومی پیدا می‌کند. به مثال زیر توجه کنید:

    function sum(a, b) {
    c = a + b;
    }
    
    sum(20, 30);
    alert(c);

    خروجی :

    50


    همانطور که مشاهده می‌کنید، متغیر c بدون تعریف شدن مورد استفاده قرار گرفته است. با اینکه به صورت محلی مقداردهی گردیده است، ولی چون توسط var اعلان نشده است، به شیء window نسبت داده شده و ماهیت عمومی پیدا کرده است.