مطالب
توزیع پروژه‌های ASP.NET Core 1.1 بدون ارائه فایل‌های View آن
پیشتر مطلب «توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن» را در مورد ASP.NET MVC 5.x مطالعه کرده بودید. این روش پشتیبانی رسمی و توکاری نداشته و توسط افزونه‌های ثالث انجام می‌شود؛ به همراه تنظیمات و نکات خاص خودش. در ASP.NET Core 1.1، یک چنین امکانی به صورت توکار و صرفا با چند تنظیم ساده، در دسترس می‌باشد که در ادامه نحوه‌ی فعال سازی آن‌را بررسی خواهیم کرد.


فعال سازی پیش کامپایل Viewهای Razor در ASP.NET Core 1.1

در ادامه تغییرات فایل project.json و بسته‌های مورد نیاز جهت فعال سازی پیش کامپایل Viewهای Razor را در برنامه‌های ASP.NET Core 1.1 ملاحظه می‌کنید:
{
    "dependencies": {
        "Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Design": {
            "version": "1.1.0-preview4-final",
            "type": "build"
        }
    },
 
    "tools": {
        "Microsoft.AspNetCore.Mvc.Razor.ViewCompilation.Tools": {
            "version": "1.1.0-preview4-final"
        }
    },

 
    "scripts": {
        "postpublish": [
            "dotnet razor-precompile --configuration %publish:Configuration% --framework %publish:TargetFramework% --output-path %publish:OutputPath% %publish:ProjectPath%",
            "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
        ]
    }
}
در اینجا کار فراخوانی عملیات پیش کامپایل، توسط فرمان dotnet razor-precompile در زمان publish پروژه انجام می‌شود.


بررسی ساختار خروجی نهایی پروژه پس از publish

پس از publish پروژه، اگر به خروجی آن دقت کنیم، فایل اسمبلی جدیدی، به نام xyz.PrecompiledViews.dll در آن اضافه شده‌است (که در اینجا xyz نام فضای نام اصلی برنامه است) و حاوی تمام Viewهای برنامه، به صورت کامپایل شده‌است:



اصلاح تنظیمات publishOptions فایل project.json

در این‌حالت دیگر نیازی به ذکر پوشه‌ی Views یا الحاق تمام فایل‌های cshtml در حین publish نیست و می‌توان این قسمت را حذف کرد:
  "publishOptions": {
    "include": [
      "wwwroot",
       //"**/*.cshtml",
      "appsettings.json",
      "web.config"
    ]
  },
مطالب
استفاده از SQLDom برای آنالیز عبارات T-SQL
به همراه بسته Features pack اس کیوال سرور 2012، دو بسته SqlDom.msi نیز وجود دارند (نسخه‌های X86 و X64). این بسته حاوی اسمبلی Microsoft.SqlServer.TransactSql.ScriptDom.dll می‌باشد که نهایتا در آدرس Program Files\Microsoft SQL Server\110\SDK\Assemblies کپی خواهد شد.
به کمک آن می‌توان عبارات پیچیده T-SQL را Parse و آنالیز کرد. البته باید در نظر داشت هرچند این بسته جهت SQL Server 2012 ارائه شده اما این اسمبلی با نگارش‌های 2005 به بعد اس کیوال سرور کاملا سازگار است و اساسا نیازی هم به SQL Server ندارد. در ادامه مروری خواهیم داشت بر نحوه استفاده از آن.


یافتن کوئری‌های * Select در بین انبوهی از اسکریپت‌ها به کمک SQLDom

در مورد مضرات کوئری‌های * select پیشتر مطلبی را در این سایت خوانده‌اید. در ادامه قصد داریم به کمک امکانات اسمبلی Microsoft.SqlServer.TransactSql.ScriptDom.dll، تعدادی عبارت T-SQL را آنالیز کرده و مشخص کنیم که آیا حاوی * select هستند یا خیر. کد کامل آن‌را در ذیل مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace DbCop
{
    // Microsoft® SQL Server® 2012 Transact-SQL ScriptDom 
    // SQL Server 2012 managed parser, Supports SQL Server 2005+
    // SQLDom.msi (redist x86/x64)
    // http://www.microsoft.com/en-us/download/details.aspx?id=29065
    // X86: http://go.microsoft.com/fwlink/?LinkID=239634&clcid=0x409
    // X64: http://go.microsoft.com/fwlink/?LinkID=239635&clcid=0x409
    // Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.TransactSql.ScriptDom.dll

    class Program
    {
        static void Main()
        {
            const string tSql = @"
                -- select * in PROCEDURE
                CREATE PROCEDURE dbo.SelectStarTest
                AS
                SELECT * FROM dbo.tbl1
                go

                -- select * in PROCEDURE with TableVar
                Create PRocedure SelectAll
                AS
                Declare @X table(Id integer)
                Select * from @x
                go

                -- select * in PROCEDURE with ctex
                CREATE PROCEDURE dbo.SelectAllCte
                AS 
                WITH ctex
                AS (
                SELECT * FROM sys.objects
                )
                SELECT * FROM ctex
                go

                -- normal select *
                select * from tbl1; 
                select * from dbo.tbl2;
            ";

            IList<ParseError> errors;
            TSqlScript sqlFragment;
            using (var reader = new StringReader(tSql))
            {
                var parser = new TSql110Parser(initialQuotedIdentifiers: true);
                sqlFragment = (TSqlScript)parser.Parse(reader, out errors);
            }

            if (errors != null && errors.Any())
            {
                var sb = new StringBuilder();
                foreach (var error in errors)
                    sb.AppendLine(error.Message);

                throw new InvalidOperationException(sb.ToString());
            }

            var i = 0;
            foreach (var batch in sqlFragment.Batches)
            {
                Console.WriteLine("Batch: {0}, Statement(s): {1}", ++i, batch.Statements.Count);
                foreach (var statement in batch.Statements)
                {
                    processStatement(statement);
                }
                Console.WriteLine();
            }

            Console.WriteLine("\nPress a key...");
            Console.Read();
        }

        private static void processStatement(TSqlStatement statement)
        {
            var createProcedureStatement = statement as CreateProcedureStatement;
            if (createProcedureStatement != null)
            {
                var statementList = createProcedureStatement.StatementList;
                foreach (var procedureStatement in statementList.Statements)
                {
                    processStatement(procedureStatement);
                }
            }

            var selectStatement = statement as SelectStatement;
            if (selectStatement != null)
            {
                var query = selectStatement.QueryExpression;
                var selectElements = ((QuerySpecification)query).SelectElements;
                foreach (var selectElement in selectElements)
                {
                    var expression = selectElement as SelectStarExpression;
                    if (expression == null) continue;
                    Console.WriteLine(
                        "`Select *` detected @StartOffset:{0}, Line:{1}, T-SQL: {2}",
                        expression.StartOffset,
                        expression.StartLine,
                        statementToString(selectStatement));
                }
            }
        }

        private static string statementToString(TSqlFragment selectStatement)
        {
            var text = new StringBuilder();
            for (var i = selectStatement.FirstTokenIndex; i <= selectStatement.LastTokenIndex; i++)
            {
                text.Append(selectStatement.ScriptTokenStream[i].Text);
            }
            return text.ToString();
        }
    }
}

توضیحات:
پس از نصب SQLDom.msi، ارجاعی را به اسمبلی زیر اضافه نمائید تا بتوانید کد فوق را کامپایل کنید:
Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.TransactSql.ScriptDom.dll

کار با ایجاد وهله‌ای از TSql110Parser شروع می‌شود. متد Parse آن، آرگومانی از نوع TextReader را قبول می‌کند. برای مثال با استفاده از StringReader می‌توان محتوای یک متغیر رشته‌ای را به آن ارسال کرد و یا توسط StreamReader یک فایل sql را.
پس از فراخوانی متد Parse، بهتر است بررسی شود که آیا عبارت T-SQL دریافتی معتبر بوده است یا خیر. اینکار را توسط لیستی از ParseError‌های دریافتی می‌توان انجام داد.
خروجی متد Parse، حاوی یک سری Batch آنالیز شده است. هر عبارت Go در اینجا یک Batch را تشکیل می‌دهد. سپس در داخل هر batch به دنبال batch.Statements خواهیم گشت تا بتوان به عبارات T-SQL آن‌ها دسترسی یافت.
در ادامه کار اصلی توسط متد processStatement صورت می‌گیرد. عبارات دریافتی، در حالت کلی از نوع TSqlStatement هستند اما در اصل می‌توانند یکی از مشتقات آن نیز باشند. در اینجا فقط دو مورد CreateProcedureStatement و SelectStatement بررسی شده‌اند (مطابق رشته tSql ابتدای مثال). هر دو عبارت، از کلاس TSqlStatement مشتق شده‌اند.
در متد processStatement عبارات select معمولی و همچنین آن‌هایی که داخل رویه‌های ذخیره شده تعریف شده‌اند، استخراج شده و در نهایت بررسی می‌شوند که آیا از نوع SelectStarExpression هستند یا خیر (همان * select صورت مساله).
خروجی مثال فوق به شرح زیر است:
Batch: 1, Statement(s): 1
`Select *` detected @StartOffset:140, Line:5, T-SQL: SELECT * FROM dbo.tbl1

Batch: 2, Statement(s): 1
`Select *` detected @StartOffset:368, Line:12, T-SQL: Select * from @x

Batch: 3, Statement(s): 1
`Select *` detected @StartOffset:659, Line:22, T-SQL: WITH ctex
                AS (
                SELECT * FROM sys.objects
                )
                SELECT * FROM ctex

Batch: 4, Statement(s): 2
`Select *` detected @StartOffset:753, Line:26, T-SQL: select * from tbl1;
`Select *` detected @StartOffset:791, Line:27, T-SQL: select * from dbo.tbl2;
 
مطالب
معرفی Microsoft.Data.dll یا WebMatrix.Data.dll

مایکروسافت اخیرا علاوه بر تکمیل ORM های خود مانند LINQ to SQL و همچنین Entity framework ، لایه دیگری را نیز بر روی ADO.NET جهت کسانی که به هر دلیلی دوست ندارند با ORMs کار کنند و از نوشتن کوئری‌های مستقیم SQL لذت می‌برند،‌ ارائه داده است که Microsoft.Data library نام دارد و از قابلیت‌های جدید زبان سی شارپ مانند واژه‌ کلیدی dynamic استفاده می‌کند.

در ادامه قصد داریم جهت بررسی توانایی‌های این کتابخانه از بانک اطلاعاتی معروف Northwind استفاده کنیم. این بانک اطلاعاتی را از اینجا می‌توانید دریافت کنید.

مراحل استفاده از Microsoft.Data library:
الف) این اسمبلی جدید به همراه پروژه WebMatrix ارائه شده است. بنابراین ابتدا باید آن‌را دریافت کنید: +
لازم به ذکر است که این کتابخانه اخیرا به WebMatrix.Data.dll تغییر نام یافته است. (اگر وب را جستجو کنید فقط به Microsoft.Data.dll اشاره شده است)

ب) پس از نصب، ارجاعی را از اسمبلی WebMatrix.Data.dll به پروژه خود اضافه نمائید. این اسمبلی در صفحه‌ی Add References ظاهر نمی‌شود و باید کامپیوتر خود را برای یافتن آن جستجو کنید که عموما در آدرس زیر قرار دارد:
C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\WebMatrix.Data.dll

ج) اتصال به بانک اطلاعاتی
پیش فرض اصلی این کتابخانه بانک اطلاعاتی SQL Server CE است. بنابراین اگر قصد استفاده از پروایدرهای دیگری را دارید باید به صورت صریح آن‌را ذکر نمائید:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="systemData:defaultProvider" value="System.Data.SqlClient" />
</appSettings>
<connectionStrings>
<add name="Northwind"
connectionString="Data Source=(local);Integrated Security = true;Initial Catalog=Northwind"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>

این تعاریف در فایل web.config و یا app.config برنامه وب یا ویندوزی شما قرار خواهند گرفت.

د) نحوه‌ی تعریف کوئری‌ها و دریافت اطلاعات
using System;
using WebMatrix.Data;

namespace TestMicrosoftDataLibrary
{
class Program
{
static void Main(string[] args)
{
getProducts();

Console.Read();
Console.WriteLine("Press a key ...");
}

private static void getProducts()
{
using (var db = Database.Open("Northwind"))
{
foreach (var product in db.Query("select * from products where UnitsInStock < @0", 20))
{
Console.WriteLine(product.ProductName + " " + product.UnitsInStock);
}
}
}
}
}
پس از افزودن ارجاعی به اسمبلی WebMatrix.Data و مشخص سازی رشته‌ی اتصالی به بانک اطلاعاتی، استفاده از آن جهت دریافت اطلاعات کوئری‌ها همانند چند سطر ساده‌ی فوق خواهد بود که از امکانات dynamic زبان سی شارپ 4 استفاده می‌کند؛ به این معنا که product.ProductName و product.UnitsInStock در زمان اجرا مورد ارزیابی قرار خواهند گرفت.
همچنین نکته‌ی مهم دیگر آن نحوه‌ی تعریف پارامتر در آن است (همان 0@ ذکر شده) که نسبت به ADO.NET کلاسیک به شدت ساده شده‌است (و نوشتن کوئری‌های امن و SQL Injection safe را تسهیل می‌کند).
در اینجا Database.Open کار گشودن name ذکر شده در فایل کانفیگ برنامه را انجام خواهد داد. اگر بخواهید این تعاریف را در کدهای خود قرار دهید (که اصلا توصیه نمی‌شود)، می‌توان از متد Database.OpenConnectionString استفاده نمود.

یا مثالی دیگر: استفاده از LINQ حین تعریف کوئری‌ها:
private static void getCustomerFax()
{
using (var db = Database.Open("Northwind"))
{
var product = db.Query("SELECT * FROM [Customers] WHERE City=@0", "Paris").FirstOrDefault();
if (product != null)
Console.WriteLine(product.Fax);
else
Console.WriteLine("not found.");
}
}

ه) اجرای کوئری‌ها بر روی بانک اطلاعاتی
private static void ExecQuery()
{
using (var db = Database.Open("Northwind"))
{
int affectedRecords = db.Execute("UPDATE [Customers] SET fax = fax + '*' WHERE City = @0", "Paris");
Console.WriteLine("Affected records: {0}", affectedRecords);
}
}

با استفاده از متد Execute آن می‌توان کوئری‌های دلخواه خود را بر روی بانک اطلاعاتی اجرا کرد. خروجی آن تعداد رکورد تغییر کرده است.

و) نحوه‌ی اجرای یک رویه ذخیره شده و نمایش خروجی آن
private static void ExecSPShowResult()
{
using (var db = Database.Open("Northwind"))
{
var customer = db.Query("exec CustOrderHist @0", "ALFKI").FirstOrDefault();
if (customer != null)
{
Console.WriteLine(customer.ProductName);
}
}
}
در این مثال رویه ذخیره شده CustOrderHist در بانک اطلاعاتی Northwind اجرا گردیده و سپس اولین خروجی آن نمایش داده شده است.

ز) اجرای یک تابع و نمایش خروجی آن
private static void useFuncs()
{
using (var db = Database.Open("Northwind"))
{
var query = db.Query("SELECT dbo.FN_GET_CATEGORY_TREE(@0) as Rec1", 3);
foreach(var tree in query)
{
Console.WriteLine(tree.Rec1);
}
}
}
در اینجا تابع FN_GET_CATEGORY_TREE موجود در بانک اطلاعاتی Northwind انتخاب گردیده و سپس خروجی آن به کمک یک نام مستعار (برای مثال Rec1) نمایش داده شده است.

سؤال : آیا WebMatrix.Data.dll بهتر است یا استفاده از ORMs ؟

در اینجا چون از قابلیت‌های داینامیک زبان سی شارپ 4 استفاده می‌شود، کامپایلر درکی از اشیاء خروجی و خواص آن‌ها برای مثال tree.Rec1 (در مثال آخر) ندارد و تنها در زمان اجرا است که مشخص می‌شود آیا یک چنین ستونی در خروجی کوئری وجود داشته است یا خیر. اما حین استفاده از ORMs این طور نیست و Schema یک بانک اطلاعاتی پیشتر از طریق نگاشت‌های جداول به اشیاء دات نتی، به کامپایلر معرفی می‌شوند و همین امر سبب می‌شود تا اگر ساختار بانک اطلاعاتی تغییر کرد، پیش از اجرای برنامه و در حین کامپایل بتوان مشکلات را دقیقا مشاهده نمود و سپس برطرف کرد.
ولی در کل استفاده از این کتابخانه نسبت به ADO.NET کلاسیک بسیار ساده‌تر بوده، می‌توان اشیاء و خواص آن‌ها را مطابق نام جداول و فیلدهای بانک اطلاعاتی تعریف کرد و همچنین تعریف پارامترها و برنامه نویسی امن نیز در آن بسیار ساده‌تر شده است.

برای مطالعه بیشتر:
Introduction to Microsoft.Data.dll

مطالب
آموزش MDX Query - قسمت ششم – شروع کار با دستورات MDX

امروز اولین دستورات MDX را خواهیم نوشت قبل از شروع کار فراموش نکنید موارد زیر را حتما انجام داده باشید :

  1. نصب پایگاه داده ی Adventure Work DW 2008 و همچنین نصب پایگاه داده‌ی چند بعدی  Adventure Work DW 2008  روی SSAS
  2. مطاله قسمت‌های قبلی برای آشنایی با مفاهیم پایه .

در صورتیکه پیش شرایط فوق را نداشته باشید، احتمالا در ادامه با مشکلاتی مواجه خواهید شد؛ زیرا برای آموزش 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 خواهیم پرداخت.

مطالب
سیستم‌های توزیع شده در NET. - بخش پنجم - اهداف
در بخشهای قبل، دلایل بوجود آمدن سیستمهای توزیع شده بررسی شد و تاکید کردیم که نیازمندی‌ها، باعث تغییر و تکامل سیستمهای ما می‌شوند و بر همین اساس بررسی کردیم که چه نیازمندی‌هایی باعث می‌شوند که دیگر سیستم‌های متمرکز به تنهایی پاسخگوی نیازهای ما نباشند و عاملی شوند برای رفتن به سمت سیستمهای توزیع شده. گفتیم که اتخاذ تصمیمات نادرست چه عواقبی را برای سیستمهای ما بوجود می‌آورد و بر همین اساس مهمترین فاکتورها را در انتخاب سیستم‌های توزیع شده، به شما معرفی کردیم. تعاریف مختلفی از این نوع سیستم‌ها را در اختیار شما قرار دادیم؛ خصوصیات، مزایا و معایب سیستمهای توزیع شده را به شما معرفی کردیم، تا با دید باز، یک انتخاب درست را با کمترین میزان ریسک انجام دهیم.
در این بخش ما 4 هدف اصلی را که باید در سیستم‌های توزیع شده برآورده شوند، بر اساس ارزش آنها مورد بررسی قرار می‌دهیم. یک سیستم توزیع شده باید اولا منابع را به آسانی در دسترس کاربران قرار دهد. دوما شفاف باشد و تمام پیچیدگی‌های یک سیستم توزیع شده را از دید کاربر، مخفی کند. سوما باز و قابل گسترش باشد؛ بصورتیکه به آسانی و با شفافیت کامل بتوانیم اجزای جدیدی را به آن اضافه کنیم. چهارما مقیاس پذیر باشد؛ بصورتیکه بدون اینکه مشکلی برای سیستم بوجود بیاید، بتوانیم منابع موجود سیستم را افزایش دهیم. در این بخش جزئیات هر یک از این اهداف را مورد بررسی قرار می‌دهیم.


اهداف سیستم‌های توزیع شده

1- Connecting resources and users: مشخص‌ترین و اصلی‌ترین هدف سیستم‌های توزیع شده، اتصال کاربران به منابع و اشتراک منابع بصورت کنترل شده با کارآیی بالا برای کاربران است. به‌صورتیکه کاربران  به آسانی به منابع دسترسی داشته باشند. منظور از منابع، هرچیزی در سیستمهای توزیع شده می‌تواند باشد. منابعی مانند داده‌ها، سخت‌افزارها، فایلها، Componentها، زیرسیستمها و هر چیز دیگری که کاربران می‌توانند بصورت مستقیم و غیر مستقیم به آنها دسترسی داشته باشند. یکی از مهمترین دلایل دسترسی به این هدف، مسائل اقتصادی است. بطور مثال ممکن است ما سیستم خودمان را طوری طراحی کنیم که پردازش‌های بسیار مهم و پیچیده در تعدادی از سخت افزار‌های بسیار پر هزینه انجام شوند. به این صورت ما این سخت افزارها را برای سایر قسمت‌های سیستم، به اشتراک میگزاریم و از طریق دیگر قسمتهای سیستم، دسترسی آن را به کاربران می‌دهیم. در این حالت دیگر نیازی نیست هر قسمت جداگانه در سیستم، سخت افزاری بسیار قوی را برای خودش نیاز داشته باشد. یا زمانیکه ما یک زیرسیستم را یکبار پیاده سازی می‌کنیم و دسترسی آن را به سایر قسمت‌ها میدهیم، سایر سیستمها، دیگر نیازی نیست آن زیرسیستم را برای خودشان پیاده سازی کنند و تنها از زیرسیستم موجود استفاده می‌کنند. دسترسی به این هدف باعث می‌شود تا کاربران به راحتی به تمام منابع موجود در سیستم‌های توزیع شده دسترسی داشته باشند.

2- Distribution transparency: بدلیل پیچیدگی‌های بسیار زیاد در سیستم‌های توزیع شده، شفافیت، کمک بسیار بزرگی به سادگی نحوه تعامل کاربر با سیستم‌های توزیع شده می‌کند. شفافیت در سیستم‌های توزیع شده بدین معنا است که سیستم باید تمام پیچیدگی‌های خود را از دید کاربر مخفی کند و پیاده سازی سیستم بصورت توزیع شده نباید هیچ پیچیدگی را در نحوه تعامل کاربر با سیستم بوجود بیاورد.
درواقع در سیستمهای توزیع شده، درخواست دریافت شده، در بین منابع سیستم توزیع می‌شود. تمام منابع با همکاری که با یکدیگر انجام می‌دهند، درخواست مورد نظر را پردازش کرده و پاسخ لازم را به کاربر ارائه می‌دهند. در این بین ممکن است منابع موجود در سیستم، رفتارهای متفاوتی را داشته باشند؛ مثلا هر زیرسیستم در سخت افزار و سیستم عامل جداگانه‌ای اجرا شود که باعث می‌شود حتی نحوه دستیابی به فایل‌ها یا داده‌های هر زیرسیستم نیز با سایر زیرسیستمها متفاوت باشد. شفافیت در سیستمهای توزیع شده بدین معنا است که یک سیستم توزیع شده باید خود را به‌صورت یک سیستم واحد که در یک سخت افزار ارائه می‌شود، ارائه دهد تا کاربران هیچ نگرانی در نحوه تعامل با سیستم نداشته باشند. شفافیت در سیستمهای توزیع شده جنبه‌های مختلفی دارد که در این قسمت آنها را بررسی می‌کنیم.


جنبه‌های مختلف شفافیت در سیستم‌های توزیع شده

1- Access Transparency: شفافیت در دسترسی به منابع یا مخفی کردن پیچیدگی‌ها و روشهای مختلف دسترسی به منابع. زیر سیستم‌های متفاوت ممکن است در سیستم عامل‌های متفاوتی نیز اجرا شوند و همانطور که می‌دانید هر سیستم عامل ممکن است نحوه دسترسی به منابعش با سایر سیستم عامل‌ها متفاوت باشد. در اینجا ما باید زیر سیستم‌های خود را طوری طراحی کنیم تا این تفاوت‌های در نحوه دسترسی به منابع را از دید کاربر مخفی کنند و این حس را به کاربر بدهد که سیستم، یک روش واحدی را برای دسترسی به منابع دارد.

2- Location Transparency: شفافیت در مکان منابع و مخفی کردن اینکه منابع در سطح شبکه توزیع شده‌اند. این جنبه بیان می‌کند که کاربران نمی‌توانند بگویند که منابع بصورت فیزیکی در سخت افزار‌های متفاوتی توزیع شده‌اند.کاربران هیچ درکی از اینکه ممکن است منابع حتی بصورت جغرافیایی در مکان‌های بسیار دوری از یکدیگر قرار گرفته‌اند، ندارند.

3- Migration Transparency: مخفی کردن اینکه ممکن است مکان منابع تغییر یابند. در یک سیستم توزیع شده ممکن است به هر دلیلی، مکان منابع تغییر کند. بطور مثال ممکن است با افزایش داده‌ها نیاز به افزودن Node جدیدی به سیستم باشد تا قسمتی از داده‌های سیستم از این پس از طریق این Node در دسترس باشند. این جابجایی منابع نباید هیچ تاثیری در نحوه‌ی تعامل کاربر داشته باشد.

4- Relocation Transparency: مخفی کردن اینکه منابع ممکن است در زمان استفاده، تغییر مکان دهند. در این حالت دقیقا در زمانیکه شخص در حال استفاده از منابع است ممکن است منابع جابجا شوند که این جابجایی در زمان استفاده از منابع نباید هیچ تاثیری در نحوه تعامل کاربر با سیستم داشته باشد. بطور مثال زمانیکه دو کاربر از طریق تلفن همراه در حال ارسال اطلاعات برای یکدیگر هستند، جابجایی هر یک از این دو کاربر، نباید تاثیری در جریان ارتباطی آنها داشته باشد.

5- Replication Transparency: مخفی کردن اینکه منابع در چند جا کپی شده‌اند. در یک سیستم توزیع شده برای بهبود کارآیی و دسترسی ممکن است منابع در چند جای مختلف کپی شوند و شفافیت در Replication می‌گوید ما باید این واقعیت را که ممکن است منابع ما در سخت افزارهای مختلفی کپی شده باشند، از دید کاربر مخفی کنیم.

6- Concurrency Transparency: مخفی کردن اینکه ممکن است منابع، بین چند کاربر مشترک باشند. بدلیل بالا بودن تعداد کاربران در سیستمهای توزیع شده، همزمانی در دسترسی به منابع مشترک، بسیار بیشتر اتفاق می‌افتد و این مهم است که هیچ یک از کاربران ندانند که کاربر دیگری بصورت همزمان در حال استفاده از آن داده است.

7- Failure Transparency: مخفی کردن خرابی و بازیابی منابع یک سیستم توزیع شده، از کاربر. در یک سیستم توزیع شده ممکن است منابع به هر دلیلی از دسترس خارج شوند. در این صورت نباید از دسترس خارج شدن منابع و بازیابی آنها هیچ تاثیری در جریان تعامل کاربر با سیستم داشته باشد.


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



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

4- Scalability: مقیاس پذیری سیستم‌های توزیع شده بدین معنی است که با رشد مواردی مانند تعداد پردازش و درخواست یا موقعیت جغرافیایی کاربران، سیستم قادر باشد بدون تاثیر بر جریان تعامل کاربر با سیستم، آنها را پوشش دهد. یعنی بطور مثال زمانیکه تعداد درخواست‌های کاربران سیستم افزایش می‌یابد، با Replicate قسمتهای موجود سیستم در سخت افزار‌های جدید می‌توانیم بار پردازشی را بین تعداد بیشتری از Node‌ها تقسیم کنیم. به این صورت سیستم می‌تواند بدون از دسترس خارج شدن، تعداد بیشتری از درخواستهای کاربران را پوشش دهد. یا زمانیکه قرار است موقعیت‌های جدید جغرافیایی را سیستم پوشش دهد، با اضافه کردن منابع مورد نیاز، در آن موقعیت جغرافیایی، سیستم قادر است نیاز کاربران آن مکان جغرافیایی را برآورده کند.


 تا به این قسمت از سری مقالات سیستم‌های توزیع شده، هدف من این بوده که با چرایی وجود این نوع از سیستم‌ها و نحوه‌ی انتخاب آنها و اهداف سیستم‌های توزیع شده آشنا شوید. در بخش‌های بعد، روشهای مختلف طراحی و پیاده سازی سیستم‌های توزیع شده را مورد بررسی قرار می‌دهیم و می‌بینیم که چه ابزارهایی برای پیاده سازی سیستم‌های توزیع شده در NET. وجود دارند و با توجه به نوع کارآیی هر یک از این ابزارها، آنها را بصورت جداگانه مورد استفاده قرار می‌دهیم تا با مزایا و معایب هریک آشنا شویم. البته این را نیز ذکر کنم که با توجه به تعاریف سیستم‌های توزیع شده، انواع مختلفی از سیستم‌های توزیع شده وجود دارند که ما از قبل با برخی از آنها آشنا هستیم؛ معماری‌هایی مانند Client/Server یا N-Tier  نمونه‌هایی از سیستم‌های توزیع شده هستند که در آنها وظایف، در سخت افزارهای متفاوتی تقسیم شده و ارتباط هر قسمت از طریق سرویس‌هایی که ما پیاده سازی می‌کنیم، صورت می‌پذیرد و به دلیل اینکه مطمئنا همه شما با نحوه طراحی و پیاده سازی آنها و نحوه ارتباط قسمتهای مختلف آنها آشنا هستید، در سری مقالات سیستمهای توزیع شده در NET.، دیگر نیازی به توضیحی در مورد آنها نمی‌باشد. هدف من از قسمت‌های مرتبط با پیاده سازی سیستم‌های توزیع شده، چگونگی تکامل معماری‌هایی مانند N-Tier بوسیله ابزارهایی است که با هدف دستیابی به خصوصیات و اهداف سیستم‌های توزیع شده بوجود آمده‌اند.  
مطالب
نحوه نمایش منوهای Visual studio 2012 با حروف کوچک
چند روز پیش بصورت اتفاقی به این فکر افتادم که چرا منوهای visual studio 2012 برخلاف ظاهر زیبای خود محیط، اینقدر زمخت و با حروف بزرگ نوشته است.


و اینکه به چه صورت میتوانم آنها را بصورت حروف کوچک نمایش دهم و حس کنجکاوی اونم از نوع مخصوص گل کرد.


برای اینکار دو روش وجود دارد :
روش 1 - تغییر مقدار در رجیستری سیستم عامل ویندوز
 بدین صورت که شما باید به این مسیر مراجعه نموده
در ویندوز 7 :
  HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0\General\
در ویندوز 8 :
 HKEY_CURRENT_USER\Software\Microsoft\VSWinExpress\11.0\General\
در نسخه web express:
 HKEY_CURRENT_USER\Software\Microsoft\VSWDExpress\11.0\General\

ایجاد یک کلید از نوع DWORD :


و با نام SuppressUppercaseConversion و با مقدار 1 در مسیر یاد شده تنظیم نمائید.

 
سپس راه اندازی مجدد visual studio  و مشاهده منوهای تغییر یافته .

روش 2 - کسانی مثل من کمی تنبل هستند و از این کارهای فوق دوست ندارند راه آسانتر را میتوانند تجربه کنند بصورت ذیل:
 
 در منوی start ویندوز با تایپ کلمه powershell  و انتخاب Windows PowerShell به صفحه‌ای آبی رنگ (در ویندوز 7 ) وارد میشود .


 دستور ذیل را کپی و به پنجره powershell انتقال دهید :
Set-ItemProperty -Path HKCU:\Software\Microsoft\VisualStudio\11.0\General -Name SuppressUppercas
eConversion -Type DWord -Value 1

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

یک تجربه
سالها پیش یکی از همکاران تعریف می‌کردند که یک شرکت نرم افزاری برای مشاوره معماری نرم افزار از ایشان دعوت به همکاری کرده است. پس از مراجعه به شرکت متوجه شدند که تیم اصلی برنامه نویسان درگیر تولید ORM ای برای پروژه جدید شرکت هستند که برای تولید این ابزار بیش از 4 ماه را وقت صرف کرده‌اند؛ اما در مراحل نهایی کار دچار مشکلات زیادی شده اند. به نحوی که از ایشان برای کمک به رفع مشکل ORM ( به جای تولید نرم افزار مشتری) دعوت کرده‌اند.
 
در آن زمان یادم هست که EF 5 (که تقریبا نسخه سوم  بعد از 3.5 و 4 می‌باشد - جزئیات در اینجا) توسط مایکروسافت ارائه شده بود. همچنین NHibernate هم همزمان با EFها (تاریخچه نسخه‌ها در اینجا) قابل دسترسی بوده‌است. با این حال تیم فنی به این دلیل که کوئری‌های تولیدی توسط EF کند هستند، اقدام به ساخت ORM کرده بودند. جالب اینکه با بررسی بیشتر مشخص شده‌است که حجم داده‌های پروژه در بدترین حالت در یک جدول به 5 هزار رکورد می‌رسد.

4 ماه صرف وقت و هزینه تیم 2 نفره برای طراحی و پیاده سازی و تست ORM ای که در نهایت به دلیل مشکلات Performance کنار گذاشته شد و از EF استفاده کردند. شاید در این 4 ماه می‌توانستند 30 درصد پروژه اصلی را پیاده سازی کنند.

شاید بتوان 3 دلیل عمده «فنی» شکست برخی از پروژه‌های نرم افزاری در ایران را به شرح زیر عنوان کرد:
- عدم استفاده مناسب از ابزارها و راهکار‌های موجود و انجام دوباره کاری
- استفاده غیر ضروری و عجولانه از تکنولوژی‌های جدید (بدون داشتن نیروی کار مسلط)
- پایین بودن سطح فنی و به‌روز نبودن برخی از برنامه نویسان ایرانی


متن باز (Open Source)
با پیشرفت توسعه نرم افزار و تمایل شرکت‌های بزرگ دنیا به تولید کامپوننت‌های متن باز (Open Source) ریسک استفاده از این نوع ابزار‌ها نیز کمتر شده است. بطوریکه درصورت نیاز می‌توان کامپوننت را برای پروژه‌ها سفارش سازی کرد.
شاید کمتر کسی باور می‌کرد که روزی شرکت مایکروسافت محصولات خود را Open Source کند. اما امروز، در سال 2017 میلادی، شرکت مایکروسافت اقدامات مهمی را در این زمینه انجام داده است که می‌توانید جزئیات پروژه‌های متن باز این غول کامپیوتری دنیا را در اینجا و همچنین اینجا ملاحظه کنید.

 
یک سناریو
فرض کنید یک پروژه تحت وب را شروع کرده اید. بدون در نظر گرفتن جزئیات پروژه می‌توان گفت به ابزارهای زیر نیاز خواهید داشت:

ابزار
مثال
  ORM   EF , NHibernate , Dapper , LLBLGEN 
 IOC COntainer   Unity , StructureMap , Autofac , Castle.Windsor, LightInject , Ninject 
 Report Tools   CrsytalReport , Stimusoft , DevExpress Report, Telerik Report Tools, EasyReport 
 UI Component   Telerik , JqueryUI , Bootsrap ,CompnentArt, ComponentOne 
 Error Logger   ELMAH , NLog , log4net 
 Mapper Tools   AutoMapper , ValueInjecter 
همانطور که ملاحظه می‌کنید برای همه‌ی موارد فوق ابزارهای مناسبی وجود دارند که برای پیاده سازی هر کدام، سالها وقت و هزینه صرف شده‌است. همچنین قابلیت اطمینان این ابزار‌ها به مراتب بالاتر از ابزارهای دست ساز خواهد بود. شاید برای ساده‌ترین ابزار فوق 3 ماه زمان لازم باشد تا یک نسخه  باگ دار تهیه شود!


ملاحظات استفاده از ابزارها
توجه به چند نکته در استفاده از ابزارها و کتابخانه‌های آماده ضروری می‌باشد، بدین شرح:
- ابزار مورد نیاز را با R&D (تحقیق و توسعه) انتخاب کنید. ابزارهایی که در پروژه‌های واقعی استفاده شده‌اند، بسیار مناسب می‌باشند.
- توجه داشته باشیدکه استفاده از چندین ابزار باعث ایجاد تداخل در پروژه نشود (این مورد معمولا در کامپوننت‌های UI مانند JqueryUI و Bootsrtap اتفاق می‌افتد)
- مستندات مربوط به ابزار‌ها را حتما مطالعه کنید. لطفا بدون تسلط از ابزاری استفاده نکنید.

گاهی پیش می‌آید که یک برنامه نویس بدون مطالعه مستندات مربوط به یک IOC Container از آن ابزار استفاده میکند و در Register اولیه ویژگی LifeCycle مربوط به Context  را با حالت Singleton مقداردهی میکند. بدین ترتیب پس از نیم ساعت، پروژه به دلیل آنچه که می‌توان "چاقی Context" نامید، DONE یا حداقل کند می‌شود که رفع این مشکل ساعت‌ها زمان می‌برد.

درصورت امکان از ابزارها بصورت مستقیم استفاده نکنید. یک لایه واسط مخصوص خودتان را برای تنظیمات کلی ابزار‌ها تهیه کنید که در آینده به دردتان خواهد خورد! (بیشتر در سمت سرور)

فرض کنید در پروژه WPF از کامپوننت‌های زیبای DevExpress استفاده میکنید. به ازای هر کامپوننت یک کلاس به پروژه اضافه کنید که از کلاس اصلی آن کامپوننت Devexspress ارث می‌برد و در لایه UI خود از کلاس جدید خود استفاده کنید. با این کار می‌توانید ویژگی‌های عمومی کامپوننت‌ها را یکبار برای کل پروژه اعمال کنید.


  نتیجه گیری
  اگر بخواهیم چرخ را اختراع نکنیم و از تجربیات موفق موجود استفاده کنیم، می‌توان نتیجه گرفت که استفاده از ابزارهای آماده برای توسعه نرم افزار با رعایت دستورالعمل استفاده امری مفید می‌باشد. اما باید توجه داشته باشیم که استفاده از هر ابزاری به هرقیمتی در هرپروژه‌ای، حرفه ای نیست. همه‌ی راهکارها، ابزراها و تکنولوژی‌های مورد استفاده باید در راستای هدف اصلی «تولید و تحویل به موقع نرم افزار با کیفیت به مشتری» باشد؛ هدفی که در بسیاری از موارد فراموش شده و بیشتر زمان پروژه، صرف کارهای غیر ضروری می‌شود.
مطالب
ASP.NET MVC #16

مدیریت خطاها در یک برنامه ASP.NET MVC


استفاده از فیلتر HandleError

یکی از فیلترهای توکار ASP.NET MVC به نام HandleError،‌ می‌تواند کار هدایت کاربر را به یک صفحه‌ی خطای عمومی، در حین بروز استثنایی در برنامه،‌ انجام دهد. برای آزمایش آن یک برنامه خالی جدید ASP.NET MVC را آغاز کنید. سپس یک کنترلر جدید را با محتوای زیر به آن اضافه نمائید:

using System;
using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class HomeController : Controller
{
[HandleError]
public ActionResult Index()
{
throw new InvalidOperationException();
return View();
}
}
}

در اینجا جهت آزمایش برنامه، به عمد یک استثنای دستی را صادر می‌کنیم. برای آزمایش برنامه هم نیاز است آن‌را خارج از دیباگر VS.NET اجرا کرد (آدرس برنامه را مستقیما خارج از VS.NET در یک مرورگر وارد کنید). همچنین یک سطر زیر را نیز لازم است به فایل web.config برنامه اضافه نمائید:

<system.web>
<customErrors mode="On" />

اکنون اگر برنامه را خارج از مرورگر اجرا کنید، با توجه به استفاده از ویژگی HandleError و همچنین بروز یک استثنا در متد Index، خودبخود صفحه Views\Shared\Error.cshtml به کاربر نمایش داده خواهد شد. در غیراینصورت صفحه زرد رنگ پیش فرض خطای ASP.NET به کاربر نمایش داده می‌شود که محتوای آن‌ها بیشتر برای برنامه نویس‌ها مناسب است و نه کاربران نهایی سیستم.
اگر علاقمند باشید که این ویژگی به صورت خودکار به تمام متدهای کنترلرهای برنامه اعمال شود، کافی است یک سطر زیر را به متد Application_Start فایل Global.asax.cs اضافه نمائید:

GlobalFilters.Filters.Add(new HandleErrorAttribute());

البته نیازی به انجام اینکار نیست زیرا اگر به متد RegisterGlobalFilters فایل Global.asax.cs دقت کنیم، اینکار پیشتر توسط قالب پیش فرض VS.NET انجام شده است. فقط برای فعال سازی آن نیاز است تگ customErrors در فایل وب کانفیگ برنامه مقدار دهی و تنظیم شود.



استفاده از صفحه خطای سفارشی دیگری بجای فایل Error.cshtml

امکان تنظیم نمایش صفحه خطای سفارشی دیگری نیز وجود دارد. برای مثال استفاده از فایل Views\Shared\CustomErrorView.cshtml :

[HandleError(View = "CustomErrorView")]



استفاده از صفحات خطای متفاوت به ازای استثناهای مختلف

می‌توان فیلتر HandleError را تنها به یک نوع استثنای خاص محدود کرد. همچنین امکان استفاده از چندین ویژگی HandleError برای یک متد نیز وجود دارد:

[HandleError(ExceptionType = typeof(NullReferenceException), View = "ErrorHandling")]



دسترسی به اطلاعات استثناء در صفحه نمایش خطاها

زمانیکه برنامه به صفحه خطا هدایت می‌شود، نوع Model آن System.Web.Mvc.HandleErrorInfo می‌باشد:

@model System.Web.Mvc.HandleErrorInfo

@{
ViewBag.Title = "DbError";
}

<h2>An Error Has Occurred</h2>

@if (Model != null)
{
<p>@Model.Exception.GetType().Name<br />
thrown in @Model.ControllerName @Model.ActionName</p>
}

البته این نکته را صرفا به عنوان اطلاعات عمومی در نظر داشته باشید. زیرا اگر قرار باشد مجددا اصل استثناء را نمایش دهیم، همان صفحه زرد رنگ ASP.NET شاید بهتر باشد.



استفاده از تگ customErrors در فایل Web.config برنامه

ویژگی حالت تگ customErrors در فایل web.config برنامه، سه مقدار را می‌تواند بپذیرد:
الف) Off : صفحه زرد رنگ معرفی خطای ASP.NET را به همراه تمام اطلاعات مرتبط با استثنای رخ داده نمایش می‌دهد.
ب) RemoteOnly : همان حالت الف است با این تفاوت که صفحه خطا را فقط در کامپیوتری که وب سرور بر روی آن نصب است نمایش خواهد داد.
ج) On : یک صفحه خطای سفارشی شده را نمایش می‌دهد.

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



مدیریت خطاهای رخ داده خارج از MVC Pipeline

HandleErrorAttribute تنها استثناهای رخ داده داخل ASP.NET MVC Pipeline را مدیریت می‌کند (یا خطاهایی از نوع 500). اگر این نوع استثناها خارج از آن رخ دهند مثلا فایلی یافت نشود (خطای 404) و امثال آن، باید به روش زیر عمل کرد:

<customErrors mode="On" defaultRedirect="error">
<error statusCode="404" redirect="error/notfound" />
<error statusCode="403" redirect="error/forbidden" />
</customErrors>

در اینجا اگر فایلی یافت نشد، کاربر به کنترلری به نام error و متدی به نام notfound هدایت خواهد شد. بنابراین نیاز به کنترلر زیر وجود دارد؛ به علاوه به ازای هر متد هم یک View متناظر باید اضافه شود (کلیک راست روی نام متد و انتخاب گزینه افزودن View جدید).

using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
public class ErrorController : Controller
{
public ActionResult Index()
{
return View();
}

public ActionResult NotFound()
{
return View();
}

public ActionResult Forbidden()
{
return View();
}
}
}

برای آزمایش این قسمت، برنامه را اجرا کرده و سپس مثلا آدرس غیرموجود http://localhost/xyz را وارد کنید.



استفاده از فیلتر HandleError اجباری نیست

در همین قسمت قبل پس از افزودن customErrors و defaultRedirect آن که به نام یک کنترلر اشاره می‌کند، کلیه فیلترهای HandleError اضافه شده به برنامه را حذف کنید. سپس برنامه را خارج از محیط VS.NET اجرا کنید. باز هم متد Index کنترلر Error اجرا خواهد شد. به عبارتی الزاما نیازی به استفاده از فیلتر HandleError نیست و به کمک مقدار دهی صحیح تگ customErrors، کار نمایش خودکار صفحه سفارشی خطاها به کاربر انجام خواهد شد.
البته بدیهی است که گزینه‌های نمایش یک View خاص به ازای استثنایی ویژه، یکی از مزیت‌های استفاده از فیلتر HandleError می‌باشد که امکان تنظیم آن در فایل web.config وجود ندارد.



ثبت اطلاعات استثناهای رخ داده به کمک ELMAH

نمایش صفحه‌ی خطای سفارشی به کاربر، یکی از موارد ضروری تمام برنامه‌های ASP.NET است، اما کافی نیست. ثبت اطلاعات جزئیات استثناهای رخ داده در طول زمان می‌توانند به بالا بردن کیفیت برنامه به شدت کمک کنند. برای این منظور می‌توان همانند سابق از متد Application_Error قابل تعریف در فایل Global.asax.cs کمک گرفت؛ اما با وجود افزونه‌ای به نام ELMAH اینکار اتلاف وقت است و اصلا توصیه نمی‌شود. همچنین به کمک ELMAH می‌توان مشکلات را تبدیل به ایمیل‌های خودکار کرد یا از آن‌ها فید RSS درست نمود.
برای دریافت ELMAH یا به سایت اصلی آن مراجعه نمائید و یا به کمک NuGet هم به سادگی قابل دریافت است. پس از دریافت، ارجاعی را به اسمبلی آن (Elmah.dll) اضافه نمائید. در ادامه فایل web.config برنامه را گشوده و چند سطر زیر را به آن در قسمت configuration اضافه کنید:

<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

سپس ذیل قسمت appSettings، تنظیمات پروایدر ذخیره سازی اطلاعات آن‌را وارد نمائید. مثلا در اینجا از فایل‌های XML برای ذخیره سازی اطلاعات استفاده خواهد شد (که امن‌ترین حالت ممکن است؛ از این لحاظ که اگر بانک اطلاعاتی را انتخاب کنید، ممکن است مشکل اصلی از همانجا ناشی شده باشد. بنابراین خطایی ثبت نخواهد شد. همچنین در این حالت نیازی به سایر DLLهای همراه ELMAH هم نیست). در اینجا مسیر ذخیره سازی اطلاعات در پوشه app_data/errorslog تنظیم شده است:

<elmah>
<security allowRemoteAccess="1"/>
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/ErrorsLog"/>
</elmah>

در ادامه در قسمت system.web، دو تعریف زیر را اضافه نمائید. به این ترتیب امکان دسترسی به آدرس http://server/elmah.axd مهیا می‌گردد:

<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</httpHandlers>

البته برای IIS7 تنظیمات ذیل نیز باید اضافه شوند:

<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</modules>
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
</handlers>
</system.webServer>

و به این ترتیب تنظیمات اولیه ELMAH به پایان می‌رسد (و با ASP.NET Web forms هیچ تفاوتی ندارد).
مرحله بعد، تنظیمات مسیریابی ASP.NET MVC است برای اینکه آدرس http://server/elmah.axd را وارد سیستم پردازشی خود نکند. البته اینکار پیشتر انجام شده است:

public static void RegisterRoutes(RouteCollection routes)
{
//routes.IgnoreRoute("elmah.axd");
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

بنابراین همین تنظیمات، به همراه قالب پیش فرض یک پروژه جدید ASP.NET MVC برای استفاده از ELMAH کفایت می‌کند. اکنون پروژه جاری را یکبار دیگر خارج از VS.NET اجرا کرده و سپس به مسیر http://localhost/elmah.axd جهت مشاهده خطاهای لاگ شده به همراه جزئیات کامل آن‌ها مراجعه کنید.

مشکل: استثناهای برنامه توسط ELMAH لاگ نمی‌شوند!

فیلتر HandleError با ELMAH سازگار نیست. زیرا با استفاده از آن، متدهای کنترلرها به صورت خودکار داخل یک try/catch اجرا شده و به این ترتیب استثناهای رخ داده، مدیریت گردیده و به ELMAH هدایت نمی‌شوند. بنابراین نیاز است به متد RegisterGlobalFilters فایل Global.asax.cs مراجعه کرده و سطر زیر را حذف کنید:

filters.Add(new HandleErrorAttribute());

و یا اگر قصد نداشتید اینکار را انجام دهید، می‌توان به نحو زیر نیز مشکل را حل کرد:

using System.Web.Mvc;
using Elmah;

namespace MvcApplication13.CustomFilters
{
public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.ExceptionHandled)
ErrorSignal.FromCurrentContext().Raise(context.Exception);
// all other exceptions will be caught by ELMAH anyway
}
}
}

در اینجا یک فیلتر سفارشی به برنامه اضافه شده است تا خطاهای مدیریت شده برنامه (خطاهای مدیریت شده توسط فیلتر HandleError توکار) را به موتور ELMAH هدایت کند. سایر خطاهای مدیریت نشده به صورت خودکار توسط ELMAH ثبت خواهند شد و نیازی به انجام کار اضافی در این مورد نیست.
سپس این فیلتر جدید را به صورت سراسری تعریف کنید:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ElmahHandledErrorLoggerFilter());
filters.Add(new HandleErrorAttribute());
}

ترتیب این‌ها هم مهم است. ابتدا باید ElmahHandledErrorLoggerFilter معرفی شود.


تذکر مهم!
حین استفاده از ELMAH یک نکته را فراموش نکنید:
اگر allowRemoteAccess آن‌را به عدد 1 تنظیم کرده‌اید، به هیچ عنوان از نام پیش فرض elmah.axd استفاده نکنید (هر نام اختیاری دیگری را که علاقمند بودید و به سادگی قابل حدس زدن نبود، در فایل web.config وارد کنید).


خلاصه بحث
1- در ASP.NET MVC نیازی نیست تا متدهای کنترلرها را با try/catch شلوغ کنید.
2- حتما قسمت customErrors فایل وب کانفیگ برنامه را دهی کنید (این مورد را به چک لیست اجباری تهیه یک برنامه ASP.NET MVC اضافه کنید).
3- استفاده از فیلتر HandleError اختیاری است. اگر از قابلیت فیلتر کردن استثناهای ویژه آن استفاده نمی‌کنید، مقدار دهی customErrors وب کانفیگ برنامه هم همان کار را انجام می‌دهد.
4- برای ثبت جزئیات دقیق استثناهای رخ داده در برنامه، از ELMAH استفاده کنید و بی‌جهت وقت خودتان را صرف بازنویسی این افزونه ارزشمند نکنید.

مطالب مشابه
معرفی ELMAH
ثبت استثناهای مدیریت شده توسط ELMAH

مطالب
نصب و راه اندازی SQL Server بر روی لینوکس با استفاده از Docker
 چند وقتی است مایکروسافت تعدادی از محصولات خود را به صورت سورس باز در اختیار برنامه نویسان قرار داده‌است. برای مثال شما می‌توانید در لینوکس یا مک، از نسخه‌ی net core. آن استفاده کنید. در این مقاله روش اجرای sql server را در لینوکس، با استفاده از docker؛ تشریح خواهیم کرد. همچنین با یک پروژه ساده net core.، بر روی دیتابیس add-migration را اجرا کرده و همچنین چند رکورد را در جدولی ثبت می‌کنیم. البته می‌توان نسخه‌ی نصبی sql server را بدون نیاز به docker نیز دانلود و آن را بر روی لینوکس نصب کرد.  


در این مقاله چه چیزی را پوشش خواهیم داد:‌

· راه اندازی داکر
· پیکره‌بندی container image
· وصل شدن به sql
· ساخت یک پروژه ساده net core.
· ایجاد دیتابیس
· ثبت رکورد در دیتابیس

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


بعد از اینکه docker را بر روی سیستم خود نصب کردید، می‌توانید از دستورات داکر استفاده کنید. در این مقاله می‌خواهیم sql server را بر روی داکر نصب و راه اندازی کنیم.
 

دانلود و نصب sql server بر روی داکر

ابتدا وارد این لینک شوید. همانطور که مشاهده میکنید، SQL Server در 3 نسخه‌ی ویندوز، لینوکس و docker قابل دانلود می‌باشد. چون میخواهیم sql server را بر روی docker نصب کنیم، پس گزینه‌ی docker را انتخاب کنید.


قبل از هرچیز باید Image اس‌‌کیوال سرور را بر روی داکر دانلود نمائید. برای این کار وارد سایت dockerhub شوید و عبارت microsoft/mssql-server-linux را جستجو کنید.


همانطور که در تصویر نیز مشاهد می‌کنید، این بسته 10 میلیون بار دریافت شده‌است! در ادامه دستور زیر را در ترمینال خود Paste کنید و منتظر بمانید تا دانلود شود:
docker pull microsoft/mssql-server-linux:2017-latest
همچنین با اسکرول کردن در این صفحه می‌توانید آموزش نصب و راه ندازی این image را ببینید. بعد از دانلود image مخصوص داکر، با دستور docker images all می‌توانید images دانلود شده را مشاهده کنید. ولی image‌ها به خودی خود  کاربردی ندارند و باید آن‌ها را اجرا کنیم.
برای اجرای image sql از دستور زیر استفاده میکنیم:
 sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=<YourStrong!Passw0rd>' \
-p 1433:1433 --name sql1 \
-d mcr.microsoft.com/mssql/server:2017-latest
در این دستور:
docker run –name sql : کار ساخت و اجرای Docker container ای به نام sql را انجام می‌دهد.
'e 'ACCEPT_EULA=Y- : سبب قرار دادن مقدار yes در ACCEPT_EULA که در قسمت environment variables تعریف شده‌است، می‌شود.
Set the  SA_PASSWORD : پسورد  environment variable ای که شما انتخاب می‌کنید.
p 1433:1433- : شماره پورتی که Docker container بر روی آن اجرا میشود.
-d microsoft/mssql-server-linux:2017-latest : نام Image ای که می‌خواهیم اجرا کنیم.

همانطور که مشاهده می‌کنید، Docker container بر روی پورت 1433 اجرا می‌شود. برای مشاهده جزئیات بیشتر، با وارد کردن دستو docker ps a می‌توان لیست containerها و وضعیت آن‌ها را مشاهده کرد.


همانطور که ملاحظه میکنید، در قسمت status، عبارت up به معنای در حال اجرا بودن container است. اگر عبارت دیگری را مشاهده کردید، با دستور dockr start id و وارد کردن شماره image خود می‌توانید آن را اجرا کنید.

تا اینجا توانستیم sql server  را اجرا کنیم. برای توضیحات بیشتر به این لینک مراجعه کنید.
 

وصل شدن به sql
برای وصل شدن به دیتابیس باید connection string دیتابیس مربوطه را داشته باشیم. با توجه به کانفیگ‌هایی که در بالا انجام دادیم، connection string ما به شکل زیر خواهد بود:
Server Host: localhost
Port: 1433
Authentication: SQL Server Authentication
Login: SA
Password: <StrongPasswordYouSet>
اگر کانکشن را به درستی کانفیگ کرده باشید، باید یک دیتابیس به نام انتخابی شما ایجاد شده باشد. در ادامه همین کار را بر روی یک پروژه‌ی  ساده netcore. انجام خواهیم داد. اما برای وصل شدن از طریق docker باید ابتدا bash (دستورات sqlcmd) را بارگذاری کنیم، تا بتوانیم به sqlcmd بر روی container در حال اجرا، دسترسی پیدا کنیم:
sudo docker exec -it sql1 "bash"
پس از آن باید sqlcmd  را به صورت مستقیم و از آدرس فیزیکی سیستم، درون container بارگذاری کنید:
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P '<YourNewStrong!Passw0rd>'
اگر دستور فوق با موفقیت اجرا شود ، عبارت 1> در ترمینال به نمایش در می‌آید. یعنی هم اکنون می‌توانید با تایپ دستوارت، آن‌ها را در sqlcmd اجرا کنید:


تا اینجای کار sql server  آماده‌ی اجرا دستورات شما می‌باشد. در ادامه می‌خواهیم چند دستور ساده‌ی sql را بر روی آن اجرا کنیم.


ساخت دیتابیس
با دستور sqlcmd زیر، ابتدا یک دیتابیس را میسازیم:
 CREATE DATABASE TestDB

ساخت جدول
در ادامه، دستور زیر را برای ساخت جدول مینویسیم:
 CREATE TABLE Inventory (id INT, name NVARCHAR(50), quantity INT)

ایجاد رکورد
مرحله بعدی، ایجاد یک رکورد جدید در دیتابیس میباشد:
 INSERT INTO Inventory VALUES (1, 'banana', 150); INSERT INTO Inventory VALUES (2, 'orange', 154);

در آخر با استفاده از دستور go، کوئری‌های بالا را اجرا می‌کنیم. اکنون باید یک دیتابیس جدید به نام TestDB و یک جدول جدید نیز به نام Inventory همچنین یک رکورد جدید در آن ثبت شده باشد. برای مشاهده‌ی تغییرات بالا، از دستورات زیر استفاده میکنیم:
- با دستور زیر لیست دیتابیس‌های موجود را می‌توان دید:
 SELECT Name from sys.Databases
- کو ئری select از دیتابیس:
 SELECT * FROM Inventory WHERE quantity > 152;

و با استفاده از دستور quit میتوانید از cmd خارج شوید.

تا اینجا توانستیم docker را بر روی سیستم راه ندازی و همچنین sql server  را بر روی آن نصب و اجرا کنیم. همچنین با دستورات sqlcmd توانستیم بر روی sql کوئری بزنیم.


ساخت و وصل شدن یک پروژه‌ی net core. و وصل شدن به sql server

حال میخواهیم با یک پروژه‌ی ساده‌ی net core. به sql server فوق وصل شده و یک جدول را به دیتابیس مذکور اضافه کرده و یک کوئری اضافه کردن رکوردی را به آن جدول بنویسیم. برای شروع، یک پروژه‌ی خالی net core. را ایجاد می‌کنیم. برای مثال یک پروژه‌ی api را ایجاد میکنیم:
dotnet new webapi -o dockerapi
سپس دو پکیج زیر را به آن اضافه میکنیم:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
در این مثال می‌خواهیم جدول Students را ایجاد و یک رکورد را در آن ثبت نماییم. پس یک کلاس را به نام Students ساخته و property‌های زیر را در آن مینویسیم:
public class Students
{
       public int Id { get; set; }
       public string Name { get; set; }
       public string Phone { get; set; }
}
مرحله‌ی بعد، ساخت context میباشد. برای اینکه وارد جزئیات نشویم، از قابلیت Scaffold استفاده می‌کنیم و context را تولید میکنیم:
 dotnet ef dbcontext scaffold "Server=localhost,1433\\Catalog=tutorial_database;Database=<YOUR_DATABASE_NAME>;User=SA;Password=<StrongPasswordYouSet>;" Microsoft.EntityFrameworkCore.SqlServer
پس از اجرای دستور بالا، context ساخته میشود. حال دورن context، یک DbSet را از students ایجاد میکنیم. بعد نوبت به تنظیم کردن connection string می‌رسد. داخل کانتکست، connection string را تنظیم کنید. همچنین connection string داخل appsettings.json  را نیز تنظیم کنید:
"ConnectionStrings": {
  "TestingDatabase": "Server=localhost:1433\\Database=<YourDatabaseName>;User=SA;Password=<StrongPasswordYouSet>;"
}
بعد از تنظیم کردن connection string، باید migration را بزنیم تا تغییرات context را مشاهده کنیم. با دستور زیر migration خود را اضافه کنید:
 dotnet ef migrations add <NAME_OF_MIGRATION>


همانطور که مشاهده می‌کنید، migrations اضافه شده و موجودیت هم اضافه شده‌است. حال باید بر روی migrations خود آپدیت بزنیم:
ef database update

همانطور که در شکل بالا نیز مشاهده می‌کنید، دیتابیس ما ایجاد شده‌است. حال به docker برمی‌گردیم و با دستور زیر، لیست تمام دیتابیس‌های موجود را نمایش میدهیم:

همانطور که مشاهده می‌کنید، دیتابیس برای ما ایجاد شده. با دستور زیر می‌توان جدول دیتابیس را مشاهده کرد:
 SELECT TABLE_NAME FROM dockerdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'
مطالب
آشنایی با SQL Server Common Table Expressions - CTE
مقدمه

تکنولوژی CTE از نسخه SQL Server 2005 رسمیت یافته است و شامل یک result set موقتی[1] است که دارای نام مشخص بوده و می­توان از آن در دستورات SELECT, INSERT, UPDATE, DELETEاستفاده کرد. همچنین از CTE می‌توان در دستور CREATE VIEW و دستور SELECT مربوط به آن استفاده کرد. در نسخه SQL Server 2008 نیز امکان استفاده از CTE در دستور MERGE فراهم شده است.
در SQL Serverاز دو نوع CTE بازگشتی[2] و غیر بازگشتی[3] پشتیبانی می­شود. در این مقاله سعی شده است نحوه تعریف و استفاده از هر دو نوع آن آموزش داده شود.

انواع روش­های ایجاد جداول موقت
برای استفاده از جداول موقتی در سرور اسکیوال، سه راه زیر وجود دارد.

روش اول: استفاده از دستوری مانند زیر است که سبب ایجاد جدول موقتی در بانک سیستمی tempdb می­شود. زمانی­که شما ارتباط خود را با سرور SQL قطع می­کنید به صورت اتوماتیک جداول موقت شما از بانک tempdb حذف می­شوند. این روش در برنامه نویسی پیشنهاد نمی­شود و فقط در کارهای موقتی و آزمایشی مناسب است.
 SELECT * INTO #temptable FROM [Northwind].[dbo].[Products]
UPDATE #temptable SET [UnitPrice] = [UnitPrice] + 10

روش دوم: استفاده از متغیر نوع Table است، که نمونه آن در مثال زیر دیده می­شود. زمانیکه از محدوده[4] جاری کد[5] خودتان خارج شوید آن متغیر نیز از حافظه پاک میشود. از این روش، عموما در کدهای Stored Procedureها و UserDefined Functionها استفاده می­شود.
DECLARE @tempTable TABLE
(
   [ProductID] [int] NOT NULL,
   [ProductName] [nvarchar](40) NOT NULL,
   [UnitPrice] [money] NULL
)
 
INSERT INTO @tempTable
SELECT
   [ProductID],
   [ProductName],
   [UnitPrice]
FROM [Northwind].[dbo].[Products]
 
UPDATE @temptable SET [UnitPrice] = [UnitPrice] + 10

روش سوم: استفاده از CTE است که مزیت­هایی نسبت به دو روش قبلی دارد و در بخش بعدی به نحوه تعریف و استفاده از آن خواهیم پرداخت.


کار با CTE
ساده ­ترین شکل تعریف یک CTE به صورت زیر است:
WITH yourName [(Column1, Column2, ...)]
AS
(
   your query
)
با کلمه WITH شروع شده و یک نام اختیاری به آن داده می­شود. سپس فهرست فیلدهای جدول موقت را درون زوج پرانتز، مشخص می­کنید. تعریف این فیلدها اختیاری است و اگر حذف شود، فیلدهای جدول موقت، مانند فیلدهای کوئری مربوطه خواهد بود.
your query شامل دستوری است که سبب تولید یک result set می­شود. قواعد تعریف این کوئری مشابه قواعد تعریف کوئری است که در دستور CREATE VIEW کاربرد دارد.


شکل کلی دستور

همانطور که از این تصویر مشخص است می­توان چندین بلوک از این ساختار را به دنبال هم تعریف نمود که با کاما از هم جدا می­شوند. در واقع یکی از کاربردهای CTE ایجاد قطعات کوچکی است که امکان استفاده مجدد را به شما داده و می­تواند سبب خواناتر شدن کدهای پیچیده شود.
یکی دیگر از کاربردهای CTE آنجایی است که شما نمی‌خواهید یک شی Viewی عمومی تعریف کنید و در عین حال می‌خواهید از مزایای Viewها بهرمند شوید.
و همچنین از کاربردهای دیگر CTE تعریف جدول موقت و استفاده از آن جدول به صورت همزمان در یک دستور است.

بعد از آنکه CTE یا CTEهای خودتان را تعریف کردید آنگاه می­توانید مانند جداول معمولی از آنها استفاده کنید. استفاده از این جداول توسط دستوری خواهد بود که دقیقا بعد از تعریف CTE نوشته می­شود.


ایجاد یک CTE غیر بازگشتی[6]

مثال اول، یک CTE غیر بازگشتی ساده را نشان میدهد.
WITH temp
AS
(
    SELECT
         [ProductName],
         [UnitPrice]
    FROM [Northwind].[dbo].[Products]
)
SELECT * FROM temp
ORDER BY [UnitPrice] DESC

مثال دوم نمونه‌­ای دیگر از یک CTE غیر بازگشتی است.
WITH orderSales (OrderID, Total)
AS
(
   SELECT
      [OrderID],
     SUM([UnitPrice]*[Quantity]) AS Total
   FROM [Northwind].[dbo].[Order Details]
   GROUP BY [OrderID]
)
SELECT
   O.[ShipCountry],
   SUM(OS.[Total]) AS TotalSales
FROM [Northwind].[dbo].[Orders] AS O INNER JOIN [orderSales] AS OS
ON O.[OrderID] = OS.[OrderID]
GROUP BY O.[ShipCountry]
ORDER BY TotalSales DESC
هدف این کوئری، محاسبه کل میزان فروش کالاها، به ازای هر کشور می­باشد. ابتدا از جدول Order Details مجموع فروش هر سفارش محاسبه شده و نتیجه آن در یک CTE به نام orderSales قرار می­گیرد و از JOIN این جدول موقت با جدول Orders محاسبه نهایی انجام شده و نتیجه­‌ای مانند این تصویر حاصل می‌شود.

نتیجه خروجی

 مثال سوم استفاده از دو CTE را به صورت همزمان نشان می­دهد:
WITH customerList
AS
(
   SELECT
      [CustomerID],
      [ContactName]
   FROM [Northwind].[dbo].[Customers]
   WHERE [Country] ='UK'
)
,orderList
AS
(
   SELECT
      [CustomerID],
      [OrderDate]
   FROM [Northwind].[dbo].[Orders]
   WHERE YEAR([OrderDate])< 2000
)
SELECT
   cl.[ContactName],
   YEAR(ol.[OrderDate]) AS SalesYear
FROM customerList AS cl JOIN orderList AS ol
ON cl.[CustomerID] = ol.[CustomerID]

مثال چهارم استفاده مجدد از یک CTE را نشان می­دهد. فرض کنید جدولی به نام digits داریم که فقط یک فیلد digit دارد و دارای 10 رکورد با مقادیر 0 تا 9 است. مانند تصویر زیر

نتیجه خروجی

حال می­خواهیم از طریق CROSS JOIN اعداد 1 تا 100 را با استفاده از مقادیر این جدول تولید کنیم. کد زیر آنرا نشان می­دهد:
WITH digitList
AS
(
   SELECT [digit] from [digits]
)
SELECT
   a.[digit] * 10 + b.[digit] + 1 AS [Digit]
FROM [digitList] AS a CROSSJOIN [digitList] AS b
در این کد یک CTE تعریف شده و دو بار مورد استفاده قرار گرفته است. مثلا اگر بخواهید اعداد 1 تا 1000 را تولید کنید می­توانید سه بار از آن استفاده کنید. حاصل این دستور result setی مانند زیر است.

نتیجه

نتیجه

حتی می­توان از یک CTE در کوئری CTE بعدی مانند کد زیر استفاده کرد.
WITH CTE_1 AS
(
   ....
),
CTE_2 AS
(
   SELECT  ... FROM CTE_1 JOIN ...
)
SELECT   *
FROM FOO
LEFTJOIN  CTE_1
LEFTJOIN  CTE_2


ایجاد یک CTE بازگشتی[7]

از CTE بازگشتی برای پیمایش جداولی استفاده می­شود که رکوردهای آن دارای رابطه سلسله مراتبی یا درختی است. نمونه این جداول، جدول کارمندان است که مدیر هر کارمند نیز مشخص شده است یا جدولی که ساختار سازمانی را نشان می­دهد یا جدولی که موضوعات درختی را در خود ذخیره کرده است. یکی از مزایای استفاده از CTE بازگشتی، سرعت کار آن در مقایسه با روش­های پردازشی دیگر است.

ساختار کلی یک دستور CTE بازگشتی به صورت زیر است.
WITH cteName AS
(
   query1
   UNION ALL
   query2
)
در بدنه CTE حداقل دو عضو[8] (کوئری) وجود دارد که بایستی با یکی از عبارت­های زیر به هم متصل شوند.
UNION
UNION ALL
INTERSECT
EXCEPT

query1 شامل دستوری است که اولین سری از رکوردهای result set نهایی را تولید می­کند. اصطلاحا به این کوئری anchor memberمی­گویند.
بعد از دستور query1، حتما بایستی از UNION ALL و امثال آنها استفاده شود.
سپس query2 ذکر می­شود. اصطلاحا به این کوئری recursive member گفته می­شود. این کوئری شامل دستوری است که سطوح بعدی درخت را تولید خواهد کرد. این کوئری دارای شرایط زیر است.
  •   حتما بایستی به CTE که همان cteName است اشاره کرده و در جایی از آن استفاده شده باشد. به عبارت دیگر از رکوردهای موجود در جدول موقت استفاده کند تا بتواند رکوردهای بعدی را تشخیص دهد.
  • حتما بایستی مطمئن شوید که شرایط کافی برای پایان حلقه پیمایش رکوردها را داشته باشد در غیر این صورت سبب تولید حلقه بی پایان[9] خواهد شد.

بدنه CTE می­تواند حاوی چندین anchor member و چندین recursive member باشد ولی فقط recursive memberها هستند که به CTE اشاره می­کنند.

برای آنکه نکات فوق روشن شود به مثال­های زیر توجه کنید.
فرض کنید جدولی از کارمندان و مدیران آنها داریم که به صورت زیر تعریف و مقداردهی اولیه شده است.
IFOBJECT_ID('Employees','U')ISNOTNULL
DROPTABLE dbo.Employees
GO 

CREATETABLE dbo.Employees
(
  EmployeeID intNOTNULLPRIMARYKEY,
  FirstName varchar(50)NOTNULL,
  LastName varchar(50)NOTNULL,
  ManagerID intNULL
)

GO


INSERTINTO Employees VALUES (101,'Alireza','Nematollahi',NULL)
INSERTINTO Employees VALUES (102,'Ahmad','Mofarrahzadeh', 101)
INSERTINTO Employees VALUES (103,'Mohammad','BozorgGhommi', 102)
INSERTINTO Employees VALUES (104,'Masoud','Narimani', 103)
INSERTINTO Employees VALUES (105,'Mohsen','Hashemi', 103)
INSERTINTO Employees VALUES (106,'Aref','Partovi', 102)
INSERTINTO Employees VALUES (107,'Hosain','Mahmoudi', 106)
INSERTINTO Employees VALUES (108,'Naser','Pourali', 106)
INSERTINTO Employees VALUES (109,'Reza','Bagheri', 102)
INSERTINTO Employees VALUES (110,'Abbas','Najafian', 102)

مثال اول: می­خواهیم فهرست کارمندان را به همراه نام مدیر آنها و شماره سطح درخت نمایش دهیم. کوئری زیر نمونه‌ای از یک کوئری بر اساس CTE بازگشتی می­باشد.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE ManagerID ISNULL
UNIONALL
SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1
FROM Employees e INNERJOINcteReports r
ON e.ManagerID = r.EmpID
)
SELECT
FirstName +' '+ LastName AS FullName,
EmpLevel,
(SELECT FirstName +' '+ LastName FROM Employees
WHERE EmployeeID = cteReports.MgrID)AS Manager
FROMcteReports
ORDERBY EmpLevel, MgrID
کوئری اول در بدنه CTE رکورد مدیری را می­دهد که ریشه درخت بوده و بالاسری ندارد و شماره سطح این رکورد را 1 در نظر می­گیرد.
کوئری دوم در بدنه CTE از یک JOIN بین Employees و cteReports استفاده کرده و کارمندان زیر دست هر کارمند قبلی (فرزندان) را بدست آورده و مقدار شماره سطح آنرا به صورت Level+1 تنظیم می­کند.
در نهایت با استفاده از CTE و یک subquery جهت بدست آوردن نام مدیر هر کارمند، نتیجه نهایی تولید می­شود.

مثال دوم: می­خواهیم شناسه یک کارمند را بدهیم و نام او و نام مدیران وی را به عنوان جواب در خروجی بگیریم.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE EmployeeID = 110
UNIONALL
SELECTe.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1
FROM Employees e INNERJOINcteReports r
ON e.EmployeeID = r.MgrID
)
SELECT
FirstName +' '+ LastName AS FullName,
EmpLevel
FROMcteReports
ORDERBY EmpLevel
اگر دقت کنید اولین تفاوت در خط اول مشاهده می­شود. در اینجا مشخص می­کند که اولین سری از رکوردها چگونه انتخاب شود. مثلا کارمندی را می­خواهیم که شناسه آن 110 باشد.
دومین تفاوت اصلی این کوئری با مثال قبلی، در قسمت دوم دیده می­شود. شما می‌خواهید مدیر (پدر) کارمندی که در آخرین پردازش در جدول موقت قرار گرفته است را استخراج کنید.

    


[1] a temporary named result set
[2] recursive
[3] nonrecursive
[4] Scope
[5]مثلا محدوده کدهای یک روال یا یک تابع
[6] nonrecursive
[7] recursive
[8] member
[9] Infinite loop