- اول، ابزار Microsoft ASP.NET Scaffolding را از منوی Tools گزینه Extensions and Updates دریافت و نصب نمایید.
- دوم پروژه جدیدی از نوع Visual C# ASP.NET Web Forms Application با فریم ورک 4.5 ایجاد نمایید.
- از پنجره NuGet Package manager با دستور install کتابخانه ASP.NET Web Forms Scaffold Generator را دریافت نمایید
install-package Microsoft.AspNet.Scaffolding.WebForms -pre
- کلاس Person را مانند زیر در فولدر Models ایحاد نماییدویژگی ScaffoldColumn را برای ID، برابر false قرار دهید تا از ایجاد این ستون جلوگیری نمائید.
public class Person { [ScaffoldColumn(false)] public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
- پروژه را Build نمایید.
- بر روی پروژه راست کلیک و از گزینه Add، گزینه ...Scaffold را انتخاب نمایید.
- از پنجره Add Scaffold باز شده بر روی گزینه Add، کلیک کنید.
- پنجره
Add Web Forms Pages مانند زیر باز میشود که امکان انتخاب کلاس،Data Context و MasterPage فراهم میباشد.
- از گزینه Data Context class گزینه New Data Context را انتخاب نمایید. صفحات مورد نیاز را در فولدر Views/Person ایجاد مینمایید.
- کدهای تولید شده را میتوانید بازبینی نمایید پروژه را اجرا تا خروجی کار را مشاهده نمایید.
- Installation
- Creating a SQL Server Database Project
- Modify Database Schema
- Schema Compare and Update
- Snapshot Project
- Publish
- نصب Sql Server Data Tools
آخرین نسخه این ابزار را از این آدرس دانلود کنید یا زمان نصب Sql Server 2012 گزینه آن را انتخاب کنید
- ایجاد یک پروژه جدید از نوع
SQL Server Database Project
پس از نصب SSDT شما از طریق Visual Studio 2012 Shell که همراه SqlServer 2012 نصب میشود یا با Visual Stadio 2012 یک پروژه جدید از نوع SQL Server Database Project ایجاد کنید.
Server Database Project Project به شما امکان توسعه پایگاه داده Sql Server را با استفاده از محیط یکپارچه Visual Studio با در اختیار گذاشتن ابزارهای همچون navigation, intellisense, validation, debugging, declarative editing و غیره را میدهد. شی پایگاه داده شما در پروژه و فایلهای مجزا ذخیره میشود مثل آن که شما در حال توسعه برنامه #C یا VB.NET هستید. - ورود پایگاه داده موجود به پروژه SQL Server Database
شما میتوانید شمای پایگاه داده موجود یا SQL Server DAC Package File (.dacpac) یا هر T-SQL دیگری را با راست کلیک بر روی پروژه و انتخاب گزینه Import به پروژه خود اضافه و تغییرات لازم را اعمال نماید شکل زیر پنجره Import Database را نشان میدهد:
پنجره Solution Explorer امکانات زیر را در اختیار شما قرارمی دهد:
- اضافه کردن اشیای همچون Table، View و غیره با راست کیلک کردن بر روی پروژه و انتخاب گزینه Add
- ویرایش اشیا موجود با دبل کیلک کردن بر روی اشیا
- مقایسه شمای پایگاه داده با پایگاه داده دیگر و یا Microsoft SQL Server DAC Package File کافیست بر روی پروژه راست کیلک و گزینه Schema Compare را انتخاب نمایید.
- ایجاد یک Snapshot از شمای پایگاه داده در یک Microsoft SQL Server DAC Package File . یک snapshot برای ایجاد یک پایگاه داده یا ورود در پروژه ای دیگر یا در مقایسه دو پایگاه داده کاربرد دارد.
- انتشار پایگاه داده که امکان ایجاد پایگاه داده یر روی یک سرور SQL Server را فراهم مینماید.
- مقایسه شمای دو پایگاه داده با هم
SSDT ابزاری برای مقایسه دو پایگاه داده و بروز رسانی پایگاه داده مقصد از روی شمای منبع از طریق ایجاد یک Script یا به صورت مستقیم را میدهد.
- انتشار پروژه با استفاده از SQL Server Data Tools
این ابزار امکان گسترش پروژه شما بر روی SQL Server 2005, 2008, 2008 R2, 2012, یا SQL Azure instance را میدهد. برای انتشار کافیت از پنجره Solution Explorer بر روی پروژه راست کیلک و گزینه Publish را انتخاب کنید.
- پنجره SQL Server Object Explorer
این پنجره با اتصال به سرور SQL Server امکان دسترسی و مدیریت پایگاه داده را به ما میدهد. علاوه بر دسترسی به اجزای یک پایگاه داده امکان مدیریت پوشههای Security , Server Objects نیز فراهم میباشد. همچنین امکان اتصال به Sql Server Express , localdb نیز وجود دارد.
3) اگر دو کامپوننت مجزا (Sibling Component) قصد به اشتراک گذاری اطلاعاتی را داشتند تکلیف چیست؟ (هر چند با استفاده از یک کامپوننت Parent مشترک مقدور میباشد)
استفاده از Event Bus:
با استفاده از EventBus، بسیاری از معایب مطرح شده در روش قبلی را نخواهیم داشت:
تعریف Event Bus: یک Design Pattern ^ ,^ می باشد. در Vue.js یک نمونه از vue را بصورت سراسری (global) ایجاد میکنیم و درکامپوننتهایی که نیاز به ارتباط دارند، آن را فراخوانی (import) و با استفاده از متدهای emit$ و on$، ارتباط را ایجاد میکنیم.
یک فایل جاوا اسکریپتی را با نامی دلخواه (eventBus.js) در فولدر src ایجاد میکنیم و یک نمونه از Vue را در آن وهله سازی میکنیم:
import Vue from 'vue' export default new Vue()
در کامپوننت Shop-Button-Add، کد زیر را در قسمت script اضافه میکنیم:
import EventBus from "../eventBus";
کد تابع buttonClicked را بشکل زیر تغییر میدهیم:
buttonClicked() { EventBus.$emit("shop-button-clicked", this.item); }
در کامپوننت App.vue هم کد زیر را در قسمت script اضافه میکنیم:
import EventBus from "./eventBus";
و در تابع mounted که از توابع life cycle مربوط به Vue.js میباشد، کد زیر را اضافه میکنیم:
mounted() { EventBus.$on("shop-button-clicked", item => { this.updateCart(item); }); }
مقایسهی روش استفاده از EventBus با روش قبلی :
مراحل انجام کار در روش قبلی:
دو کامپوننت ارتباط داشتند Shop-Button-Add و App.vue:
1) در کامپوننت Shop-Button-Add با زدن دکمه Add To Cart متد buttonClicked اجرا میشد.
2) متد buttonClicked یک سیگنال به کامپوننت Parent خود (Shop-Item) ارسال مینمود.
this.$emit('button-clicked')
3) در کامپوننت Shop-Item مشخص شده بود در صورت ارسال سیگنال از Shop-Button-Add، متد addToCart اجرا شود.
<Shop-Button-Add @button-clicked="addToCart(item)" :item="item"> <p>Add To Cart</p> </Shop-Button-Add>
4) اجرای متد addToCart در کامپوننت Shop-Item یک سیگنال را به کامپوننت App.vue به همراه دیتای مورد نظر ارسال مینمود.
addToCart(item) { this.$emit('update-cart', item) }
5) در کامپوننت App.vue مشخص شده بود با ارسال سیگنال از کامپوننت Shop-Item، متد updateCart اجرا شود.
<shop-item v-for="item in this.items" :item="item" :key="item.id" @update-cart="updateCart"> </shop-item>
6) در نهایت کار بروزرسانی سبد خرید با اجرای متد updateCart انجام میشد:
updateCart(e) { this.cart.push(e); this.total = this.shoppingCartTotal; }
نتیجه گیری:
مزایای استفاده از Event Bus :
1) کم شدن مراحل ارتباط بین کامپوننتها
2) حل مشکل ارتباطی بین Sibling Component
3) نوشتن کد کمتر
کد سبد خرید به روش مقالهی جاری-استفاده از EventBus
نکته: برای اجرای برنامه و دریافت پکیجهای مورد استفاده در مثال جاری، نیاز است دستور زیر را اجرا کنید:
npm install
همچنین نیاز هست تا پکیچ node-sass را نیز با دستور زیر برای این مثال نصب کنید.
npm install node-sass
// Extending the built-in validator validateEmail(user, errors);
ایجاد یک پروژهی جدید Blazor WASM
برای تکمیل پیاده سازی قسمت سمت کلاینت پروژهی این سری، نیاز به یک پروژهی جدید Blazor WASM را داریم که میتوان آنرا با اجرای دستور dotnet new blazorwasm در یک پوشهی خالی، ایجاد کرد. کدهای این پروژه را میتوانید در پوشهی HotelManagement\BlazorWasm\BlazorWasm.Client فایل پیوستی انتهای بحث مشاهده کنید.
افزودن فایلهای جاوااسکریپتی مورد نیاز
شبیه به کاری که در مطلب «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» انجام دادیم، در اینجا هم قصد افزودن یکسری کتابخانهی جاوااسکریپتی و CSS ای را داریم که توسط LibMan آنها را مدیریت خواهیم کرد.
- بنابراین در ابتدا به پوشهی BlazorWasm.Client\wwwroot\css وارد شده و پوشههای پیشفرض bootstrap و open-iconic آنرا حذف میکنیم؛ چون تحت مدیریت هیچ package manager ای نیستند و در این حالت، مدیریت به روز رسانی و یا بازیابی آنها به صورت خودکار میسر نیست.
- سپس فایل wwwroot\css\app.css را هم ویرایش کرده و سطر زیر را از ابتدای آن حذف میکنیم:
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
dotnet tool update -g Microsoft.Web.LibraryManager.Cli libman init libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap libman install open-iconic --provider unpkg --destination wwwroot/lib/open-iconic libman install jquery --provider unpkg --destination wwwroot/lib/jquery libman install toastr --provider unpkg --destination wwwroot/lib/toastr
- بعد از نصب بستههای ذکر شده، فایل wwwroot\index.html را به صورت زیر به روز رسانی میکنیم تا به مسیرهای جدید بستههای CSS و JS نصب شده، اشاره کند:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>BlazorWasm.Client</title> <base href="/" /> <link href="lib/toastr/build/toastr.min.css" rel="stylesheet" /> <link href="lib/open-iconic/font/css/open-iconic-bootstrap.min.css" rel="stylesheet" /> <link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="BlazorWasm.Client.styles.css" rel="stylesheet" /> </head> <body> <div id="app">Loading...</div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="js/common.js"></script> <script src="_framework/blazor.webassembly.js"></script> </body> </html>
- محتویات فایل wwwroot\css\app.css را هم به صورت زیر تغییر میدهیم تا یک spinner و شیوه نامههای نمایش تصاویر، به آن اضافه شوند:
.valid.modified:not([type="checkbox"]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; } .validation-message { color: red; } #blazor-error-ui { background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); display: none; left: 0; padding: 0.6rem 1.25rem 0.7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; } #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } .spinner { border: 16px solid silver !important; border-top: 16px solid #337ab7 !important; border-radius: 50% !important; width: 80px !important; height: 80px !important; animation: spin 700ms linear infinite !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%); position: absolute !important; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .room-image { display: block; width: 100%; height: 150px; background-size: cover !important; border: 3px solid green; position: relative; } .room-image-title { position: absolute; top: 0; right: 0; background-color: green; color: white; padding: 0px 6px; display: inline-block; }
window.ShowToastr = (type, message) => { if (type === "success") { toastr.success(message, "Operation Successful", { timeOut: 10000 }); } if (type === "error") { toastr.error(message, "Operation Failed", { timeOut: 10000 }); } };
- در قسمت 11، در بخش «کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی» آن، کلاس JSRuntimeExtensions را تعریف کردیم که سبب کاهش تکرار کدهای استفاده از تابع ShowToastr میشود. این فایلرا در پروژهی BlazorServer.App\Utils\JSRuntimeExtensions.cs این سری نیز استفاده کردیم. یا میتوان مجددا آنرا به پروژهی جاری کپی کرد؛ یا آنرا در یک پروژهی اشتراکی قرار داد. برای مثال اگر آنرا به پوشهی BlazorWasm.Client\Utils کپی کردیم، نیاز است فضای نام آنرا اصلاح کرده و سپس آنرا به انتهای فایل BlazorWasm.Client\_Imports.razor نیز اضافه کنیم تا در تمام کامپوننتهای برنامه قابل استفاده شود:
@using BlazorWasm.Client.Utils
تغییر و ساده سازی منوی برنامهی کلاینت
در برنامهی کلاینت جاری دیگر نمیخواهیم منوی پیشفرض سمت چپ صفحه را شاهد باشیم. به همین جهت ابتدا فایل Shared\MainLayout.razor را به صورت زیر ساده میکنیم:
@inherits LayoutComponentBase <NavMenu /> <div> @Body </div>
<nav class="navbar navbar-expand-sm navbar-dark bg-dark p-0"> <a class="navbar-brand mx-4" href="#">Navbar</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse pr-2" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"></ul> <ul class="my-0 navbar-nav"> <li class="nav-item p-0"> <NavLink class="nav-link" href="registration"> <span class="p-2"> Register </span> </NavLink> </li> <li class="nav-item p-0"> <NavLink class="nav-link" href="login"> <span class="p-2"> Login </span> </NavLink> </li> </ul> </div> </nav>
تغییر محتوای صفحهی آغازین برنامه
صفحهی ابتدایی برنامه، یعنی کامپوننت Pages\Index.razor را نیز به صورت زیر تغییر میدهیم:
@page "/" <form> <div class="row p-0 mx-0 mt-4"> <div class="col-12 col-md-5 offset-md-1 pl-2 pr-2 pr-md-0"> <div class="form-group"> <label>Check In Date</label> <input type="text" class="form-control" /> </div> </div> <div class="col-8 col-md-3 pl-2 pr-2"> <div class="form-group"> <label>No. of nights</label> <select class="form-control"> @for (var i = 1; i <= 10; i++) { <option value="@i">@i</option> } </select> </div> </div> <div class="col-4 col-md-2 p-0 pr-2"> <div class="form-group"> <label> </label> <input type="submit" value="Go" class="btn btn-success btn-block" /> </div> </div> </div> </form>
تعریف View Model رابط کاربری Pages\Index.razor
پس از تعریف محتوای ثابت برنامه، اکنون نوبت به پویا سازی آن است. به همین جهت نیاز است مدلی را برای صفحهی آغازین برنامه تعریف کرد تا بتوان فرم آنرا به این مدل متصل کرد. این مدل چون مختص به برنامهی کلاینت است، آنرا در پوشهی جدید Models\ViewModels ایجاد میکنیم:
using System; namespace BlazorWasm.Client.Models.ViewModels { public class HomeVM { public DateTime StartDate { get; set; } = DateTime.Now; public DateTime EndDate { get; set; } public int NoOfNights { get; set; } = 1; } }
پس از این تعریف، بهتر است فضای نام آنرا نیز به فایل BlazorWasm.Client\_Imports.razor افزود، تا کار با آن در کامپوننتهای برنامه، سادهتر شود:
using BlazorWasm.Client.Models.ViewModels
- ابتدا فیلدی که ارائه کنندهی شیء ViewModel فرم است را تعریف میکنیم:
@code{ HomeVM HomeModel = new HomeVM(); }
<EditForm Model="HomeModel"> // ... </EditForm>
<InputDate min="@DateTime.Now.ToString("yyyy-MM-dd")" @bind-Value="HomeModel.StartDate" type="text" class="form-control" />
<select @bind="HomeModel.NoOfNights">
تعریف Local Storage سمت کلاینت
در ادامه میخواهیم اگر کاربری زمان شروع رزرو اتاقی را به همراه تعداد شب مدنظر، انتخاب کرد، با کلیک بر روی دکمهی Go، به یک صفحهی مشاهدهی جزئیات منتقل شود. بنابراین نیاز داریم تا اطلاعات انتخابی کاربر را به نحوی ذخیره سازی کنیم. برای یک چنین سناریوی سمت کلاینتی، میتوان از local storage استاندارد مرورگرها استفاده کرد که امکان کار آفلاین با برنامه را نیز فراهم میکند.
برای این منظور کتابخانهای به نام Blazored.LocalStorage طراحی شدهاست که پس از نصب آن توسط دستور زیر:
dotnet add package Blazored.LocalStorage
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddBlazoredLocalStorage(); // ... } } }
@using Blazored.LocalStorage
@page "/" @inject ILocalStorageService LocalStorage @inject IJSRuntime JsRuntime <EditForm Model="HomeModel" OnValidSubmit="SaveInitialData">
@code{ HomeVM HomeModel = new HomeVM(); private async Task SaveInitialData() { try { HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights); await LocalStorage.SetItemAsync("InitialRoomBookingInfo", HomeModel); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } }
برای مثال اگر تاریخ و عددی را انتخاب کنیم، نتیجهی حاصل از کلیک بر روی دکمهی Go را میتوان در قسمت Local storage مرورگر جاری مشاهده کرد:
البته با توجه به اینکه میخواهیم از کلید InitialRoomBookingInfo در سایر کامپوننتهای برنامه نیز استفاده کنیم، بهتر است آنرا به یک پروژهی مشترک مانند BlazorServer.Common که پیشتر نام نقشهایی مانند Admin را در آن تعریف کردیم، منتقل کنیم:
namespace BlazorServer.Common { public static class ConstantKeys { public const string LocalInitialBooking = "InitialRoomBookingInfo"; } }
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <ItemGroup> <ProjectReference Include="..\..\BlazorServer\BlazorServer.Common\BlazorServer.Common.csproj" /> </ItemGroup> </Project>
@using BlazorServer.Common
await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel);
در آخر قصد داریم با کلیک بر روی Go، به یک صفحهی جدید مانند نمایش لیست اتاقها هدایت شویم. به همین جهت کامپوننت جدید Pages\HotelRooms\HotelRooms.razor را ایجاد میکنیم:
@page "/hotel/rooms" <h3>HotelRooms</h3> @code { }
@page "/" @inject ILocalStorageService LocalStorage @inject IJSRuntime JsRuntime @inject NavigationManager NavigationManager @code{ HomeVM HomeModel = new HomeVM(); private async Task SaveInitialData() { try { HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights); await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel); NavigationManager.NavigateTo("hotel/rooms"); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-26.zip
این روش دارای تمام خواص یک تراکنش است(اصطلاحا به این خواص ACID Properties گفته میشود)
1-Atomic : به این معناست که تمام دستورات بین بلاک (دستورات SQL و سایر عملیات) باید به صورت عملیات اتمی کار کنند. یعنی یا تمام عملیات موفقیت آمیز است یا همه با شکست روبرو میشوند.
2 - Consistent: به این معناست که اگر تراکنش موفقیت آمیز بود پایگاه داده باید در شروع تراکنش بعدی تغییرات لازم رو انجام داده باشد و در غیر این صورت پایگاه داده باید به حالت قبل از شروع تراکنش برگردد.
3- Isolated: اگر چند تا تراکنش هم زمان شروع شوند اجرای هیچ کدوم از اونها نباید بر اجرای بقیه تاثیر بزاره.
4- Durable: یعنی تغییرات حاصل شده بعد از اتمام تراکنش باید دائمی باشند.
روش کار به این صورت است تمام کارهایی که قصد داریم در طی یک تراکنش انجام شوند باید در یک بلاک قرار بگیرند و تا زمانی که متد Complete فراخوانی شود. در این بلاک شما هر عملیاتی رو که به عنوان جزئی از تراکنش میدونید قرار بدید. در صورتی که کنترل اجرا به فراخوانی دستور Complete برسه تمام موارد قبل از این دستور Commit میشوند در غیر این صورت RollBack.
به مثال زیر دقت کنید.
ابتدا به پروژه مربوطه باید اسمبلی System.Transaction رو اضافه کنید.
using ( TransactionScope scope = new TransactionScope() ) { //Statement1 //Statement2 //Statement3 scope.Complete(); }
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope() ) { if ( result == 0 ) { throw new ApplicationException(); } scope.Complete(); }
نکته 1: میزان Timeout در این تراکنشها چه قدر است؟
برای بدست آوردن مقدار Timeout در این گونه تراکنشها میتوانید از کلاس TransactionManager استفاده کنید. به صورت زیر :
var defaultTimeout = TransactionManager.DefaultTimeout var maxTimeout = TransactionManager.MaximumTimeout
TransactionOptions option = new TransactionOptions(); option.Timeout = TimeSpan.MaxValue;
using ( System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope(TransactionScopeOption.Required ,option) ) { scope.Complete(); }
1 - Required : یعنی نیاز به تراکنش وجود دارد. در صورتی که تراکنش در یک تراکنش دیگر شروع شود نیاز به ساختن تراکنش جدید نیست و از همان تراکنش قبلی برای این کار استفاده میشود.
2 - RequiresNew: در هر صورت برای محدوده یک تراکنش تولید میشود.
3- Suppress : به عنوان محدوه تراکنش در نظر گرفته نمیشود.
using(TransactionScope scope1 = new TransactionScope()) { try { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress)) { //به دلیل استفاده از Suppress این محدوده خارج از تراکنش محسوب میشود } //شروع محدوده تراکنش } catch {} //Rest of scope1 }
1-این روش از تراکنشهای توزیع شده پشتیبانی میکند . یعنی میتونید از چند تا منبع داده استفاده کنید یا میتونید از یک تراکنش چند تا Connection به یک منبع داده باز کنید.(استفاده از چند تاconnection در طی یک تراکنش)
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required)) { string strCmd = "SQL to Execute"; conn = new SqlClient.SqlConnection("Connection to DB1"); conn.Open() objCmd = new SqlClient.SqlCommand(strCmd, conn); objCmd.ExecuteNonQuery(); string strCmd2 = "SQL to Execute"; conn2 = new SqlClient.SqlConnection("Connection to DB2"); conn2.Open() objCmd2 = new SqlClient.SqlCommand(strCmd2, conn2); objCmd2.ExecuteNonQuery(); }
3- با DataProviderهای متفاوت نظیر Oracle و OleDb و ODBC سازگار است.
4- از تراکنشهای تو در تو به خوبی پشتیبانی میکنه(Nested Transaction)
using(TransactionScope scope1 = new TransactionScope()) { using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required)) { ... } using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) { ... } using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress)) { ... } }
*استفاده از این روش در سیستم هایی که تعداد کاربران آنلاین آن زیاد است و هم چنین تعداد تراکنشهای موجود نیز در سطح سیستم خیلی زیاد باشه مناسب نیست.
*تراکنشهای استفاده شده از این روش کند هستند.(مخصوصا که تراکنش در سطح دیتابیس با تعداد و حجم داده زیاد باشه)
امکان تغییر IsolationLevel در طی انجام یک تراکنش امکان پذیر نیست.
(به شخصه مواد * رو در سطح یک پروژه با شرایط کاربران و حجم داده زیاد تست کردم و نتیجه مطلوب حاصل نشد)
موفق باشید.
برای ادامه دادن این سری از مقالات آموزش MDX Query نیاز میباشد که پایگاه دادهی Advwnture Work DW را نصب کرده و سپس توسط SSAS عمل Deploy را انجام دهیم تا پایگاه دادهی Multidimensional Database توسط SSAS ساخته شود .
در ابتدا میبایست فایل نصب پایگاه داده ی Advwnture Work را دانلود نمایید برای این منظور به آدرس زیر رفته و فایل AdventureWorks2008R2_SR1.exe را دانلود نمایید .
http://www.general-files.biz/download/gs4ac37d18h17i0/AdventureWorks2008R2_SR1.exe.html
یا به آدرس زیر مراجعه کنید
https://msftdbprodsamples.codeplex.com/releases/view/59211
نیاز میباشد قبل از شروع به نصب نرم افزار SQL Server Management Studio را ببندید.
سپس مراحل زیر را انجام دهید.
1. فایل AdventureWorks2008R2_SR1.exe را اجرا نمایید.
2. کمی صبر کنید تا صفحهی زیر نمایش داده شود. و گزینهی I Accept … را انتخاب نماید و دکمهی Next را بزنید.
3. در صورتی که از ویندوز 8 استفاده نماید احتمال دارد با خطای زیر مواجه شوید در این صورت به قسمت روش نصب در ویندوز 8 در ادامهی این مقاله مراجعه کنید .
4. در صورتی که از ویندوزهای Server 2003, XP , Win7 استفاده کنید صفحهی زیر را خواهید دید. در این صفحه ابتدا Instance مربوط به SQL سرور خود را انتخاب نماید (در صورت داشتن چندین Instance روی سرور پایگاه داده) سپس مسیر نصب فایلهای Sample را مشخص نمایید (بعدا از همین مسیر اقدام به Deploy کردن پایگاه دادهی Multidimensional خواهیم کرد) و پیش فرضها را بپذیرید و دکمهی Install را بزنید.
5. کمی صبر کنید تا نصب انجام گردد. و در انتها کلید Finish را بزنید.
پس از مراحل بالا (به جز ویندوز 8) با باز کردن نرم افزار SQL Server Management Studio و اتصال به سرویس Database Engine در قسمت Database تصویر زیر را خواهید دید (البته امکان دارد شما از قبل دارای پایگاه دادههای شخصی بوده باشید که بنابر این آنها نیز در لیست شما وجود خواهند داشت)
همچنین شما میتوانید از پنجرهی Object Explorer در قسمت Connect اقدام به اتصال به سرویس SSAS نموده .
و در پنجرهی باز شده Server Name را انتخاب نمایید (با توجه به اینکه شما در حال حاضر میخواهید به SSAS موجود در سیستم Local متصل شوید ، بنابر این انتخاب سرور Local با وارد کردن کاراکتر (.) انجام میشود.)
بعد از اتصال شکل زیر را خواهید داشت و در شاخهی Database همچنان هیچ Multidimensional Database ی نخواهید داشت.(بعد از عمل Deploy که در ادامه آموزش داده خواهد شد پایگاه دادهی Multidimensional ساخته میشود.)
تنها روشی که تاکنون برای نصب پایگاه دادهی Adventure Work DW برروی ویندوز 8 یافته ام (البته کمی غیر حرفه ای میباشد.) به صورت زیر میباشد.
فایل بالا را ( AdventureWorks2008R2_SR1.exe ) روی سیستم عاملهای ( Server 2003,XP,Win 7 ) نصب کرده (به عنوان یک سیستم عامل واسط) و سپس سرویس Database Engine را Stop کرده و فایلهای پایگاه داده را به سیستم عامل ویندوز 8 انتقال داده و به صورت دستی Restore کنیم.
مراحل ایجاد پایگاه دادهی Multidimensional در ویندوزهای مختلف ، یکسان میباشد.
بعد از نصب پایگاه دادهی Adventure Work DW باید به شاخهی نصب Sample بروید (همان مسیری که در مراحل نصب وارد کردیم و البته آدرس پیش فرض آن C:\Program Files\Microsoft Sql Server\100\Tools\Sample میباشد.)
(در صورتی که در ویندوز 8 مراحل نصب را دنبال میکنید مسیر زیر را در سیستم خود درست نمایید و فایلها و پوشههای موجود در مسیر فوق در سیستم عامل واسط (همان سیستم عاملی که فایل نصب بر روی آن نصب شده است) را به درون آن انتقال دهید.)
سپس به زیر شاخهی \ AdventureWorks 2008R2 Analysis Services Project\enterprise بروید و فایل Adventure Works.sln را با Visual Studio 2010 باز کنید.
احتمال دارد که نیاز باشد روی کل شاخهی enterprise در قسمت Security کاربر جاری را Add کنید و به آن دسترسی Full Control بدهید تا عملیات Convert این پروژه به درستی انجام شود.
پس از باز کردن پروژه در Visual Studio 2010 صفحه ای مطابق تصویر زیر در پنجرهی Solution Explorer خواهید دید.
به هیچ عنوان نگران ساختار این پروژه نباشید ، زیرا در مقالههای آیند شرح کاملی در این خصوص کار با Business Intelligence Management Studio خواهم داد. فعلا هدف ما ایجاد پایگاه دادهی Multidimensional می باشد.
برای ساخت پایگاه دادهی Multidimensional مراحل زیر را دنبال نمایید.
1. در ابتدا روی پروژه کلیک راست کرده و گزینهی Properties را انتخاب نمایید.
2. در قسمت Configuration Properties منوی Deployment را انتخاب کرده و اطمینان حاصل کنید که سرور شما LocalHost و نام پایگاه داده شما Adventure Works DW 2008R2 باشد.
3. سپس روی Adventure Works.ds کلیک راست کنید تا تنظیمات Connection String به DW را انجام دهیم. مطابق شکل زیر
4. سپس در پنجرهی باز شده دکمهی Edit را بزنید .
5. و در صفحه باز شده تنظیمات زیر را مطابق تصویر زیر انجام دهید. دقت داشته باشید که تغییرات را از بالا به پایین باید انجام دهید و قبل از زدن دکمهی OK حتما Test Connection را بزنید تا از صحت تنظیمات مطمعا شوید.
6. سپس دو بار دکمهی OK را در دوصفحه کلیک کنید. (بعد از این مراحل شما آمادهی Deploy کردن میباشد)
7. در ابتدا پروژه را Build نمایید ( CTRl + Shift + B ) و اطمینان حاصل کنید که Build با موفقیت انجام میشود.
8. در انتها برروی نام پروژه کلیک راست نمایید و گزینهی Deploy را انتخاب نمایید. فرایند Deploy کردن میتواند کمی زمان بر باشد بنابر این شکیبا باشید و در انتها پیام Deployment Completed Successfully را دریافت خواهید کرد.
9. حال به SQL Server Management Studio بروید و به سرویس SSAS کانکت شوید . در قسمت DataBase یک پایگاه داده با نام Adventure Works DW 2008R2 مشاهده خواهید کرد .
به شما تبریک میگویم اینک شما یک پایگاه دادهی Multidimensional را ساخته اید .
در مقالهی بعدی توضیحاتی در خصوص BIMS (Business Intelligence Management Studio) خواهم داد و همچنین اولین MDX Query را خواهیم نوشت.
پیش فرضهای تعیین کلید اصلی در EF Core
به صورت پیش فرض هر خاصیتی که به نام Id و یا type name>Id> باشد، به عنوان primary key تفسیر خواهد شد؛ مانند:
public class Car { public string Id { get; set; }
public class Car { public string CarId { get; set; }
نحوهی تعیین کلید اصلی به صورت صریح
اگر یکی از دو حالت فوق برقرار نباشند، باید کلید اصلی را به نحو صریحی مشخص کرد.
الف) از طریق ویژگیها
public class Car { [Key] public string LicensePlate { get; set; }
ب) با استفاده از روش Fluent API
public class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => c.LicensePlate); } }
پیشنیاز کار با ویژگیها در EF Core
در اسمبلی که مدلهای موجودیتها شما قرار دارند، نیاز است وابستگی System.ComponentModel.Annotations به فایل project.json پروژه اضافه شود، تا ویژگیهایی مانند Key، شناسایی و قابل استفاده شوند:
{ "dependencies": { "System.ComponentModel.Annotations": "4.1.0" } }
تعیین کلید ترکیبی و یا Composite key
اگر نیاز است چندین خاصیت را به صورت کلید اصلی معرفی کرد که به آن composite key هم میگویند، تنها روش ممکن، استفاده از Fluent API و به صورت زیر است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => new { c.State, c.LicensePlate }); }
روشهای مختلف تولید خودکار مقادیر خواص
حالت پیش فرض تولید مقدار فیلدهای Id عددی، همان حالت خود افزایندهای است که توسط بانک اطلاعاتی کنترل میشود و یا کلید اصلی که از نوع Guid تعیین شود نیز به صورت خودکار توسط بانک اطلاعاتی در حین عملیات Add، مقدار دهی میشود (با استفاده از الگوریتم Guid سری در SQL Server).
اگر این حالات مطلوب شما نیست، حالتهای سه گانهی ذیل را میتوان استفاده کرد:
الف) هیچ دادهی خودکاری تولید نشود
برای اینکار میتوان با استفاده از ویژگی DatabaseGenerated و تنظیم مقدار آن به None، جلوی تولید خودکار کلید اصلی را گرفت. در این حالت باید هم در حین عملیات Add و هم در حین عملیات Update، مقادیر را خودتان مقدار دهی کنید:
public class Blog { [DatabaseGenerated(DatabaseGeneratedOption.None)] public int BlogId { get; set; } public string Url { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.BlogId) .ValueGeneratedNever(); }
ب) تولید دادههای خودکار فقط در حالت Add
حالت Add به این معنا است که دادههای خواص مشخصی، برای موجودیتهای «جدید»، به صورت خودکار تولید خواهند شد. اینکه آیا واقعا این مقادیر به صورت خودکار تولید میشوند یا خیر، صرفا وابستهاست به بانک اطلاعاتی در حال استفاده. برای مثال SQL Server برای نوعهای Guid، به صورت خودکار با کمک الگوریتم SQL Server sequential GUID، کار مقدار دهی یک چنین فیلدهایی را انجام میدهد.
این فیلدها باید توسط ویژگی DatabaseGenerated و با مقدار Identity مشخص شوند. در اینجا Identity به معنای فیلدهایی است که به صورت خودکار توسط بانک اطلاعاتی مقدار دهی میشوند و الزاما به کلید اصلی اشاره نمیکنند. برای مثال در موجودیت ذیل، خاصیت تاریخ ثبت رکورد، از نوع Identity مشخص شدهاست. به این معنا که در حین ثبت اولیهی رکورد آن، نیازی نیست تا خاصیت Inserted را مقدار دهی کرد. اما اینکه آیا SQL Server یک چنین کاری را به صورت خودکار انجام میدهد، پاسخ آن خیر است. SQL server فقط برای فیلدهای عددی و Guid ایی که با DatabaseGeneratedOption.Identity مزین شده باشند، مقادیر متناظری را به صورت خودکار تولید میکند. برای حالت DateTime نیاز است، مقدار پیش فرض فیلد را صریحا مشخص کرد که توسط ویژگیها میسر نیست و فقط fluent API از آن پشتیبانی میکند.
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public DateTime Inserted { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Inserted) .ValueGeneratedOnAdd(); }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Inserted) .HasDefaultValueSql("getdate()"); }
یک نکته: در حالت DatabaseGeneratedOption.Identity و یا ValueGeneratedOnAdd فوق، اگر مقداری به این نوع فیلدها انتساب داده شده باشد که با مقدار پیش فرض آنها (property.ClrType.GetDefaultValue) متفاوت باشد، از این مقدار جدید، بجای تولید مقداری خودکار، استفاده خواهد شد. برای مثال مقدار پیش فرض رشتهها، نال، مقادیر عددی، صفر و برای Guid مقدار Guid.Empty است. اگر هر مقدار دیگری بجای اینها به فیلدهای فوق انتساب داده شوند، از آنها استفاده میشود.
ج) تولید دادههای خودکار در هر دو حالت Add و Update
تولید دادهها در حالتهای Add و Update به این معنا است که یک چنین خواصی، همواره با فراخوانی متد SaveChanges، دارای مقدار خودکار جدیدی خواهند شد و نیازی نیست در کدها مقدار دهی شوند. برای مشخص سازی این نوع خواص، از ویژگی DatabaseGenerated با مقدار Computed و یا متد ValueGeneratedOnAddOrUpdate در حالت Fluent API میتوان استفاده کرد:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime LastUpdated { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.LastUpdated) .ValueGeneratedOnAddOrUpdate(); }
تذکر: در اینجا نیز همانند حالت ValueGeneratedOnAdd، اگر این خواص مشخص شده، دارای مقدار متفاوتی با مقدار پیش فرض آنها باشند، از این مقادیر جدید بجای تولید خودکار مقادیر استفاده خواهد شد.
خواص محاسباتی (Computed Columns) و تفاوت آنها با DatabaseGeneratedOption.Computed
خواص محاسباتی (Computed Columns)، خواصی هستند که مقادیر آنها در بانک اطلاعاتی محاسبه میشوند و کاملا متفاوت هستند با DatabaseGeneratedOption.Computed که مفهوم دیگری دارد. DatabaseGeneratedOption.Computed به این معنا است که این فیلد خاص، با هر بار فراخوانی SaveChanges باید مقدار محاسبه شدهی جدیدی را داشته باشد و روش تولید این مقدار خودکار، یا بر اساس Guidهای سری است، یا توسط فیلدهای خود افزایندهی عددی و یا از طریق مقادیر پیش فرضی مانند getdate در حین ثبت یا به روز رسانی، مقدار دهی میشوند. اما خواص محاسباتی، یکی از امکانات «گزارشگیری سریع» SQL Server هستند و به نحو ذیل، تنها توسط Fluent API قابل تنظیم میباشند:
public class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string DisplayName { get; set; } } public class MyContext : DbContext { public DbSet<Person> People { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .Property(p => p.DisplayName) .HasComputedColumnSql("[LastName] + ', ' + [FirstName]"); } }
امکان تعریف Sequence در EF Core 1.0
Sequence قابلیتی است که به SQL Server 2012 اضافه شدهاست و توضیحات بیشتر آنرا در مطلب «نحوه ایجاد Sequence و استفاده آن در Sql Server 2012» میتوانید مطالعه کنید.
در EF Core، امکان مدلسازی Sequence نیز پیش بینی شدهاست. آنها به صورت پیش فرض در مدلها ذکر نمیشوند و همچنین وابستگی به جدول خاصی ندارند. به همین جهت امکان تعریف آنها صرفا توسط Fluent API وجود دارد:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared") .StartsAt(1000).IncrementsBy(5); modelBuilder.Entity<Order>() .Property(o => o.OrderNo) .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers"); }
در مثال فوق، ابتدا یک Sequence نمونه به نام OrderNumbers تعریف شدهاست که از عدد 1000 شروع شده و واحد افزایش آن 5 است. سپس از این نام در قسمت مقدار پیش فرض ستون OrderNo استفاده شدهاست.
و یا از Sequence ها میتوان برای تعیین مقدار پیش فرض Primary key بجای حالت identity خود افزایش یابنده استفاده کرد:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasSequence<int>("PrimaryKeyWithSequenceSequence"); modelBuilder.Entity<PrimaryKeyWithSequence>(entity => { entity.Property(e => e.PrimaryKeyWithSequenceId).HasDefaultValueSql("NEXT VALUE FOR [PrimaryKeyWithSequenceSequence]"); }); }
[PrimaryKeyWithSequenceSequence] دریافت و سپس بجای فیلد id درج میشود.
به این روش الگوریتم Hi-Low هم میگویند که یکی از مهمترین اهداف آن داشتن یک سری Id منحصربفرد، جهت بالابردن سرعت insertها در یک batch است. در حالت عادی insertها، ابتدا یک insert انجام میشود، سپس کوئری گرفته شده و آخرین Id درج شده به کلاینت بازگشت داده میشود. این روش، برای انجام تنها یک insert، سریع است. اما برای batch insert، به شدت کارآیی پایینی دارد. به همین جهت دسترسی به بازهای از اعداد منحصربفرد، پیش از شروع به insert تعداد زیادی رکورد، سرعت نهایی کار را بالا میبرد.
نحوهی تعریف ایندکسها در EF Core 1.0
برای افزودن ایندکسها به EF Core 1.0، تنها روش میسر، استفاده از Fluent API است (و برخلاف EF 6.x از روش data annotations فعلا پشتیبانی نمیکند؛ هرچند API جدید آن نسبت به EF 6.x بسیار واضحتر است و با ابهامات کمتر).
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasIndex(b => b.Url) .HasName("Index_Url");
modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasName("Index_Url").IsUnique();
modelBuilder.Entity<Person>() .HasIndex(idx => new { idx.FirstName, idx.LastName }) .IsUnique();
یک نکته: اگر از پروایدر SQL Server استفاده میکنید، میتوان متد الحاقی ویژهای را به نام ForSqlServerIsClustered نیز برای تعریف clustered indexes، در این زنجیره ذکر کرد.
امکان تعریف Alternate Keys در EF Core 1.0
به Unique Constraints در EF Core، نام Alternate Keys را دادهاند و این مورد نیز تنها از طریق Fluent API قابل تنظیم است:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasAlternateKey(c => c.LicensePlate) .HasName("AlteranteKey_LicensePlate"); }
اگر متد HasName در اینجا ذکر نشود، نام پیش فرض آن <type name>_<property name> خواهد بود و اگر همانند composite keys و یا ایندکسهای ترکیبی، چند خاصیت ذکر شوند، قسمت property name به جمع نام تمام خواص ذکر شده و جدا شدهی با _ تنظیم میشود.
برای نمونه اگر یک Alternate Key ترکیبی را به صورت ذیل تعریف کنیم:
modelBuilder.Entity<Person>() .HasAlternateKey(x => new { x.FirstName, x.LastName });
table.UniqueConstraint("AK_Persons_FirstName_LastName", x => new { x.FirstName, x.LastName });
سؤال: یک Unique Constraint با Unique Index چه تفاوتی دارد؟
در پشت صحنه، پیاده سازی یک Unique Constraint با Unique Index تفاوتی ندارند. فقط از دیدگاه روشنتر شدن مقصود، استفادهی از Unique Constraint ترجیح داده میشود.
البته از دیدگاه بانک اطلاعاتی پیاده سازی کننده نیز برای نمونه SQL Server، این تفاوتها وجود دارند:
الف) یک Unique Constraint را نمیتوان غیرفعال کرد؛ برخلاف Unique Indexها.
ب) Unique Constraintها موارد اضافهتری را مانند FILLFACTOR و IGNORE_DUP_KEY نیز میتوانند تنظیم کنند.
ج) امکان تعریف فیلترها برای Unique Indexها وجود دارد؛ برخلاف Unique Constraint ها.
که البته از دیدگاه EF، این سه مورد اهمیتی ندارند و بیشتر روشنتر شدن مقصود، هدف اصلی آنها است.
امروز اولین دستورات MDX را خواهیم نوشت. قبل از شروع کار فراموش نکنید موارد زیر را حتما انجام داده باشید :
- نصب پایگاه داده ی Adventure Work DW 2008 و همچنین نصب پایگاه دادهی چند بعدی Adventure Work DW 2008 روی SSAS
- مطاله قسمتهای قبلی برای آشنایی با مفاهیم پایه .
در صورتیکه پیش شرایط فوق را نداشته باشید، احتمالا در ادامه با مشکلاتی مواجه خواهید شد؛ زیرا برای آموزش MDX Query ها از پایگاه دادهی Adventure Work DW 2008 استفاده شده است.
دقت داشته باشید که MDX Query ها تا حدودی شبیه T/SQL میباشند؛ اما مطلقا از نظر مفهومی با هم شباهت ندارند. به عبارت دیگر ما در T/SQL با یک مدل رابطهای سرو کار داریم در حالیکه در MDX ها با یک پایگاه داده چند بعدی کار میکنیم. به بیان دیگر در پایگاه دادههای رابطهای صحبت از جداول، ردیفها، ستونها و ضرب دکارتی مجموعهها میباشد، اما در پایگاه دادههای چند بعدی در خصوص Dimension,Fact,Cube,Tuple و ... صحبت میکنیم. البته ماکروسافت تلاش کردهاست تا حد زیادی Syntax ها شبیه به یکدیگر باشند.
نحوهی نوشتن یک Select در MDX ها به صورت زیر میباشد :
Select {} On Columns , {} On Rows From <Cube_Name> Where <Condition>
در ادامه با اجرای هر کوئری، توضیحات لازم در خصوص آن ارایه میگردد و با پیگیری این آموزشها میتوانید مفاهیم، توابع و ... را در MDX Query ها بیاموزید.
برای اجرای دستورات زیر باید Microsoft SQL Server Management Studio را باز نمایید و به سرویس SSAS متصل شوید. سپس پایگاه دادهی Adventure Works DW 2008R2 را انتخاب نمایید و از Cubes Adventure Works را انتخاب نمایید.
حال دکمهی New Query را در بالای صفحه بزنید ( Ctrl + N )
سپس در صفحهی باز شده میتوانید Cube یا SubCube های آن Cube را انتخاب کرده و کمی پایینتر Measure Group را خواهیم داشت و در انتها Measure ها و Dimension ها قرار گرفتهاند. (در هنگام نوشتن Select میتوان از عمل Drag&Drop برای آسانتر شدن نوشتن MDX Query ها نیز استفاده کنید)
متاسفانه هنوز در IDE مربوط به SQL Server کلیدی برای مرتب سازی دستورات MDX وجود ندارد و البته در نرم افزار هایی مانند SQL Toll Belt هم چنین چیزی قرار داده نشده است . بنابر این توصیه میشود در نوشتن دستورات MDX تمام تلاش خود را بکنید تا دستوراتی مرتب و خوانا را تولید کنید.
با اجرای دستور زیر اولین کوئری خود را در پایگاه دادهی چند بعدی بنویسید (برای اجرا کلید F5 مانند T/SQL کار خواهد کرد.)
Select From [Adventure Works]
شاید تعجب کنید. کوئری فاقد قسمت Projection میباشد! در MDX ها میتوان هیچ سطر یا ستونی را انتخاب نکرد. اما چگونه؟ و خروجی نمایش داده شده چیست؟
برای توضیح مطلب فوق باید در خصوص Default Measure کمی اطلاعات داشته باشید. در هنگام Deploy کردن پروژه در SSAS برای هر Cube یک Measure به عنوان Measure پیش فرض انتخاب شده. بنابر این در صورتیکه هیچ گونه Projection یا Where ایی اعمال نشده باشد، SQL Server به صورت پیش فرض مقدار Mesaure پیش فرض را بدون اعمال هیچ بعدی نمایش میدهد.
خروجی دستور بالا مشابه تصویر زیر میباشد.
حال دستور زیر را اجرا میکنیم :
Select From [Adventure Works] Where [Measures].[Reseller Sales Amount]
تصویر خروجی به صورت زیر میباشد :
شاید باز هم تعجب کنید. نوشتن نام یک شاخص به جای عبارت شرط؟! آیا خروجی عبارات شرطی نباید Boolean باشند؟
خیر. اگر چنین پرسش هایی در ذهن شما ایجاد شده باشد، به دلیل مقایسهی MDX با T/SQL میباشد. در اینجا شرط Where بر روی ردیفهای جدول مدل رابطه ای اعمال نمیشود و عملا بیانگر واکشی اطلاعات از مدل چند بعدی میباشد. با اعمال شرط فوق به SSAS اعلام کرده ایم که خروجی بر اساس شاخص [Measures].[Reseller Sales Amount] باشد. با توجه به این که شاخص انتخاب شده با شاخص پیش فرض یکی میباشد خروجی با حالت قبل تفاوتی نخواهد کرد.
برای درک بهتر، کوئری زیر را اجرا کنید :
Select From [Adventure Works] where [Measures].[Internet Sales Amount]
استفاده از این شرط سبب استفاده نشدن از شاخص پیش فرض می شود . به عبارت دیگر این کوئری دارای سرجمع مبلغ فروش اینترنتی می باشد.
دستور زیر را اجرا کنید :
Select [Measures].[Reseller Sales Amount] on columns From [Adventure Works]
با اعمال یک شاخص خاص در ستون ، عملا فیلترینگ انجام می شود
استفاده از یک دایمنشن در ستون :
دستور زیر را اجرا کنید
Select [Date].[Calendar].[Calendar Year] on columns From [Adventure Works]
خروجی به شکل زیر خواهد بود
همان طور که مشاهده میکنید خروجی دارای چندین ستون میباشد و دارای مقادیری در هر ستون. اما این مقادیر از کجا آمده اند؟
همواره این نکته را به خاطر بسپارید که در صورت عدم ذکر نام یک Measure در کوئری ، SSAS از Measure پیش فرض استفاده میکند. حال کوئری فوق میزان فروش نمایندگان ( Reseller Sales Amount ) را در هر سال نمایش میدهد.
سوال بعدی این میباشد که این سالها از کجا آمده اند؟ خوب برای درک بهتر این مورد میتوانیم مانند تصویر زیر به دایمنشن Date رفته و در ساختار سلسله مراتبی ، اعضای سطح [Date].[Calendar].[Calendar Year] را مشاهده کنیم.
ایجاد سرجمع ستونها :
کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns From [Adventure Works]
بعد از اجرا
تصویر زیر را خواهید دید :
سوال اول این میباشد که کاربرد {} در انتخاب دایمنشنها چیست؟ در پاسخ میتوان گفت که اگر شاخص ها یا بعد ها ، مرتبط به یک سلسله مراتب باشند آنها را در یک {} قرار می دهیم ولی اگر سلسله مراتب متفاوت باشد، یا بعد و شاخص باشند باید در () قرار بگیرند .
خوب همان طور که مشخص است در ساختار سلسله مراتبی ابتدا سال و بعد یک سطح بالاتر را انتخاب کرده ایم این به معنی نمایش سرجمع در سطح بالاتر از سال میباشد(سرجمع تمامی سال ها).
استفاده از دایمنشن و Measure در سطر و ستون مجرا :
کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, [Product].[Product Categories].[Category] on rows From [Adventure Works]
خروجی مشابه شکل زیر میباشد
در مثال فوق از بعدها در ستون و همزمان، نمایش نوع دسته بندی محصولات در ردیفها استفاده شده است. به عبارت دیگر نتیجه عبارت است از فروش نماینگان فروش ( Reseller Sales Amount ) براساس هر سال به تفکیک نوع دسته بندی محصول فروخته شده.
(کسانی که چنین گزارشی را با استفاده از T/SQL نوشته اند، احتمالا از آسانی نوشتن این گزارش توسط MDX ها شگفت زده شده اند.)
قراردادن فیلد سرجمع در ردیف :
برای این منظور کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, {[Product].[Product Categories].[Category],[Product].[Product Categories]}on rows From [Adventure Works]
خروجی به صورت زیر میباشد
نحوهی نمایش سرجمع در ردیف، مشابه نمایش سرجمع در ستون میباشد.
استفاده از تابع non empty :
برای حذف ستون هایی که کاملا دارای مقدار null میباشند به صورت زیر عمل میکنیم :
Select non empty {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns , {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works]
خروجی به صورت زیر میباشد:
انتخاب دو دایمنشن در سطر و ستون و مشخص نمودن یک Measure خاص برای کوئری :
برای این کار به صورت زیر عمل خواهیم کرد:
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works] Where [Measures].[Internet Sales Amount]
در اینجا با اعمال شرط Where عملا از SSAS خواستهایم خروجی برای شاخص مشخص شده واکشی شود.
در بالا میزان فروش اینترنتی برای دسته بندی محصولات و در سالهای مختلف ارائه و همچنین سرجمع ستون و سطر نیز نمایش داده شده است.
در صورتیکه بخواهیم ستون و سطرهایی را که دارای مقدار null در تمامی آن سطر یا ستون میباشند، حذف کنیم به صورت زیر عمل میکنیم:
Select non empty {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, non empty {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works] Where [Measures].[Internet Sales Amount]
اگر در یک دایمنشن فقط یک سلسله مراتب باشد یا اصلا سلسله مراتبی وجود نداشته باشد، می توان از نام خود دایمنشن استفاده کرد
Select [Sales Channel] on columns From [Adventure Works]
و دقت داشته باشید دایمنشنی که دارای بیش از یک سلسله مراتب باشد، حتما باید در Select مشخص شود که از کدام سلسله مراتب می خواهیم استفاده کنیم .در غیر این صورت با خطا مواجه خواهیم شد.
Select [Product] on columns From [Adventure Works]
استفاده از فیلدهای یک دایمنشن که دارای سلسه مراتب می باشد نیز جایز می باشد
Select [Product].[Category] on columns From [Adventure Works]
Select [Product].[Category].[all] on columns From [Adventure Works] -- Select [Product].[Category].[All] on columns From [Adventure Works] -- Select [Product].[Category].[(all)] on columns From [Adventure Works] -- Select [Product].[Category].[all products] on columns From [Adventure Works]
برای به دست آوردن سرجمع کل روی یک صفت از دایمنشن، باید از سه حالت آخر استفاده کرد. حالت اول خطا دارد و خروجی خالی نمایش داده می شود .
در صورتی که بخواهیم از یک دایمنشن تمامی Member های آن را واکشی کنیم به صورت زیر عمل خواهیم کرد
Select {[Product].[Category].members} on columns From [Adventure Works]
استفاده از Members روی یک خصوصیت در دایمنشن به معنی دریافت سرجمع آن صفت و سپس تک تک اجزای آن صفت میباشد.
اگر از یک صفت واکشی اطلاعات انجام شود در سطح اعضای آن، در آن صورت دیگر سرجمع نمایش داده نمی شود و فقط جمع هر عضو در آن صفت نمایش داده می شود .
Select [Product].[Category].[Category].members -- dimension.hierarchy.level.members on columns From [Adventure Works]
اگر بخواهیم دو ستون را داشته باشیم که هر دو برای یک دایمنشن میباشند باید از {} استفاده کرد . دستور اول خطا خواهد داشت.
Select [Product].[Category].[Category].members,[Product].[Category].[All Products] on columns From [Adventure Works]
در دستور دوم با استفاده از {} خروجی نمایش داده میشود که عبارت است از تمامی اعضای سطح [Product].[Category].[Category]. به همراه سرجمع تمامی محصولات.
Select {[Product].[Category].[Category].members,[Product].[Category].[All Products]} on columns From [Adventure Works]
یک راه کوتاهتر برای انتخاب تمامی اعضا و سرجمع آنها
Select {[Product].[Category].[Category],[Product].[Category]} on columns From [Adventure Works]
می توان از کلمات Members, All X استفاده نکرد.
انتخاب اولین دسته بندی محصول البته این ترتیب بر اساس Key Columns در SSAS می باشد .
Select [Product].[Category].&[1] on columns From [Adventure Works]
انتخاب دقیق یک عضو در خروجی
Select [Product].[Category].[Bikes] on columns From [Adventure Works]
انتخاب دو عضو از یک دایمنشن
Select {[Product].[Category].[Bikes],[Product].[Category].[Clothing]} on columns From [Adventure Works]
واکشی تمامی دسته بندی محصولات بر اساس Measure پیش فرض :
Select [Product].[Product Categories].members on columns From [Adventure Works]
در صورتیکه بخواهیم دو Dimension مختلف را در یک ستون یا سطر بیاوریم باید از Join استفاده کنیم. بنابر این دو دستور زیر با خطا روبرو میشوند
Select [Product].[Product Categories],[Product].[Category] on columns From [Adventure Works] Go Select {[Product].[Product Categories],[Product].[Category]} on columns From [Adventure Works]
تعریف Axis : به هر کدام از ستون یا سطر یک محور یا Axis گفته میشود.
با بررسی مثال فوق به نتایج زیر خواهیم رسید.
1. امکان استفاده از دو سلسله مراتب مختلف از یک دایمنشن در یک Axis وجود ندارد . مگر اینکه آنها را باهمدیگر CrossJoin کنیم .
2. امکان استفاده از دو سلسله مراتب مختلف از یک دایمنشن در دو Axis مختلف وجود دارد .
ترتیب انتخاب Axis ها به صورت زیر میباشد:
1. Columns
2. Rows
برای مشخص شدن موضوع کوئری زیر را اجرا کنید
Select [Product].[Product Categories].members on rows From [Adventure Works]
نمیتوانیم ردیفی را واکشی کنیم بدون اینکه ستونی برای کوئری مشخص کرده باشیم.
البته میتوان ستون خالی ایجاد نماییم مانند مثال زیر :
Select {} on columns, [Product].[Product Categories].members on rows From [Adventure Works]
البته در این صورت خروجی فقط نام دسته بندی محصولات خواهد بود زیرا هیچ ستونی مشخص نشده .
در مقالات بعدی به ادامهی مطالب MDX Query خواهیم پرداخت.