در بیشتر مواقع نیازی به ارائه پروایدرهای پایگاه داده چندگانه ندارید. به عنوان مثال ، شما با پروایدرSQL Server شروع میکنید و نیازی به رفتن به پروایدر پایگاه داده دیگری نیست. در این پروژهی نمونه، ما باید چندین پروایدر را برای پشتیبانی از محیطهای مختلف استقرار اضافه کنیم. برخی از مشتریان ترجیح میدهند از پایگاه داده Windows Server و SQL Server و برخی دیگر از Linux و MySQL یا PostgreSQL استفاده کنند.
اجرای یک Script حاوی دستورات Go در سی شارپ
آقای کیاست، بهترین روش برای اجرای Script هایی که حاوی کلمه کلیدی GO هستند، استفاده از SMO است. به کد زیر دقت کنید:
Server server = new Server(new ServerConnection() { ConnectionString = cnnStr }); server.ConnectionContext.ExecuteNonQuery(sql);
موفق باشید.
نگارش نهایی SQL Server 2017 منتشر شد
Download SQL Server 2017 for Windows
Microsoft® SQL Server® 2017 Feature Pack
Microsoft SQL Server 2017 Developer Edition: https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-x64-ENU-Dev.iso Microsoft SQL Server 2017 Evaluation: https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLServer2017-x64-ENU.iso Microsoft SQL Server 2017 Express: https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLEXPRADV_x64_ENU.exe https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLEXPR_x64_ENU.exe https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SqlLocalDB.msi https://download.microsoft.com/download/E/F/2/EF23C21D-7860-4F05-88CE-39AA114B014B/SQLRules_x64_ENU.exe
مفهوم READ_COMMITTED_SNAPSHOT در EF 6
- Sanpshot مطابق واژه نامه مایکروسافت معنای «نگارش» را میدهد. در این حالت کلیه کوئریهای داخل یک تراکنش، یک نگارش یا snapshot از دیتابیس را مشاهده خواهند کرد. این نگارش یا Row version، در tempdb نگه داری میشود. با فعال سازی SNAPSHOT isolation، هر زمانیکه یک ردیف به روز رسانی میشود، موتور SQL Server یک نسخه از اطلاعات اولیه این ردیف را در tempdb ذخیره میکند (اینجا بود که عنوان شد با یک کپی فقط خواندنی از اطلاعات در حین واکشی اطلاعات سر و کار خواهید داشت).
خلاصه الگوریتم کاری آن :
الف) با آغاز یک تراکنش، یک عدد متوالی منحصربفرد تراکنش (شماره نگارش) ایجاد شده و به آن نسبت داده میشود.
ب) در حین این تراکنش، موتور SQL Server، به tempdb مراجعه کرده و شماره نگارشی نزدیک و کمتر از شماره نگارش تراکنش جاری را پیدا میکند. همچنین SQL Server بررسی میکند که این شماره یافت شده حتما جزو تراکنشهای پایان یافته سیستم باشد.
ج) بر اساس این شماره یافت شده، نگارش معتبری از اطلاعات از tempdb استخراج میشود.
به این ترتیب یک تراکنش، کلیه اطلاعات موجود در ابتدای کار خود را بدون قرار دادن قفلی بر روی جداول مرتبط، دریافت خواهد کرد.
اطلاعات بیشتر
- در متن ذکر گردید که از SQL Server 2005 به بعد قابلیت فوق اضافه شده.
- همچنین SQL Server 2000 دیگر پشتیبانی رسمی ندارد و استفاده از آن حداقل از لحاظ امنیتی معقول نیست.
public string Content { get; set; }
بنابراین اگر هدف، ثبت اطلاعات در فیلدی از نوع ntext در این بانک اطلاعاتی باشد باید به یکی از دو روش زیر عمل کرد:
[MaxLength] public string Content { get; set; }
[Column(TypeName = "ntext")] public string Text { get; set; }
بنابراین قید MaxLength بر روی خواصی که قرار است حاوی متونی طولانی باشند، میتواند به عنوان یک کار مفید جهت سازگاری با بانکهای مختلف، به شمار آید.
در قسمت قبلی بروز رسانی موجودیتهای منفصل با WCF را بررسی کردیم. در این قسمت خواهیم دید چگونه میتوان تغییرات موجودیتها را تشخیص داد و عملیات CRUD را روی یک Object Graph اجرا کرد.
تشخیص تغییرات با Web API
فرض کنید میخواهیم از سرویسهای Web API برای انجام عملیات CRUD استفاده کنیم، اما بدون آنکه برای هر موجودیت متدهایی مجزا تعریف کنیم. به بیان دیگر میخواهیم عملیات مذکور را روی یک Object Graph انجام دهیم. مدیریت دادهها هم با مدل Code-First پیاده سازی میشود. در مثال جاری یک اپلیکیشن کنسول خواهیم داشت که بعنوان یک کلاینت سرویس را فراخوانی میکند. هر پروژه نیز در Solution مجزایی قرار دارد، تا یک محیط n-Tier را شبیه سازی کنیم.
مدل زیر را در نظر بگیرید.
همانطور که میبینید مدل ما آژانسهای مسافرتی و رزرواسیون آنها را ارائه میکند. میخواهیم مدل و کد دسترسی دادهها را در یک سرویس Web API پیاده سازی کنیم تا هر کلاینتی که به HTTP دسترسی دارد بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید:
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe3.Service تغییر دهید.
- کنترلر جدیدی بنام TravelAgentController به پروژه اضافه کنید.
- دو کلاس جدید با نامهای TravelAgent و Booking بسازید و کد آنها را مطابق لیست زیر تغییر دهید.
public class TravelAgent { public TravelAgent() { this.Bookings = new HashSet<Booking>(); } public int AgentId { get; set; } public string Name { get; set; } public virtual ICollection<Booking> Bookings { get; set; } } public class Booking { public int BookingId { get; set; } public int AgentId { get; set; } public string Customer { get; set; } public DateTime BookingDate { get; set; } public bool Paid { get; set; } public virtual TravelAgent TravelAgent { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- کلاس جدیدی بنام Recipe3Context بسازید و کد آن را مطابق لیست زیر تغییر دهید.
public class Recipe3Context : DbContext { public Recipe3Context() : base("Recipe3ConnectionString") { } public DbSet<TravelAgent> TravelAgents { get; set; } public DbSet<Booking> Bookings { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<TravelAgent>().HasKey(x => x.AgentId); modelBuilder.Entity<TravelAgent>().ToTable("TravelAgents"); modelBuilder.Entity<Booking>().ToTable("Bookings"); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه کنید.
<connectionStrings> <add name="Recipe3ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به متد Application_Start اضافه نمایید. این کد بررسی Model Compatibility در EF را غیرفعال میکند. همچنین به JSON serializer میگوییم که self-referencing loop خاصیتهای پیمایشی را نادیده بگیرد. این حلقه بدلیل ارتباط bidirectional بین موجودیتها بوجود میآید.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); // The bidirectional navigation properties between related entities // create a self-referencing loop that breaks Web API's effort to // serialize the objects as JSON. By default, Json.NET is configured // to error when a reference loop is detected. To resolve problem, // simply configure JSON serializer to ignore self-referencing loops. GlobalConfiguration.Configuration.Formatters.JsonFormatter .SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; ... }
- فایل RouteConfig.cs را باز کنید و قوانین مسیریابی را مانند لیست زیر تغییر دهید.
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "ActionMethodSave", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional }); }
- در آخر کنترلر TravelAgent را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
public class TravelAgentController : ApiController { // GET api/travelagent [HttpGet] public IEnumerable<TravelAgent> Retrieve() { using (var context = new Recipe3Context()) { return context.TravelAgents.Include(x => x.Bookings).ToList(); } } /// <summary> /// Update changes to TravelAgent, implementing Action-Based Routing in Web API /// </summary> public HttpResponseMessage Update(TravelAgent travelAgent) { using (var context = new Recipe3Context()) { var newParentEntity = true; // adding the object graph makes the context aware of entire // object graph (parent and child entities) and assigns a state // of added to each entity. context.TravelAgents.Add(travelAgent); if (travelAgent.AgentId > 0) { // as the Id property has a value greater than 0, we assume // that travel agent already exists and set entity state to // be updated. context.Entry(travelAgent).State = EntityState.Modified; newParentEntity = false; } // iterate through child entities, assigning correct state. foreach (var booking in travelAgent.Bookings) { if (booking.BookingId > 0) // assume booking already exists if ID is greater than zero. // set entity to be updated. context.Entry(booking).State = EntityState.Modified; } context.SaveChanges(); HttpResponseMessage response; // set Http Status code based on operation type response = Request.CreateResponse(newParentEntity ? HttpStatusCode.Created : HttpStatusCode.OK, travelAgent); return response; } } [HttpDelete] public HttpResponseMessage Cleanup() { using (var context = new Recipe3Context()) { context.Database.ExecuteSqlCommand("delete from [bookings]"); context.Database.ExecuteSqlCommand("delete from [travelagents]"); } return Request.CreateResponse(HttpStatusCode.OK); } }
- در ویژوال استودیو پروژه جدیدی از نوع Console application بسازید و نام آن را به Recipe3.Client تغییر دهید.
- فایل program.cs را باز کنید و کد آن را مطابق لیست زیر بروز رسانی کنید.
internal class Program { private HttpClient _client; private TravelAgent _agent1, _agent2; private Booking _booking1, _booking2, _booking3; private HttpResponseMessage _response; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { var program = new Program(); program.ServiceSetup(); // do not proceed until clean-up is completed await program.CleanupAsync(); program.CreateFirstAgent(); // do not proceed until agent is created await program.AddAgentAsync(); program.CreateSecondAgent(); // do not proceed until agent is created await program.AddSecondAgentAsync(); program.ModifyAgent(); // do not proceed until agent is updated await program.UpdateAgentAsync(); // do not proceed until agents are fetched await program.FetchAgentsAsync(); } private void ServiceSetup() { // set up infrastructure for Web API call _client = new HttpClient {BaseAddress = new Uri("http://localhost:6687/")}; // add Accept Header to request Web API content negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } private async Task CleanupAsync() { // call cleanup method in service _response = await _client.DeleteAsync("api/travelagent/cleanup/"); } private void CreateFirstAgent() { // create new Travel Agent and booking _agent1 = new TravelAgent {Name = "John Tate"}; _booking1 = new Booking { Customer = "Karen Stevens", Paid = false, BookingDate = DateTime.Parse("2/2/2010") }; _booking2 = new Booking { Customer = "Dolly Parton", Paid = true, BookingDate = DateTime.Parse("3/10/2010") }; _agent1.Bookings.Add(_booking1); _agent1.Bookings.Add(_booking2); } private async Task AddAgentAsync() { // call generic update method in Web API service to add agent and bookings _response = await _client.PostAsync("api/travelagent/update/", _agent1, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include // database-generated Ids for each entity _agent1 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking1 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Karen Stevens"); _booking2 = _agent1.Bookings.FirstOrDefault(x => x.Customer == "Dolly Parton"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void CreateSecondAgent() { // add new agent and booking _agent2 = new TravelAgent {Name = "Perry Como"}; _booking3 = new Booking { Customer = "Loretta Lynn", Paid = true, BookingDate = DateTime.Parse("3/15/2010")}; _agent2.Bookings.Add(_booking3); } private async Task AddSecondAgentAsync() { // call generic update method in Web API service to add agent and booking _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service _agent2 = await _response.Content.ReadAsAsync<TravelAgent>(); _booking3 = _agent2.Bookings.FirstOrDefault(x => x.Customer == "Loretta Lynn"); Console.WriteLine("Successfully created Travel Agent {0} and {1} Booking(s)", _agent2.Name, _agent2.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private void ModifyAgent() { // modify agent 2 by changing agent name and assigning booking 1 to him from agent 1 _agent2.Name = "Perry Como, Jr."; _agent2.Bookings.Add(_booking1); } private async Task UpdateAgentAsync() { // call generic update method in Web API service to update agent 2 _response = await _client.PostAsync("api/travelagent/update/", _agent2, new JsonMediaTypeFormatter()); if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids _agent1 = _response.Content.ReadAsAsync<TravelAgent>().Result; Console.WriteLine("Successfully updated Travel Agent {0} and {1} Booking(s)", _agent1.Name, _agent1.Bookings.Count); } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } private async Task FetchAgentsAsync() { // call Get method on service to fetch all Travel Agents and Bookings _response = _client.GetAsync("api/travelagent/retrieve").Result; if (_response.IsSuccessStatusCode) { // capture newly created travel agent from service, which will include Ids var agents = await _response.Content.ReadAsAsync<IEnumerable<TravelAgent>>(); foreach (var agent in agents) { Console.WriteLine("Travel Agent {0} has {1} Booking(s)", agent.Name, agent.Bookings.Count()); } } else Console.WriteLine("{0} ({1})", (int) _response.StatusCode, _response.ReasonPhrase); } }
- در آخر کلاسهای TravelAgent و Booking را به پروژه کلاینت اضافه کنید. اینگونه کدها بهتر است در لایه مجزایی قرار گیرند و بین پروژهها به اشتراک گذاشته شوند.
اگر اپلیکیشن کنسول (کلاینت) را اجرا کنید با خروجی زیر مواجه خواهید شد.
(Successfully created Travel Agent John Tate and 2 Booking(s
(Successfully created Travel Agent Perry Como and 1 Booking(s
(Successfully updated Travel Agent Perry Como, Jr. and 2 Booking(s
(Travel Agent John Tate has 1 Booking(s
(Travel Agent Perry Como, Jr. has 2 Booking(s
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر MVC Web Controller دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله سایت در حال اجرا است و سرویسها قابل دسترسی هستند.
سپس اپلیکیشن کنسول را باز کنید، روی خط اول کد فایل program.cs یک breakpoint قرار دهید و آن را اجرا کنید. ابتدا آدرس سرویس Web API را نگاشت میکنیم و با تنظیم مقدار خاصیت Accept Header از سرویس درخواست میکنیم که اطلاعات را با فرمت JSON بازگرداند.
بعد از آن با استفاده از آبجکت HttpClient متد DeleteAsync را فراخوانی میکنیم که روی کنترلر TravelAgent تعریف شده است. این متد تمام دادههای پیشین را حذف میکند.
در قدم بعدی سه آبجکت جدید میسازیم: یک آژانس مسافرتی و دو رزرواسیون. سپس این آبجکتها را با فراخوانی متد PostAsync روی آبجکت HttpClient به سرویس ارسال میکنیم. اگر به متد Update در کنترلر TravelAgent یک breakpoint اضافه کنید، خواهید دید که این متد آبجکت آژانس مسافرتی را بعنوان یک پارامتر دریافت میکند و آن را به موجودیت TravelAgents در Context جاری اضافه مینماید. این کار آبجکت آژانس مسافرتی و تمام آبجکتهای فرزند آن را در حالت Added اضافه میکند و باعث میشود که context جاری شروع به ردیابی (tracking) آنها کند.
نکته: قابل ذکر است که اگر موجودیتهای متعددی با مقداری یکسان در خاصیت کلید اصلی (Primary-key value) دارید باید مجموعه آبجکتهای خود را Add کنید و نه Attach. در مثال جاری چند آبجکت Booking داریم که مقدار کلید اصلی آنها صفر است (Bookings with Id = 0). اگر از Attach استفاده کنید EF پیغام خطایی صادر میکند چرا که چند موجودیت با مقادیر کلید اصلی یکسان به context جاری اضافه کرده اید.
بعد از آن بر اساس مقدار خاصیت Id مشخص میکنیم که موجودیتها باید بروز رسانی شوند یا خیر. اگر مقدار این فیلد بزرگتر از صفر باشد، فرض بر این است که این موجودیت در دیتابیس وجود دارد بنابراین خاصیت EntityState را به Modified تغییر میدهیم. علاوه بر این فیلدی هم با نام newParentEntity تعریف کرده ایم که توسط آن بتوانیم کد وضعیت مناسبی به کلاینت بازگردانیم. در صورتی که مقدار فیلد Id در موجودیت TravelAgent برابر با یک باشد، مقدار خاصیت EntityState را به همان Added رها میکنیم.
سپس تمام آبجکتهای فرزند آژانس مسافرتی (رزرواسیون ها) را بررسی میکنیم و همین منطق را روی آنها اعمال میکنیم. یعنی در صورتی که مقدار فیلد Id آنها بزرگتر از 0 باشد وضعیت EntityState را به Modified تغییر میدهیم. در نهایت متد SaveChanges را فراخوانی میکنیم. در این مرحله برای موجودیتهای جدید اسکریپتهای Insert و برای موجودیتهای تغییر کرده اسکریپتهای Update تولید میشود. سپس کد وضعیت مناسب را به کلاینت بر میگردانیم. برای موجودیتهای اضافه شده کد وضعیت 201 (Created) و برای موجودیتهای بروز رسانی شده کد وضعیت 200 (OK) باز میگردد. کد 201 به کلاینت اطلاع میدهد که رکورد جدید با موفقیت ثبت شده است، و کد 200 از بروز رسانی موفقیت آمیز خبر میدهد. هنگام تولید سرویسهای REST-based بهتر است همیشه کد وضعیت مناسبی تولید کنید.
پس از این مراحل، آژانس مسافرتی و رزرواسیون جدیدی میسازیم و آنها را به سرویس ارسال میکنیم. سپس نام آژانس مسافرتی دوم را تغییر میدهیم، و یکی از رزرواسیونها را از آژانس اولی به آژانس دومی منتقل میکنیم. اینبار هنگام فراخوانی متد Update تمام موجودیتها شناسه ای بزرگتر از 1 دارند، بنابراین وضعیت EntityState آنها را به Modified تغییر میدهیم تا هنگام ثبت تغییرات دستورات بروز رسانی مناسب تولید و اجرا شوند.
در آخر کلاینت ما متد Retreive را روی سرویس فراخوانی میکند. این فراخوانی با کمک متد GetAsync انجام میشود که روی آبجکت HttpClient تعریف شده است. فراخوانی این متد تمام آژانسهای مسافرتی بهمراه رزرواسیونهای متناظرشان را دریافت میکند. در اینجا با استفاده از متد Include تمام رکوردهای فرزند را بهمراه تمام خاصیت هایشان (properties) بارگذاری میکنیم.
دقت کنید که مرتب کننده JSON تمام خواص عمومی (public properties) را باز میگرداند، حتی اگر در کد خود تعداد مشخصی از آنها را انتخاب کرده باشید.
نکته دیگر آنکه در مثال جاری از قراردادهای توکار Web API برای نگاشت درخواستهای HTTP به اکشن متدها استفاده نکرده ایم. مثلا بصورت پیش فرض درخواستهای POST به متدهایی نگاشت میشوند که نام آنها با "Post" شروع میشود. در مثال جاری قواعد مسیریابی را تغییر داده ایم و رویکرد مسیریابی RPC-based را در پیش گرفته ایم. در اپلیکیشنهای واقعی بهتر است از قواعد پیش فرض استفاده کنید چرا که هدف Web API ارائه سرویسهای REST-based است. بنابراین بعنوان یک قاعده کلی بهتر است متدهای سرویس شما به درخواستهای متناظر HTTP نگاشت شوند. و در آخر آنکه بهتر است لایه مجزایی برای میزبانی کدهای دسترسی داده ایجاد کنید و آنها را از سرویس Web API تفکیک نمایید.
مروری مختصر بر زبان DMX
برای بسیاری داده کاوی تنها مجموعه ای از تعدادی الگوریتم تعبیر میشود؛ به همان طریقی که در گذشته تصورشان از بانک اطلاعاتی تنها ساختاری سلسله مراتبی به منظور ذخیره دادهها بود. بدین ترتیب داده کاوی به ابزاری تبدیل شده که تنها در انحصار تعدادی متخصص (بویژه PhDهای علم آمار و یادگیری ماشین) قرار دارد که آشنائی با اصطلاحات یک زمینه خاص را دارند. هدف از ایجاد زبان DMX تعریف مفاهیمی استاندارد و گزارهایی متداول است که در دنیای داده کاوی استفاده میشود به شکلی که زبان SQL برای بانک اطلاعاتی این کار را انجام میدهد.
فرضیه اساسی در داده کاوی و همچنین یادگیری ماشین از این قرار است که تعدادی نمونه به الگوریتم نشان داده میشود و الگوریتم با استفاده از این نمونهها قادر است به استخراج الگوها بپردازد. بدین ترتیب به منظور بازبینی و همچنین استنتاج از اطلاعات درباره نمونههای جدید میتواند مورد استفاده قرار گیرد.
ذکر این نکته ضروری است که الگوهای استخراج شده میتوانند مفید، آموزنده و دقیق باشند. تصویر زیر به اختصار مراحل فرآیند داده کاوی را نمایان میسازد:
در گام نخست اقدام به تعریف مسئله و فرموله کردن آن میکنیم که اصطلاحاً Mining Model نامیده میشود. در واقع Mining Model توصیف کننده این است که داده نمونه به چه شکل به نظر میرسد و چگونه الگوریتم داده کاوی باید دادهها را تفسیر کند. در گام بعدی به فراهم کردن نمونههای داده برای الگوریتم میپردازیم، الگوریتم با بهره گیری از Mining Model به طریقی که یک لنز دادهها را مرتب میکند، به بررسی دادهها و استخراج الگوها میپردازد؛ این عملیات را اصطلاحاً Training Model مینامیم. هنگامی که این عملیات به پایان رسید، بسته به اینکه چگونه آنرا انجام داده اید، میتوانید به تحلیل الگوهایی که توسط الگوریتم از روی نمونه هایتان بدست آمده بپردازید. و در نهایت میتوانید اقدام به فراهم کردن دادههای جدید و فرموله کردن آنها، به همان طریقی که نمونهها آموزش دیده اند، به منظور انجام پیش بینی و استنتاج از اطلاعات با استفاده از الگوهای کشف شده توسط الگوریتم پرداخت.
زبان DMX وظیفه تبدیل دادههای موجودتان (سطرها و ستونهای Tables) به دادههای مورد نیاز الگوریتمهای داده کاوی (Cases و Attributes) را دارد. به منظور انجام این تبدیل به Mining Structure و Mining Model (که در قسمت اول به شرح آن پرداخته شد) نیاز است. بطور خلاصه Mining Structure صورت مسئله را توصیف میکند و Mining Model وظیفه تبدیل سطرهای داده ای به درون Caseها و انجام عملیات یادگیری ماشین با استفاده از الگوریتم داده کاوی مشخص شده را بر عهده دارد.
Syntax زبان DMX
مشابه زبان SQL دستورات زبان DMX نیز به محیطی جهت اجرا نیاز دارند که میتوان با استفاده از (SQL Server Management Studio (SSMS به اجرای دستورات DMX اقدام نمود. ایجاد ساختار کاوش (Mining Structure) و مدل کاوشی (Mining Model) مشابه دستورات ایجاد Table در زبان SQL میباشد. همانطور که اشاره شد، گام اول (از سه مرحله اصلی در داده کاوی) ایجاد یک مدل کاوش است؛ شامل تعیین تعداد ستونهای ورودی، ستونهای قابل پیش بینی و مشخص کردن نام الگوریتم مورد استفاده در مدل. گام دوم آموزش مدل که پردازش نیز نامیده میشود و گام سوم مرحله پیش بینی است که نیاز به یک مدل کاوش آموزش دیده و مجموعه اطلاعات جدید دارد. در طول پیش بینی، موتور داده کاوی قوانین (Rules) پیدا شده در مرحلهی آموزش (یادگیری) را با مجموعه اطلاعات جدید تطبیق داده و نتیجه پیش بینی را برای هر Case ورودی انجام میدهد. دو نوع پرس و جوی پیش بینی وجود دارد Batch و Singleton که به ترتیب چند Case ورودی دارد و خروجی در یک جدول ذخیره میشود و دیگری تنها یک Case ورودی دارد و خروجی در زمان اجرا ساخته میشود.
در زبان DMX دو روش برای ساخت مدلهای کاوش وجود دارد:
• ایجاد یک ساختار کاوش و مدل کاوش مربوط به هم و تحت یک نام، زمانی کاربرد دارد که یک ساختار کاوش فقط شامل یک مدل کاوش باشد.
• ایجاد یک ساختار کاوش و سپس اضافه نمودن یک مدل کاوش به ساختار تعریف شده، زمانی کاربرد دارد که یک ساختار کاوش شامل چندین مدل کاوشی باشد. دلایل مختلفی وجود دارد که ممکن است نیاز به این روش باشد، برای مثال ممکن است مدلهای متعددی را با استفاده از الگوریتمهای مختلف ساخت و سپس بررسی نمود که کدام مدل بهتر عمل خواهد کرد و یا مدلهای متعددی را با استفاده از یک الگوریتم ولی با مجموعه پارامترهای متفاوت برای هر مدل ساخت و سپس بهترین را انتخاب نمود.
عناصر سازندهی ساختار کاوش، ستونهای ساختار کاوشی هستند که داده هایی را که منبع اصلی داده فراهم میکند، توصیف میکند. این ستونها شامل اطلاعاتی از قبیل نوع داده (Data Type)، نوع محتوا (Content Type)، ماهیت داده و اینکه داده چگونه توزیع شده است میباشند. نوع محتوا پیوسته و یا گسسته بودن آن را مشخص میکند و بدین ترتیب به الگوریتم راه درست مدل کردن ستون را نشان میدهیم. کلمه کلیدی Discrete برای ماهیت گسسته داده و از کلمه Continuous برای ماهیت پیوسته داده استفاده میشود. مقادیر نوع داده و نوع محتوا به قرار زیر میباشند:
Data Type | کاربرد |
LONG | اعداد صحیح |
DOUBLE | اعداد اعشاری |
TEXT | دادههای رشته ای |
DATE | دادههای تاریخی |
BOOLEAN | دادههای منطقی (True و False) |
TABLE | برای تعریف Nested Case |
Content Type | کاربرد |
KEY | مشخص کننده کلید |
DISCRETE | دادههای گسسته |
CONTINUOUS | دادههای پیوسته |
DISCRETIZED | دادههای گسسته شده |
KEY TIME | کلید زمان، تنها در مدلهای Time Series استفاده میشود |
KEY SEQUENCE | کلید توالی، تنها در بخش Nested Table مدلهای Sequence Clustering استفاده میشود |
همچنین یک مدل کاوش استفاده و کاربرد هر ستون و الگوریتمی که برای ساخت مدل استفاده میشود را تعریف میکند، میتوانید با استفاده از کلمه کلیدی Predict و یا Predict_Only خاصیت پیش بینی را به ستونها اضافه نمود، برای نمونه به دستورات زیر توجه نمائید:
CREATE MINING STRUCTURE [New Mailing] ( CustomerKey LONG KEY, Gender TEXT DISCRETE, [Number Cars Owned] LONG DISCRETE, [Bike Buyer] LONG DISCRETE ) GO ALTER MINING STRUCTURE [New Mailing] ADD MINING MODEL [Naive Bayes] ( CustomerKey, Gender, [Number Cars Owned], [Bike Buyer] PREDICT ) USING Microsoft_Naive_Bayes
به منظور آموزش یک مدل کاوش از دستور Insert به شکل زیر استفاده میشود:
INSERT INTO <mining model name> [<mapped model columns>] <source data query>
در ادامه به شکل عملی میتوانید با طی مراحل و اجرای کوئریهای زیر به بررسی بیشتر موضوع بپردازید.
ابتدا به سرویس SSAS متصل شوید و اقدام به ایجاد یک Database با تنظیمات پیش فرض (مثلاً با نام DM-02) نمائید و در ادامه کوئری XMLA زیر را جهت ایجاد Data Source ای به بانک AdventureWorksDW2012 موجود روی دستگاه تان، اجرا نمائید.
<Create xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"> <ParentObject> <DatabaseID>DM-02</DatabaseID> </ParentObject> <ObjectDefinition> <DataSource xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2" xmlns:ddl100_100="http://schemas.microsoft.com/analysisservices/2008/engine/100/100" xmlns:ddl200="http://schemas.microsoft.com/analysisservices/2010/engine/200" xmlns:ddl200_200="http://schemas.microsoft.com/analysisservices/2010/engine/200/200" xmlns:ddl300="http://schemas.microsoft.com/analysisservices/2011/engine/300" xmlns:ddl300_300="http://schemas.microsoft.com/analysisservices/2011/engine/300/300" xmlns:ddl400="http://schemas.microsoft.com/analysisservices/2012/engine/400" xmlns:ddl400_400="http://schemas.microsoft.com/analysisservices/2012/engine/400/400" xsi:type="RelationalDataSource"> <ID>Adventure Works DW2012</ID> <Name>Adventure Works DW2012</Name> <ConnectionString>Provider=SQLNCLI11.1;Data Source=(local);Integrated Security=SSPI; Initial Catalog=AdventureWorksDW2012</ConnectionString> <ImpersonationInfo> <ImpersonationMode>ImpersonateCurrentUser</ImpersonationMode> </ImpersonationInfo> <Timeout>PT0S</Timeout> </DataSource> </ObjectDefinition> </Create>
/* Step 1 */ CREATE MINING MODEL [NBSample] ( CustomerKey LONG KEY, Gender TEXT DISCRETE, [Number Cars Owned] LONG DISCRETE, [Bike Buyer] LONG DISCRETE PREDICT ) USING Microsoft_Naive_Bayes Go /* Step 2 */ INSERT INTO NBSample (CustomerKey, Gender, [Number Cars Owned], [Bike Buyer]) OPENQUERY([Adventure Works DW2012],'Select CustomerKey, Gender, [NumberCarsOwned], [BikeBuyer] FROM [vTargetMail]') /* */ SELECT * FROM [NBSample].CONTENT /* */ SELECT * FROM [NBSample_Structure].CASES /* Step 3*/ SELECT FLATTENED MODEL_NAME, (SELECT ATTRIBUTE_NAME, ATTRIBUTE_VALUE, [SUPPORT], [PROBABILITY], VALUETYPE FROM NODE_DISTRIBUTION) AS t FROM [NBSample].CONTENT WHERE NODE_TYPE = 26
در ادامه روشهای مختلف ارسال درخواستهای Ajax را توسط jQuery و همچنین معادلهای XMLHttpRequest و Fetch API آنها بررسی میکنیم.
ارسال درخواستهای GET
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد |
توسط jQuery |
fetch('/my/name').then(function(response) { if (response.ok) { return response.text(); } else { throw new Error(); } }).then( function success(name) { console.log('my name is ' + name); }, function failure() { console.error('Name request failed!'); } ); | var xhr = new XMLHttpRequest(); xhr.open('GET', '/my/name'); xhr.onload = function() { if (xhr.status >= 400) { console.error('Name request failed!'); } else { console.log('my name is ' + xhr.responseText); } }; xhr.onerror = function() { console.error('Name request failed!'); }; xhr.send(); | $.get('/my/name').then( function success(name) { console.log('my name is ' + name); }, function failure() { console.error('Name request failed!'); } ); |
jQuery برای پشتیبانی از درخواستهای Ajax، متد ویژهای را به نام ()ajax ارائه میکند که برای ارسال درخواستهایی از هر نوع، مانندGET، POST و غیره کاربرد دارد. همچنین برای بعضی از نوعها، متدهای کوتاهتری را مانند ()get و ()post نیز در اختیار برنامه نویس قرار میدهد. جاوا اسکریپت خالص و Web API مرورگرها نیز دو شیء XMLHttpRequest و fetch را برای ارسال درخواستهای غیرهمزمان، ارائه میکند. XMLHttpRequest در تمام مرورگرهای قدیمی و جدید پشتیبانی میشود، اما fetch API مدتی است که در غالب مرورگرهای امروزی در دسترس است. در جدول فوق روش ارسال درخواستهای Ajax از نوع GET را توسط این سه روش مشاهده میکنید.
در این مثالها درخواستی به آدرس my/name سمت سرور ارسال شده و انتظار میرود که یک plaintext حاوی متن کاربر بازگشت داده شود که در نهایت در console لاگ میشود.
- در حالت استفادهی از jQuery در صورت بازگشت موفقیت آمیز پاسخی از طرف سرور، متد success و در غیراینصورت متد failure اجرا میشود. باید درنظر داشت که متد ajax جیکوئری، چیزی بیشتر از یک محصور کنندهی اشیاء XMLHttpRequest نیست.
- در حالت کار با XMLHttpRequest باید اندکی بیشتر تایپ کرد؛ اما اصول کار یکی است. در اینجا onload زمانی فراخوانی میشود که پاسخی از سرور دریافت شده و عملیات خاتمه یافتهاست؛ هرچند این پاسخ میتواند یک خطا نیز باشد. به همین جهت باید status آنرا بررسی کرد. اگر رخداد onerror فراخوانی شد، یعنی درخواست، در سطوح بسیار پایین آن مانند بروز یک خطای CORS با شکست مواجه شدهاست.
همانطور که مشاهده میکنید در حالت کار با XMLHttpRequest جاوا اسکریپت از اشیاء Promise پشتیبانی نمیکند که این کمبود با معرفی fetch API برطرف شدهاست که نمونهای از آنرا با متد then متصل به fetch مشاهده میکنید؛ دقیقا مشابه متد ajax جیکوئری که آن نیز یک Promise را بازگشت میدهد.
تفاوت Promise جیکوئری با fetch API در این است که جیکوئری در صورتیکه یک status code بیانگر خطا را دریافت کند، قسمت failure را اجرا میکند؛ اما fetch API مانند اشیاء XMLHttpRequest تنها در صورت بروز خطاهای سطح پایین درخواست، این متد را فراخوانی خواهد کرد. هرچند اگر در اینجا response.ok نبود، میتوان با صدور یک استثناء به رفتاری شبیه به jQuery رسید و قسمت then failure را به صورت خودکار اجرا کرد.
ارسال درخواستهای Ajax از نوع POST ، PUT و DELETE
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/name', { method: 'POST', body: 'some data' }); | var xhr = new XMLHttpRequest(); xhr.open('POST', '/user/name'); xhr.send('some data'); | $.ajax({ method: 'POST', url: '/user/name', contentType: 'text/plain', data: 'some data' }); |
اگر از شیء XMLHttpRequest استفاده شود، Content-Type آن به صورت پیشفرض به text/plain تنظیم شدهاست. در اینجا بدنهی درخواست به متد send ارسال میشود.
متد fetch نیز همانند شیء XMLHttpRequest دارای Content-Type پیشفرض از نوع text/plain است. در اینجا بدنهی درخواست به خاصیت body انتساب داده میشود.
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/1', { method: 'PUT', body: //record including new mobile number }); | var xhr = new XMLHttpRequest(); xhr.open('PUT', '/user/1'); xhr.send(/* record including new data */); | $.ajax({ method: 'PUT', url: '/user/1', contentType: 'text/plain', data: //record including new data }); |
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/1', {method: 'DELETE'}); | var xhr = new XMLHttpRequest(); xhr.open('DELETE', '/user/1'); xhr.send(); | $.ajax('/user/1', {method: 'DELETE'}); |
مدیریت Encoding درخواستهای Ajax
در مثالهای قبل، اطلاعاتی از نوع text/plain را به سمت سرور ارسال کردیم که به آن encoding type نیز گفته میشود. برای تکمیل بحث میتوان حالتهای دیگری مانند application/x-www-form-urlencoded، application/json و یا multipart/form-data را که در برنامههای کاربردی زیاد مورد استفاده قرار میگیرند، بررسی کرد.
کار با URL Encoding
عموما URL encoding در دو قسمت آدرس درخواستی به سرور و یا حتی بدنهی درخواست ارسالی تنظیم میشود. MIME type آن نیز application/x-www-form-urlencoded است و اطلاعات آن شامل یکسری جفت کلید/مقدار است. برای متدهای ارسال از نوع GET و DELETE، اطلاعات آن در انتهای آدرس درخواستی و برای سایر حالات در بدنهی درخواست ذکر میشوند.
در جیکوئری با استفاده از متد param آن میتوان یک شیء جاوا اسکریپتی را به معادل URL-encoded string آنها تبدیل کرد:
$.param({ key1: 'some value', 'key 2': 'another value' });
key1=some+value&key+2=another+value
$.ajax({ method: 'POST', url: '/user', data: { name: 'VahidN', address: 'Address 1', phone: '555-555-5555' } });
برخلاف جیکوئری در حین کار با روشهای جاوا اسکریپتی خالص این encoding باید به صورت دستی و صریحی انجام شود. برای این منظور دو متد استاندارد encodeURI و encodeURIComponent در جاوا اسکریپت مورد استفاده قرار میگیرند. هدف متد encodeURI اعمال آن بر روی یک URL کامل است و یا کلید/مقدارهای جدا شدهی توسط & مانند first=Vahid&last=N. اما متد encodeURIComponent صرفا جهت اعمال بر روی یک تک مقدار طراحی شدهاست.
به این ترتیب معادل جاوا اسکریپتی قطعه کد جیکوئری فوق به صورت زیر است:
var xhr = new XMLHttpRequest(); var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555'); xhr.open('POST', '/user'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(data);
روش انجام اینکار توسط fetch API به صورت زیر است:
var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555'); fetch('/user', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: data });
function param(object) { var encodedString = ''; for (var prop in object) { if (object.hasOwnProperty(prop)) { if (encodedString.length > 0) { encodedString += '&'; } encodedString += encodeURI(prop + '=' + object[prop]); } } return encodedString; }
کار با JSON Encoding
در عمل JSON نمایش رشتهای یک شیء جاوا اسکریپتی است و هدف آن سهولت نقل و انتقالات این اشیاء به سرور و برعکس است. برخلاف حالت application/x-www-form-urlencoded که اطلاعات آن مسطح است، حالت application/json امکان ارسال اطلاعات سلسله مراتبی را نیز میسر میکند (مانند مثال زیر که phone آن دیگر مسطح نیست و خود آن نیز یک شیء جاوا اسکریپتی است).
در جیکوئری برای ارسال اشیاء جاوا اسکریپتی JSON Encoded به سمت سرور از روش زیر استفاده میشود:
$.ajax({ method: 'POST', url: '/user', contentType: 'application/json', data: JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); });
پیاده سازی همین مثال با جاوا اسکریپت خالص و XMLHttpRequest استاندارد به صورت زیر است:
var xhr = new XMLHttpRequest(); var data = JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); xhr.open('POST', '/user'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(data);
در این حالت اگر خروجی سرور نیز JSON باشد، روش دریافت و پردازش آن به صورت زیر است:
var xhr = new XMLHttpRequest(); xhr.open('GET', '/user/1'); xhr.onload = function() { var user = JSON.parse(xhr.responseText); // do something with this user JavaScript object }; xhr.send();
اگر از مرورگر IE صرفنظر کنیم، تمام مرورگرهای دیگر دارای خاصیتی به نام xhr.response نیز هستند که نیاز به تبدیل و Parse دستی رشتهی دریافتی را حذف میکند؛ از این جهت که این خاصیت حاوی شیء جاوا اسکریپتی معادل بدنهی response دریافتی از سمت سرور است. البته با این شرط که سرور، Content-Type مساوی application/json را برای response تنظیم کرده باشد.
و روش انجام این عملیات توسط fetch API به صورت زیر است:
fetch('/user', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); });
و یا اگر بخواهیم اطلاعات JSON دریافتی از سمت سرور را در اینجا پردازش کنیم، روش کار به صورت زیر است:
fetch('/user/1').then(function(response) { return response.json(); }).then(function(userRecord) { // do something with this user JavaScript object });
همین مثال را اگر بخواهیم توسط ECMAScript 2016 و Arrow functions آن بازنویسی کنیم، به قطعه کد زیر میرسیم:
fetch('/user/1') .then(response => response.json()) // Transform the data into json .then(userRecord => { // do something with this userRecord object });
کار با Multipart Encoding
نوع دیگری از encoding که بیشتر با فرمهای HTML بکار میرود، multipart/form-data نام دارد:
<form action="my/server" method="POST" enctype="multipart/form-data"> <label>First Name: <input name="first"> </label> <label>Last Name: <input name="last"> </label> <button>Submit</button> </form>
-----------------------------1686536745986416462127721994 Content-Disposition: form-data; name="first" Vahid -----------------------------1686536745986416462127721994 Content-Disposition: form-data; name="last" N -----------------------------1686536745986416462127721994--
روش ارسال اطلاعات با این نوع encoding خاص به سمت سرور توسط متد ajax جیکوئری به صورت زیر است:
var formData = new FormData(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); $.ajax({ method: 'POST', url: '/user', contentType: false, processData: false, data: formData });
در اینجا ذکر processData: false ضروری است. در غیراینصورت jQuery این اطلاعات را به یک URL-encoded string تبدیل میکند. همچنین با اعلام contentType: false، جیکوئری در کار مرورگر دخالت نمیکند. از این جهت که هدر ویژهی این نوع درخواستها توسط خود مرورگر تنظیم میشود و برای مثال یک چنین شکلی را دارد:
multipart/form-data; boundary=—————————1686536745986416462127721994
روش انجام اینکار با XMLHttpRequest و همچنین fetch API به صورت زیر است:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد |
var formData = new FormData(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); fetch('/user', { method: 'POST', body: formData }); | var formData = new FormData(), xhr = new XMLHttpRequest(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); xhr.open('POST', '/user'); xhr.send(formData); |
آپلود فایلها توسط درخواستهای Ajax ایی
تنها راه آپلود فایلها در مرورگرهای قدیمی که شامل IE 9.0 هم میشود، تعریف المان <"input type="file> در داخل المان <form> و سپس submit مستقیم آن فرم است. برای رفع این مشکل در مرورگرهای پس از IE 9.0 و پشتیبانی از Ajax، جهت آپلود فایلها، استاندارد XMLHttpRequest Level 2 معرفی شدهاست. در این حالت اگر المان <input type=file> در صفحه وجود داشته باشد، روش ارسال Ajax ایی آن به سمت سرور به صورت زیر است:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
var file = document.querySelector( 'INPUT[type="file"]').files[0]; fetch('/uploads', { method: 'POST', body: file }); } | var file = document.querySelector( 'INPUT[type="file"]').files[0], xhr = new XMLHttpRequest(); xhr.open('POST', '/uploads'); xhr.send(file); | var file = $('INPUT[type="file"]')[0].files[0]; $.ajax({ method: 'POST', url: '/uploads', contentType: false, processData: false, data: file }); |
یک نکته: اگر نیاز به آپلود بیش از یک فایل را داشتید و همچنین در اینجا نیاز به اطلاعات دیگری مانند سایر فیلدهای فرم نیز وجود داشت، از همان روش تعریف new FormData و افزودن اطلاعات مورد نیاز به آن استفاده کنید. امکان افزودن شیء file نیز به FormData پیش بینی شدهاست.
دانلود فایلها توسط درخواستهای Ajax ایی
پیشتر در حین بررسی JSON encoding توسط fetch API از متد ()json برای تبدیل اطلاعات دریافتی از سرور به json و بازگشت آن به صورت یک Promise استفاده کردیم:
fetch(url) .then((resp) => resp.json()) // Transform the data into json .then(function(data) { // use data object }) })
- ()clone یک کپی از response را تهیه میکند.
- ()redirect یک response جدید را با URL دیگری ایجاد میکند.
- ()arrayBuffer یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء ArrayBuffer تبدیل میکند.
- ()formData یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء FormData تبدیل میکند.
- ()blob یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء Blob تبدیل میکند.
- ()text یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به string تبدیل میکند.
- ()json یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء جاوا اسکریپتی تبدیل میکند.
در اینجا متدی که میتواند برای تبدیل یک byte array بازگشتی از سرور به فایل قابل دریافت در سمت کلاینت مورد استفاده قرار گیرد، متد blob است:
function downloadBlob() { fetch('/Home/InMemoryReport') .then(function(response) { return response.blob(); }) .then(function(xlsxBlob) { var a = document.createElement("a"); document.body.appendChild(a); a.style = "display: none"; let url = window.URL.createObjectURL(xlsxBlob); a.href = url; a.download = "report.xlsx"; a.click(); window.URL.revokeObjectURL(url); }); }
ارسال درخواستهای Ajax به دومینهای دیگر (CORS)
گاهی از اوقات نیاز است اطلاعاتی را توسط درخواستهای Ajax، به سروری دیگر در دومینی دیگر ارسال و یا دریافت کرد. هرچند انجام اینکار به صورت مستقیم و خارج از مرورگر بدون مشکل قابل انجام است، اما مرورگرها برای درخواستهای جاوا اسکریپتی محدودیت «same-origin policy» را اعمال میکنند. به این معنا که XMLHttpRequest بین دومینها به صورت پیشفرض ممنوع است. برای ارسال درخواستهای مجاز و از پیش مشخص شدهی Ajax بین دومینها، تاکنون دو روش پیش بینی شدهاست:
الف) روش JSONP
«same-origin policy» از شروع ارسال درخواستی به خارج از دومین جاری، جلوگیری میکند. هرچند این مورد به درخواستهای XMLHttpRequest اعمال میشود، اما در مورد المانهایی از نوع <a>، <img> و <script> صادق نیست و آنها محدود به این سیاست امنیتی نیستند. روش «JavaScript Object Notation with Padding» و یا به اختصار JSONP از یکی از همین استثناءها جهت ارسال درخواستهایی به سایر دومینها استفاده میکند. البته نام این روش کمی غلط انداز است؛ از این جهت که در این فرآیند اصلا JSON ایی مورد استفاده قرار نمیگیرد؛ خروجی سرور در این حالت یک تابع جاوا اسکریپتی است و نه JSON.
روش انجام این نوع درخواستها را توسط جیکوئری در ذیل مشاهده میکنید:
$.ajax('http://jsonp-aware-endpoint.com/user/1', { jsonp: 'callback', dataType: 'jsonp' }).then(function(response) { // handle user info from server });
انجام اینکار بدون jQuery و در حقیقت کاری که jQuery در پشت صحنه برای ایجاد تگ script انجام میدهد، چنین چیزی است:
window.myJsonpCallback = function(data) { // handle user info from server }; var scriptEl = document.createElement('script'); scriptEl.setAttribute('src', 'http://jsonp-aware-endpoint.com/user/1?callback=myJsonpCallback'); document.body.appendChild(scriptEl);
ب) روش CORS
CORS و یا Cross Origin Resource Sharing روش مدرن و پذیرفته شدهی ارسال درخواستهای Ajax در بین دومینها است و دارای دو نوع ساده و غیرساده است. نوع سادهی آن به همراه هدر مخصوص Origin است که جهت بیان دومین ارسال کنندهی درخواست بکار میرود و تنها از encodingهای “text/plain” و “application/x-www-form-urlencoded” پشتیبانی میکند. نوع غیرسادهی آن که این روزها بیشتر بکار میرود، از نوع «preflight» است. Preflight در اینجا به این معنا است که زمانیکه درخواست Ajax ایی را به دومین دیگری ارسال کردید، پیش از ارسال، مرورگر یک درخواست از نوع OPTIONS را به سمت سرور مقصد ارسال میکند. در این حالت اگر سرور مجوز مناسبی را صادر کرد، آنگاه مرورگر اصل درخواست را به سمت آن سرور ارسال میکند. به همین جهت در این حالت به ازای هر درخواستی که در برنامه ارسال میشود، در برگهی network مرورگر، دو درخواست را مشاهده خواهید کرد. درخواست preflight از نوع OPTIONS به صورت خودکار توسط مرورگر مدیریت میشود و نیازی به کدنویسی خاصی ندارد.
مدیریت کوکیها در درخواستهای Ajax
اگر درخواست Ajax ایی را به دومین دیگری ارسال کنید، به صورت پیشفرض به همراه کوکیهای مرتبط نخواهد بود. برای رفع این مشکل نیاز است خاصیت withCredentials را به true تنظیم کنید:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('http://someotherdomain.com', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, credentials: 'include' }); | var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://someotherdomain.com'); xhr.withCredentials = true; xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.send('sometext'); | $.ajax('http://someotherdomain.com', { method: 'POST', contentType: 'text/plain', data: 'sometext', beforeSend: function(xmlHttpRequest) { xmlHttpRequest.withCredentials = true; } }); |
یک نکتهی مهم: در fetch API حتی برای درخواستهای ساده نیز کوکیها ارسال نمیشوند. در این حالت برای کار با دومین جاری و ارسال کوکیهای کاربر به سمت سرور، باید از تنظیم 'credentials: 'same-origin استفاده کرد؛ زیرا مقدار پیشفرض آن omit است.
با آمدن SQL server 2008 استفاده از کتابخانه SQL-DMO برای انجام یک سری از امور بر روی اس کیوال سرور با استفاده از برنامه نویسی منسوخ شد. یکی از تواناییهای این کتابخانه لیست کردن سرورهای اس کیوال (قابل دسترسی) موجود در شبکه بود.
برای مثال توسط این کتابخانه به صورت زیر میتوان اینکار را انجام داد:
در قطعه کد زیر فرض بر این است که ارجاعی به کتابخانه sqldmo را در برگه com مربوط به project->add reference اضافه کردهاید:
using SQLDMO;
using System.Collections.Generic;
public static List<string> GetSQLServersList2()
{
List<string> result = new List<string>();
ApplicationClass sqlApp = new ApplicationClass();
NameList lst = sqlApp.ListAvailableSQLServers();
for (int i = 1; i <= lst.Count; i++)
result.Add(lst.Item(i));
lst = null;
sqlApp = null;
return result;
}
using System.Collections.Generic;
using System.Data;
using System.Data.Sql;
public class CListServers
{
public static List<string> GetSQLServersList()
{
List<string> result = new List<string>();
// Retrieve the enumerator instance and then the data.
var instance = SqlDataSourceEnumerator.Instance;
var table = instance.GetDataSources();
// Display the contents of the table.
foreach (DataRow row in table.Rows)
{
result.Add(string.Format("{0}\\{1}", row[0], row[1]));
}
return result;
}
}
کتابخانه COM یاد شده (SQL-DMO) در SQL server 2008 با کتابخانه SMO جایگزین شده است.
در این حالت خواهیم داشت:
using System.Collections.Generic;
using System.Data;
using Microsoft.SqlServer.Management.Smo;
public class CListServers
{
public static List<string> GetSQLServersListSMO()
{
List<string> result = new List<string>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
result.Add(dr["Name"].ToString());
}
}
return result;
}
}