مطالب
حذف همزمان چندین رکورد GridView با استفاده از CheckBox در ASP.NET
همانطور که می‌دانید GridView جزء جداناپذیر از اکثر پروژه‌های برنامه نویسان ASP.NET Web forms می‌باشد. اکثرا روشی که در میان برنامه نویسان بیشتر استفاده می‌شود، قرار دادن یک دکمه/لینک در هر ردیف از GridView برای حذف رکورد مورد نظر می‌باشد. در این مقاله  قصد دارم روشی را ارائه کنم تا کاربر قادر باشد هر تعداد رکورد را که مدنظر دارد، انتخاب کرده و با فشردن دکمه "حذف" رکوردهای انتخاب شده را حذف کند. 
برای درک بهتر، ابتدا جدولی به اسم "Emploee" را در SQL Server با مشخصات زیر ساخته :
CREATE TABLE [dbo].[Employee] (  
    [EmpId]     INT          NOT NULL,  
    [FirstName] VARCHAR (20) NOT NULL,  
    [LastName]  VARCHAR (20) NOT NULL,  
    [City]      VARCHAR (20) NOT NULL,  
    PRIMARY KEY CLUSTERED ([EmpId] ASC)  
);
1- یک GridView به صفحه افزوده و خاصیت AutoGenerateColumns  آن را برابر False قرار دهید .  
2- فیلدهایی را که قصد نمایش آنها در GridView را دارید به صورت زیر به GridView بیفزایید :
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
3- برای قرار دادن کنترل‌های Asp.net  که در اینجا منظور CheckBox می‌باشد می‌بایست از TemplateField و قرار دادن تگ ItemTemplate درون آن، به صورت زیر استفاده نمایید :
<asp:TemplateField>  
                    <ItemTemplate>  
                        <asp:CheckBox ID="chkDel" runat="server" />  
                    </ItemTemplate>  
                </asp:TemplateField>
و بعد از تگ GridView دکمه‌ای را برای حذف موارد انتخابی در فرم قرار دهید :
<asp:Button ID="btnDeleteRecord" runat="server" OnClick="btnDeleteRecord_Click" Text="Delete"  />
برای نمایش یک پیغام به کاربر  به منظور Confirm کردن دستور حذف در سمت کلاینت، قطعه کد Javascript زیر را قرار می‌دهیم:
function DeleteConfirm() 
        {  
            var Ans = confirm("Do you want to Delete Selected Employee Record?");  
            if (Ans)
            {  
                return true;  
            }  
            else 
            {  
                return false;  
            }  
        }
و در رویداد Page_Load کدهای زیر را جهت نمایش مقادیر در GridView و افزودن تابع فوق به دکمه، قبل از حذف رکوردها می‌افزاییم :
protected void Page_Load(object sender, EventArgs e)  
{  
    if(!IsPostBack)  
    {  
        //Displaying the Data  
        showData();  
        //Adding an Attribute to Server Control(i.e. btnDeleteRecord)  
        btnDeleteRecord.Attributes.Add("onclick", "javascript:return DeleteConfirm()");  
    }  
}
//Method for Displaying Data  
protected void showData()  
{  
    DataTable dt = new DataTable();  
    SqlConnection con = new SqlConnection(cs);  
    SqlDataAdapter adapt = new SqlDataAdapter("select * from Employee",con);  
    con.Open();  
    adapt.Fill(dt);  
    con.Close();  
    GridView1.DataSource = dt;  
    GridView1.DataBind();  
}
ابتدا تابع  DeleteRecode را به صورت زیر پیاده سازی میکنیم :
که یک پارمتر را از ورودی دریافت میکند که ID رکورد انتخاب شده می‌باشد و با استفاده از ID، رکورد مورد نظر را حذف میکنیم :
protected void DeleteRecord(int empid)  
{  
    SqlConnection con = new SqlConnection(cs);  
    SqlCommand com = new SqlCommand("delete from Employee where EmpId=@ID",con);  
    com.Parameters.AddWithValue("@ID",empid);  
    con.Open();  
    com.ExecuteNonQuery();  
    con.Close();  
}
و اما بخش مهم مربوط به رویداد دکمه می‌باشد. در هنگام کلیک بر روی دکمه باید تمامی رکوردهای GridView را چک و تمامی رکوردهایی را که CheckBox آنها تیک خورده است گرفته و ID رکورد مورد نظر را به تابع DeleteRecode فرستاد و در پایان برای اعمال تغییرات، متد ShowDate را فراخوانی و GridView را مجددا Bind می‌کنیم.
protected void btnDeleteRecord_Click(object sender, EventArgs e)  
{  
    foreach (GridViewRow grow in GridView1.Rows)  
    {  
        //Searching CheckBox("chkDel") in an individual row of Grid  
        CheckBox chkdel = (CheckBox)grow.FindControl("chkDel");  
        //If CheckBox is checked than delete the record with particular empid  
        if(chkdel.Checked)  
        {  
            int empid = Convert.ToInt32(grow.Cells[1].Text);  
            DeleteRecord(empid);  
        }  
    }  
    //Displaying the Data in GridView  
    showData();  
}

نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت دوم - سرویس‌های پایه
من موقعی که میخوام کلاس‌ها رو Extract کنم که واسط رو بسازم. کلی پراپرتی و متود دیگه اضافه میشه که میام و با سورس کد شما مطابقت میدم و اضافه هارو برمیدارم، میشه یک توضیحی در مورد اون موارد اضافی بدید مثل پراپرتی‌ها و متود‌های پیشفرض مثل ThrowIfDisposed.
و اینکه بعضی از متود هارو داخل خود کلاس پیاده سازی نوشتید مثل 
Task<PasswordVerificationResult> IApplicationUserManager.VerifyPasswordAsync(IUserPasswordStore<User> store, User user, string password)
{
    return base.VerifyPasswordAsync(store, user, password);
}
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 10 - بررسی تغییرات Viewها
در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor» قسمت «نحوه‌ی سفارشی سازی کلاس پایه‌ی تمام Viewهای برنامه و معرفی inherits@ »، این مورد را توضیح دادم. برای نمونه اگر به فایل Views\_ViewStart.cshtml مراجعه کنید، یک چنین مقدار دهی عمومی در آن هست:
@{
    Layout = "_Layout";
}
این خاصیت عمومی Layout در کلاس پایه‌ی تمام ویووها تعریف شده‌است. اگر می‌خواهید معادل آن‌را داشته باشید، باید یک کلاس سفارشی پایه را با ارث بری از RazorPage ایجاد کنید:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal;

namespace Core1RtmTestResources.StartupCustomizations
{
    public abstract class MyCustomBaseView<TModel> : RazorPage<TModel>
    {
        //روش خاص تزریق وابستگی‌ها در فایل ویژه‌ی جاری
        [RazorInject]
        public IHtmlLocalizerFactory MyHtmlLocalizerFactory { get; set; }

        public IHtmlLocalizer MySharedLocalizer => MyHtmlLocalizerFactory.Create(
            baseName: "SharedResource" /*مشخصات*/,
            location: "Core1RtmTestResources.ExternalResources" /*نام اسمبلی ثالث*/);

        public bool IsAuthenticated()
        {
            return Context.User.Identity.IsAuthenticated;
        }

#pragma warning disable 1998
        public override async Task ExecuteAsync()
        {
        }
#pragma warning restore 1998
    }
}
در اینجا چند نکته مهم هستند:
- در کلاس پایه‌ی سفارشی، امکان تزریق وابستگی‌ها در سازنده‌ی کلاس وجود ندارد. اما از طریق ویژگی RazorInject می‌توان این‌کار را انجام داد.
- RazorInject نیاز به نصب وابستگی ذیل را دارد:
{
    "dependencies": {
        //same as before
        "Microsoft.AspNetCore.Mvc.Razor": "1.0.0"
    }
}
- پس از تعریف این کلاس پایه، برای معرفی آن به فایل Views\_ViewImports.cshtml مراجعه کنید:
@using Core1RtmTestResources
@using Microsoft.AspNetCore.Mvc.Localization
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@inherits Core1RtmTestResources.StartupCustomizations.MyCustomBaseView<TModel>
اکنون در تمام ویووهای برنامه به خاصیت عمومی MySharedLocalizer واقع در کلاس پایه‌ی سفارشی برنامه، دسترسی خواهید داشت:
MySharedLocalizer from MyCustomBaseView: @MySharedLocalizer["About Title"]
نظرات مطالب
Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor
سلام،
گاهی در پروژه‌های blazor نیاز می‌شود که از صفحات razor به جای کامپوننت‌های razor استفاده نمود.(درحقیقت از فایل‌های a.cshtml به جای a.razor)
آیا راهی هست که هم صفحات و هم کامپوننت‌های razor فقط از یک layout استفاده کنند یا اینکه مجبوریم برای صفحات razor یک layout جداگانه درنظر بگیریم؟
با سپاس فراوان. 
مطالب
نکات ویژه کار با عملیات نامتقارن در Blazor Server
 در برنامه‌های Blazor Server، تنها از یک نخ رابط کاربری واحد ( single UI thread ) استفاده نمی‌شود؛ بلکه هر نخی که در دسترس باشد، می‌تواند در موقع رندر، استفاده شود. علاوه بر این اگر از عملیات نامتقارن استفاده شود، زمانیکه به کلمه‌ی کلیدی await می‌رسیم، آنگاه نخ اختصاص داده شده‌ی برای ادامه پردازش متد، ممکن است لزوما همان چیزی نباشد که آن را شروع کرده است. برای نشان دادن این موضوع مثالی را در پیش می‌گیریم.
کامپوننتی را با نام  SynchronousInitComponent با کد زیر درنظر می‌گیریم. همانطور که از اسم آن مشخص است این کامپوننت به صورت متقارن یا همزمان پیاده‌سازی شده است:
<p>Sync rendered by thread @IdOfRenderingThread</p>

@code
{
  int IdOfRenderingThread;

  protected override void OnInitialized()
  {
    base.OnInitialized();
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;
  }
}
در حقیقت در متد OnInitialized آن، مقدار نخ جاری را توسط Thread.ManagedThreadId به دست می‌آوریم. بنابراین شماره نخ جاری پس از رندر شدن کامپوننت، در صفحه نمایش داده می‌شود.
حال در کامپوننت دیگری برای مثال کامپوننت index، کامپوننت همزمان فوق را به شکل زیر فراخوانی می‌کنیم:
@page "/"

<h1>Components with synchronous OnInitialized()</h1>
@for (int i = 0; i < 5; i++)
{
  <SynchronousInitComponent />
}
با این نتیجه:
Components with synchronous OnInitialized()
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
Sync rendered by thread 4
همانطور که ملاحظه می‌نمایید شناسه نخ یکسانی برای هر فراخوانی کامپوننت نشان داده می‌شود. بدیهی است در صورتیکه شما همین کد را اجرا کنید، ممکن است شماره نخ برنامه شما با کد من یکی نباشد؛ اما نتیجه یکی است. یعنی در تمامی موارد، یک عدد مشاهده می‌شود.
حال همین آزمایش را با متدهای نامتقارن یا ناهمزمان انجام می‌دهیم. کامپوننت AsynchronousInitComponent را با کد زیر درنظر بگیرید:
<p>Async rendered by thread @IdOfRenderingThread</p>

@code
{
  int IdOfRenderingThread;

  protected override async Task OnInitializedAsync()
  {
    // Runs synchronously as there is no code in base.OnInitialized(),
    // so the same thread is used
    await base.OnInitializedAsync().ConfigureAwait(false);
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;

    // Awaiting will schedule a job for later, and we will be assigned
    // whichever worker thread is next available
    await Task.Delay(1000).ConfigureAwait(false);
    IdOfRenderingThread =
      System.Threading.Thread.CurrentThread.ManagedThreadId;
  }
}
این کامپوننت هم دقیقا شبیه کامپوننت قبلی است؛ با این تفاوت که IdOfRenderingThread، مجددا بعد از یک تاخیر یک ثانیه‌ای مقداردهی شده‌است. این مقداردهی سبب رندر مجدد کامپوننت با تاخیر یک ثانیه می‌شود. حال در کامپوننت دیگری، کامپوننت غیرمتقارن فوق را به شکل زیر فراخوانی می‌کنیم:
@page "/async-init"

<h1>Components with asynchronous OnInitializedAsync()</h1>
@for (int i = 0; i < 5; i++)
{
  <AsynchronousInitComponent/>
}
با اجرا کردن برنامه، دقیقا نتایج، شبیه نتایج نتایج کامپوننت متقارن می‌باشد:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
Async rendered by thread 4
اما تنها بعد از یک ثانیه (await Task.Delay(1000)) ، متدهای OnInitializedAsync کامپوننت‌ها، به پایان می‌رسند و مقدار IdOfRenderingThread را پیش از به پایان رسیدن رندر صفحه، به روز رسانی می‌نمایند. اینبار، دیگر مقادیر نخ‌ها متفاوت خواهند بود:
Components with asynchronous OnInitializedAsync()
Async rendered by thread 7
Async rendered by thread 18
Async rendered by thread 10
Async rendered by thread 13
Async rendered by thread 11
با توجه به مطالب مطرح شده به این نتیجه می‌رسیم که این موضوع می‌تواند هنگام استفاده از یک وابستگی غیر ایمن ( non-thread-safe dependency ) مانند DBContext در چندین کامپوننت باعث بروز مشکل شود. نمونه‌ای از نحوه‌ی رویارویی با مشکلات احتمالی آن، در اینجا و اینجا بررسی شده‌است.  
نظرات اشتراک‌ها
پیاده سازی چندمستاجری با EF Core و Blazor Server
سلام، پس با این وجود و چیزی که من تا به اینجا متوجه شدم نرم افزارهای سازمانی که میخوایم با بلیزر و البته با استفاده از استراتژی multi-Tenancy طراحی و پیاده سازی کنیم باید با Blazor Server انجام شوند و نه با WASM...درسته؟؟
نظرات مطالب
خودکار کردن تعاریف DbSetها در EF Code first
در EF Core یک چنین شکلی را پیدا می‌کند (البته «روش صحیح بارگذاری پویای اسمبلی‌ها در NET Core.» را هم مدنظر داشته باشید):
    public class BloggingContext : DbContext
    {        
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            loadEntities(modelBuilder,
            asmPath: @"D:\Prog\SomeAsm1\bin\Debug\netstandard2.0\SomeAsm1.dll",
            nameSpace: "SomeAsm1.Models");
            base.OnModelCreating(modelBuilder);
        }

        private static void loadEntities(ModelBuilder modelBuilder, string asmPath, string nameSpace)
        {
            var modelInAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(asmPath);
            // var modelInAssembly = Assembly.Load(new AssemblyName("ModuleApp"));
            var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[] { });
            foreach (var type in modelInAssembly.ExportedTypes)
            {
                if (type.BaseType is System.Object && !type.IsAbstract && type.Namespace == nameSpace)
                {
                    entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
                }
            }
        }
مطالب
EF Code First #13

استفاده مستقیم از عبارات SQL در EF Code first

طراحی اکثر ORMهای موجود به نحوی است که برنامه نهایی شما را مستقل از بانک اطلاعاتی کنند و این پروایدر نهایی است که معادل‌های صحیح بسیاری از توابع توکار بانک اطلاعاتی مورد استفاده را در اختیار EF قرار می‌دهد. برای مثال در یک بانک اطلاعاتی تابعی به نام substr تعریف شده، در بانک اطلاعاتی دیگری همین تابع substring نام دارد. اگر برنامه را به کمک کوئری‌های LINQ تهیه کنیم، نهایتا پروایدر نهایی مخصوص بانک اطلاعاتی مورد استفاده است که این معادل‌ها را در اختیار EF قرار می‌دهد و برنامه بدون مشکل کار خواهد کرد. اما یک سری از موارد شاید معادلی در سایر بانک‌های اطلاعاتی نداشته باشند؛ برای مثال رویه‌های ذخیره شده یا توابع تعریف شده توسط کاربر. امکان استفاده از یک چنین توانایی‌هایی نیز با اجرای مستقیم عبارات SQL در EF Code first پیش بینی شده و بدیهی است در این حالت برنامه به یک بانک اطلاعاتی خاص گره خواهد خورد؛ همچنین مزیت استفاده از کوئری‌های Strongly typed تحت نظر کامپایلر را نیز از دست خواهیم داد. به علاوه باید به یک سری مسایل امنیتی نیز دقت داشت که در ادامه بررسی خواهند شد.


کلاس‌های مدل مثال جاری

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

using System.Collections.Generic;

namespace EF_Sample08.DomainClasses
{
public class Doctor
{
public int Id { set; get; }
public string Name { set; get; }

public virtual ICollection<Patient> Patients { set; get; }
}
}

namespace EF_Sample08.DomainClasses
{
public class Patient
{
public int Id { set; get; }
public string Name { set; get; }

public virtual Doctor Doctor { set; get; }
}
}

کلاس Context برنامه به نحو زیر تعریف شده:

using System.Data.Entity;
using EF_Sample08.DomainClasses;

namespace EF_Sample08.DataLayer.Context
{
public class Sample08Context : DbContext
{
public DbSet<Doctor> Doctors { set; get; }
public DbSet<Patient> Patients { set; get; }
}
}

و اینبار کلاس DbMigrationsConfiguration تعریف شده اندکی با مثال‌های قبلی متفاوت است:

using System.Data.Entity.Migrations;
using EF_Sample08.DomainClasses;
using System.Collections.Generic;

namespace EF_Sample08.DataLayer.Context
{
public class Configuration : DbMigrationsConfiguration<Sample08Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}

protected override void Seed(Sample08Context context)
{
addData(context);
addSP(context);
addFn(context);
base.Seed(context);
}

private static void addData(Sample08Context context)
{
var patient1 = new Patient { Name = "p1" };
var patient2 = new Patient { Name = "p2" };
var doctor1 = new Doctor { Name = "doc1", Patients = new List<Patient> { patient1, patient2 } };
context.Doctors.Add(doctor1);
}

private static void addFn(Sample08Context context)
{
context.Database.ExecuteSqlCommand(
@"IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FindDoctorPatientsCount]')
AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
DROP FUNCTION [dbo].[FindDoctorPatientsCount]");
context.Database.ExecuteSqlCommand(
@"CREATE FUNCTION FindDoctorPatientsCount(@Doctor_Id INT)
RETURNS INT
BEGIN
RETURN
(
SELECT COUNT(*)
FROM Patients
WHERE Doctor_Id = @Doctor_Id
);
END");
}

private static void addSP(Sample08Context context)
{
context.Database.ExecuteSqlCommand(
@"IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FindDoctorsStartWith]')
AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[FindDoctorsStartWith]
");
context.Database.ExecuteSqlCommand(
@"CREATE PROCEDURE FindDoctorsStartWith(@name NVARCHAR(400))
AS
SELECT *
FROM Doctors
WHERE [Name] LIKE @name + '%'");
}
}
}

در اینجا از متد Seed علاوه بر مقدار دهی اولیه جداول، برای تعریف یک رویه ذخیره شده به نام FindDoctorsStartWith و یک تابع سفارشی به نام FindDoctorPatientsCount نیز استفاده شده است. متد context.Database.ExecuteSqlCommand مستقیما یک عبارت SQL را بر روی بانک اطلاعاتی اجرا می‌کند.

در ادامه کدهای کامل برنامه نهایی را ملاحظه می‌کنید:
using System;
using System.Data;
using System.Data.Entity;
using System.Data.Objects.SqlClient;
using System.Data.SqlClient;
using System.Linq;
using EF_Sample08.DataLayer.Context;
using EF_Sample08.DomainClasses;

namespace EF_Sample08
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample08Context, Configuration>());

using (var db = new Sample08Context())
{
runSp(db);
runFn(db);
usingSqlFunctions(db);
}
}

private static void usingSqlFunctions(Sample08Context db)
{
var doctorsWithNumericNameList = db.Doctors.Where(x => SqlFunctions.IsNumeric(x.Name) == 1).ToList();
if (doctorsWithNumericNameList.Any())
{
//do something
}
}

private static void runFn(Sample08Context db)
{
var doctorIdParameter = new SqlParameter
{
ParameterName = "@doctor_id",
Value = 1,
SqlDbType = SqlDbType.Int
};
var patientsCount = db.Database.SqlQuery<int>("select dbo.FindDoctorPatientsCount(@doctor_id)", doctorIdParameter).FirstOrDefault();
Console.WriteLine(patientsCount);
}

private static void runSp(Sample08Context db)
{
var nameParameter = new SqlParameter
{
ParameterName = "@name",
Value = "doc",
Direction = ParameterDirection.Input,
SqlDbType = SqlDbType.NVarChar
};
var doctors = db.Database.SqlQuery<Doctor>("exec FindDoctorsStartWith @name", nameParameter).ToList();
if (doctors.Any())
{
foreach (var item in doctors)
{
Console.WriteLine(item.Name);
}
}
}
}
}

توضیحات

همانطور که ملاحظه می‌کنید، برای اجرای مستقیم یک عبارت SQL صرفنظر از اینکه یک رویه ذخیره شده است یا یک تابع و یا یک کوئری معمولی، باید از متد db.Database.SqlQuery استفاده کرد. خروجی این متد از نوع IEnumerable است و این توانایی را دارد که رکوردهای بازگشت داده شده از بانک اطلاعاتی را به خواص یک کلاس به صورت خودکار نگاشت کند.
پارامتر اول متد db.Database.SqlQuery، عبارت SQL مورد نظر است. پارامتر دوم آن باید توسط وهله‌هایی از کلاس SqlParameter مقدار دهی شود. به کمک SqlParameter نام پارامتر مورد استفاده، مقدار و نوع آن مشخص می‌گردد. همچنین Direction آن نیز برای استفاده از رویه‌های ذخیره شده ویژه‌ای که دارای پارامتری از نوع out هستند درنظر گرفته شده است.

چند نکته

- در متد runSp فوق، متد الحاقی ToList را حذف کرده و برنامه را اجرا کنید. بلافاصله پیغام خطای «The SqlParameter is already contained by another SqlParameterCollection.» ظاهر خواهد شد. علت هم این است که با بکارگیری متد ToList، تمام عملیات یکبار انجام شده و نتیجه بازگشت داده می‌شود اما اگر به صورت مستقیم از خروجی IEnumerable آن استفاده کنیم، در حلقه foreach تعریف شده، ممکن است این فراخوانی چندبار انجام شود. به همین جهت ذکر متد ToList در اینجا ضروری است.

- عنوان شد که در اینجا باید به مسایل امنیتی دقت داشت. بدیهی است امکان نوشتن یک چنین کوئری‌هایی نیز وجود دارد:

db.Database.SqlQuery<Doctor>("exec FindDoctorsStartWith "+ txtName.Text, nameParameter).ToList()

در این حالت به ظاهر مشغول به استفاده از رویه‌های ذخیره شده‌ای هستیم که عنوان می‌شود در برابر حملات تزریق SQL در امان هستند، اما چون در کدهای ما به نحو ناصحیحی با جمع زدن رشته‌ها مقدار دهی شده است، برنامه و بانک اطلاعاتی دیگر در امان نخواهند بود. بنابراین در این حالت استفاده از پارامترها را نباید فراموش کرد.
زمانیکه از کوئری‌های LINQ استفاده می‌شود تمام این مسایل توسط EF مدیریت خواهد شد. اما اگر قصد دارید مستقیما عبارات SQL را فراخوانی کنید، تامین امنیت برنامه به عهده خودتان خواهد بود.

- در متد usingSqlFunctions از SqlFunctions.IsNumeric استفاده شده است. این مورد مختص به SQL Server است و امکان استفاده از توابع توکار ویژه SQL Server را در کوئری‌های LINQ برنامه فراهم می‌سازد. برای مثال متدالحاقی از پیش تعریف شده‌ای به نام IsNumeric به صورت مستقیم در دسترس نیست، اما به کمک کلاس SqlFunctions این تابع و بسیاری از توابع دیگر توکار SQL Server قابل استفاده خواهند بود.
اگر علاقمند هستید که لیست این توابع را مشاهده کنید، در ویژوال استودیو بر روی SqlFunctions کلیک راست کرده و گزینه Go to definition را انتخاب کنید.


مطالب
استفاده از چندین Context در EF 6 Code first
در نگارش قبلی EF Code first به ازای یک پروژه تنها یک سیستم Migration قابل تعریف بود و این سیستم مهاجرت، تنها با یک DbContext کار می‌کرد. در نگارش ششم این کتابخانه، سیستم مهاجرت Code first آن از چندین DbContext، به ازای یک پروژه که به یک بانک اطلاعاتی اشاره می‌کنند، پشتیبانی می‌کند. مزیت اینکار اندکی بهبود در نگهداری تنها کلاس DbContext تعریف شده است. برای مثال می‌توان یک کلاس DbContext مخصوص قسمت ثبت نام را ایجاد کرد. یک کلاس DbContext مخصوص کلیه جداول مرتبط با مقالات را و همینطور الی آخر. نهایتا تمام این Contextها سبب ایجاد یک بانک اطلاعاتی واحد خواهند شد.
اگر در یک پروژه EF Code first چندین Context وجود داشته باشد و دستور enable-migrations را بدون پارامتری فراخوانی کنیم، پیغام خطای More than one context type was found in the assmbly xyz را دریافت خواهیم کرد.
الف) اما در EF 6 می‌توان با بکار بردن سوئیچ جدید ContextTypeName، به ازای هر Context، مهاجرت مرتبط با آن‌را تنظیم نمود:
 enable-migrations -ContextTypeName dbContextName1 -MigrationDirectory DataContexts\Name1Migrations
همچنین در اینجا نیز می‌توان با استفاده از سوئیچ MigrationDirectory، فایل‌های تولید شده را در پوشه‌های مجزایی ذخیره کرد.

ب) در مرحله بعد، نیاز به فراخوانی دستور add-migration است:
 add-migration -ConfigurationTypeName FullNameSpaceCtx1.Configuration "InitialCreate"
با اجرای دستور enable-migrations یک کلاس Configuration جهت DbContext مشخص شده، ایجاد می‌شود. سپس آدرس کامل این کلاس را به همراه ذکر دقیق فضای نام آن در اختیار دستور add-migration قرار می‌دهیم.
ذکر کامل فضای نام، از این جهت مهم است که کلاس Configuration به ازای Contextهای مختلف ایجاد شده، یک نام را خواهد داشت؛ اما در فضاهای نام متفاوتی قرار می‌گیرد.
با اجرای دستور add-migration، کدهای سی شارپ مورد نیاز جهت اعمال تغییرات بر روی ساختار بانک اطلاعاتی تولید می‌شوند. در مرحله بعد، این کدها تبدیل به دستورات SQL متناظری شده و بر روی بانک اطلاعاتی اجرا خواهند شد.
بدیهی است اگر دو Context در برنامه تعریف کرده باشید، دوبار باید دستور enable-migrations و دوبار دستور add-migration را با پارامترهای اشاره کننده به Conetxtهای مدنظر اجرا کرد.

ج) سپس برای اعمال این تغییرات، باید دستور update-database را اجرا کرد.
 update-database  -ConfigurationTypeName FullNameSpaceCtx1.Configuration
اینبار دستور update-database نیز بر اساس نام کامل کلاس Configuration مدنظر باید اجرا گردد و به ازای هر Context موجود، یکبار نیاز است اجرا گردد.
نهایتا اگر به بانک اطلاعاتی مراجعه کنید، تمام جداول و تعاریف را یکجا در همان بانک اطلاعاتی می‌توانید مشاهده نمائید.


داشتن چندین Context در برنامه و مدیریت تراکنش‌ها

در EF، هر DbContext معرف یک واحد کار است. یعنی تراکنش‌ها و چندین عمل متوالی مرتبط انجام شده، درون یک DbContext معنا پیدا می‌کنند. متد SaveChanges نیز بر همین اساس است که کلیه اعمال ردیابی شده در طی یک واحد کار را در طی یک تراکنش به بانک اطلاعاتی اعمال می‌کند. همچنین مباحثی مانند lazy loading نیز در طی یک Context مفهوم دارند. به علاوه دیگر امکان join نویسی بین دو Context وجود نخواهد داشت. باید اطلاعات را از یکی واکشی و سپس این اطلاعات درون حافظه‌ای را به دیگری ارسال کنید.

یک نکته
می‌توان یک DbSet را در چندین ‍Context تعریف کرد. یعنی اگر بحث join نویسی مطرح است، با تکرار تعریف DbSetها اینکار قابل انجام است اما این مساله اساس جداسازی Contextها را نیز زیر سؤال می‌برد.
 

داشتن چندین Context در برنامه و مدیریت رشته‌های اتصالی

در EF Code first روش‌های مختلفی برای تعریف رشته اتصالی به بانک اطلاعاتی وجود دارند. اگر تغییر خاصی در کلاس مشتق شده از DbContext ایجاد نکنیم، نام کلید رشته اتصالی تعریف شده در فایل کانفیگ باید به نام کامل کلاس Context برنامه اشاره کند. اما با داشتن چندین Context به ازای یک دیتابیس می‌توان از روش ذیل استفاده کرد:

  public class Ctx1 : DbContext
    {
        public Ctx1()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }
    }

  public class Ctx2 : DbContext
    {
        public Ctx2()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }
    }

  <connectionStrings>
    <add name="DefaultConnection" connectionString="…." providerName="System.Data.SqlClient" />
  </connectionStrings>
در اینجا در سازنده کلاس‌های Context تعریف شده، نام کلید رشته اتصالی موجود در فایل کانفیگ برنامه ذکر شده است. به این ترتیب این دو Context به یک بانک اطلاعاتی اشاره خواهند کرد.


چه زمانی بهتر است از چندین Context در برنامه استفاده کرد؟
عموما در طراحی‌های سازمانی SQL Server، تمام جداول از schema مدیریتی به نام dbo استفاده نمی‌کنند. جداول فروش از schema خاص خود و جداول کاربران از schema دیگری استفاده خواهند کرد. با استفاده از چندین Context می‌توان به ازای هر کدام از schemaهای متفاوت موجود، «یک ناحیه ایزوله» را ایجاد و مدیریت کرد.
  public class Ctx2 : DbContext
    {
        public Ctx2()
            : base("DefaultConnection")
        {
            //Database.Log = sql => Debug.Write(sql);
        }

         protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("sales");
            base.OnModelCreating(modelBuilder);
        }
    }
در این حالت در EF 6 می‌توان DefaultSchema کلی یک Context را در متد بازنویسی شده OnModelCreating به نحو فوق تعریف و مدیریت کرد. در این مثال به صورت خودکار کلیه DbSetهای Ctx2 از schema ایی به نام sales استفاده می‌کنند.