مفهوم Context Transition
transition عملی است که توسط توابع CALCULATE و CALCULATETABLE در هنگام تعریف کانتکست فیلتر جدید انجام میشود؛ کانتکستی که عبارت داخل این توابع در آن ارزیابی میشود. تعریف رسمی context transition ساده به نظر میرسد، اما پیچیدگیهایی در پشت آن پنهان است. در توضیحات زیر، مثالهایی بر پایه تابع CALCULATE ارائه شدهاند، اما تمام مفاهیم برای CALCULATETABLE نیز صدق میکنند.
تابع CALCULATE تمامی کانتکستهای ردیفی (Row Context) موجود را به کانتکست فیلتر معادل تبدیل میکند، پیش از آنکه آرگومانهای فیلتر خودش را به کانتکست فیلتر اصلی اضافه کند. این تبدیل، در هنگام ایجاد کانتکست فیلتر جدید برای اجرای عبارت درون تابع رخ میدهد.
context transition با مثالها بسیار بهتر قابل درک است تا از طریق تئوری. به عنوان مثال، فرض کنید یک مدل داده دارید که فقط شامل جدولهای Product و Sales است، با یک رابطه بر اساس کلید ProductKey:

شما میتوانید یک ستون محاسبهشده (Calculated Column) در جدول Product ایجاد کنید:

از آنجایی که این ستون محاسبهشده است، در یک row context محاسبه میشود. با این حال، چون تابع SUM مجموع قیمتهای واحد (Unit Price) قابل مشاهده در filter context فعلی را محاسبه میکند، نتیجه برای هر محصول برابر است با مجموع قیمتهای واحد از کل جدول.در یک ستون محاسبهشده، هیچ filter contextی وجود ندارد، تنها یک row context وجود دارد.

بسیاری از مبتدیان DAX به اشتباه فکر میکنند که نتیجهی این ستون محاسبهشده باید فقط مقدار Unit Price در سطر جاری باشد. اما بهمحض اینکه مفاهیم row context و filter context را بهدرستی یاد میگیرند، این رفتار برایشان کاملاً طبیعی میشود.
حالا اگر ستونی ایجاد کنید که شامل کدی با یک فراخوانی به CALCULATE باشد، چه اتفاقی میافتد؟

اینبار، CALCULATE دور تابع SUM قرار گرفته، بنابراین SUM در یک filter context متفاوت اجرا میشود.از آنجایی که CALCULATE هیچ پارامتر فیلتری ندارد، تنها اثر آن context transition است.
در این حالت، row context که فقط شامل یک سطر است، به یک filter context تبدیل میشود که همان سطر را شامل میشود.
در این مرحله، تابع SUM با یک filter context که تنها شامل یک سطر است مواجه میشود و در نتیجه، مقدار Unit Price فقط برای همان سطر را برمیگرداند:

میتوانی همین رفتار را هنگام استفاده از SUM با یک ستون از جدول Sales نیز مشاهده کنی، مانند دو ستون محاسبهشدهی زیر:

اولین ستون محاسبهشده، مجموع کل Sales[Quantity] را برمیگرداند، چون هیچ filter context فعالی وجود ندارد.در حالی که ستونی که از CALCULATE استفاده میکند، مجموع Sales[Quantity] فقط برای محصول فعلی را برمیگرداند؛ زیرا filter context شامل محصول فعلی، بهصورت خودکار به جدول Sales منتقل میشود، بهدلیل وجود رابطهای که بین این دو جدول برقرار است.

تفاوت بزرگی بین filter contextای که توسط context transition ایجاد میشود و row context وجود دارد.
در واقع، filter contextی که توسط CALCULATE تولید میشود، بر روی تمام ستونهای جدول فیلتر اعمال میکند تا یک سطر خاص را شناسایی کند؛ نه براساس شماره سطر.
بنابراین، اگر از context transition در جدولی استفاده کنی که سطرهای تکراری دارد، filter context تولیدشده توسط CALCULATE تمام آن سطرهای تکراری را در بر میگیرد. در نتیجه، فقط در صورتی میتوان با اطمینان از context transition برای فیلتر کردن یک سطر خاص استفاده کرد که جدول فاقد سطرهای تکراری باشد. طبیعتاً این شرط در حالتی برقرار است که جدول دارای کلید اصلی (Primary Key) باشد که نبودِ تکرار را تضمین کند. همچنین مهم است که بدانی context transition قبل از اعمال فیلترهای اضافی داخل CALCULATE اتفاق میافتد.
بنابراین، فیلترهایی که در CALCULATE مشخص شدهاند ممکن است فیلترهای حاصل از context transition را بازنویسی یا نادیده بگیرند. در مثال بعدی، جدول Product تنها شامل سه ستون است: ProductKey، Unit Price و Color. اگر ستونی محاسبهشده با کد زیر تعریف شود:

تابع ALL هرگونه فیلتر روی ستون ProductKey را حذف میکند و چون این کار بعد از context transition انجام میشود، فیلتر اعمالشده بر ProductKey توسط context transition نیز حذف خواهد شد. در نتیجه، ستون محاسبهشده مجموع Unit Price را برای تمام محصولاتی که رنگ (Color) و قیمت واحد (Unit Price) یکسانی دارند محاسبه میکند.

شایان ذکر است که هرگاه در یک عبارت DAX از یک Measure استفاده میکنی، این Measure بهصورت خودکار توسط CALCULATE احاطه میشود. این CALCULATE خودکار که توسط DAX اضافه میشود، عملیات context transition را انجام میدهد و اگر به این نکته توجه نداشته باشی، ممکن است باعث بروز خطا در کد شود.
برای مثال، اگر بخواهی مدلی را کوئری بگیری که محصولاتی را برگرداند که بیش از ۱٪ از مجموع فروش را فروختهاند، ممکن است وسوسه شوی که کوئری را به شکل زیر بنویسی:

به این نکته دقت کن که در خط ۸، یک CALCULATE تابع TotalSales را درون ADDCOLUMNS احاطه کرده است. این موضوع نشان میدهد که در آن نقطه context transition مورد نیاز است تا فقط فروش مربوط به محصولی که در حال پیمایش (iteration) است، محاسبه شود. اما در واقعیت، TotalSales یک Measure است، بنابراین نیازی به CALCULATE نیست، چون DAX بهصورت خودکار آن را اضافه میکند.
بدتر اینکه، این کوئری درست کار نمیکند، چون موتور DAX بهصورت خودکار یک CALCULATE را دور دومین فراخوانی TotalSales در شرط FILTER (خط ۱۰) هم اضافه میکند؛ جایی که داریم SalesOfProduct را با ۱٪ از TotalSales مقایسه میکنیم.
از آنجایی که CALCULATE در آنجا هم هست، کوئری در واقع فروش یک محصول را با ۱٪ از فروش همان محصول مقایسه میکند! در نتیجه، نتیجه نهایی اشتباه خواهد بود.
برای اینکه نتیجهی درست را تولید کنی، میتوانی کوئری را به شکل زیر بنویسی:

همانطور که میبینی، CALCULATE غیرضروری (چون بهصورت ضمنی اضافه میشود) را از دور اولین فراخوانی TotalSales (خط ۸) حذف کردیم. سپس، در شرط استفادهشده توسط FILTER (خط ۱۰)، از SUM(Sales[Quantity]) استفاده کردیم تا از context transition ضمنی جلوگیری کنیم.
کوئری قبلی میتواند به شکل زیباتری نوشته شود با استفاده از متغیرها:

متغیرها در DAX به شما کمک میکنند تا یک عبارت را در contextی متفاوت از آنچه که میخواهید نتیجهاش را استفاده کنید، ارزیابی کنید. برای مشاهده مثالهای بیشتر، میتوانید به متغیرها در DAX مراجعه کنید.
دیدگاهتان را بنویسید