نظرات مطالب
EF Code First #15
نمونه‌ای از پیاده سازی اتصال به اوراکل 11g در Entity Framework 6 بوسیله پروایدر تجاری شرکت devart:

  1. ابتدا نسخه آزمایشی dotconnect for oracle 8.2 professional را از این آدرس دریافت و آن را نصب می‌کنیم.
  2. نصب آخرین نسخه Entity Framework از طریق پاور شل نیوگت.
  3. افزودن Devart.Data و Devart.Data.Oracle و Devart.Data.Oracle.Entity به Solution.
  4. حذف تگ defaultConnectionFactory در entityFramework.
  5. افزودن تگ زیر در قسمت providers همانند کد زیر:
<provider invariantName="Devart.Data.Oracle" type="Devart.Data.Oracle.Entity.OracleEntityProviderServices, Devart.Data.Oracle.Entity, Version=8.2.100.6, Culture=neutral, PublicKeyToken=09af7300eec23701" />
تکمیلی: اصول کلی دسترسی به اوراکل به شرح بالاست، ولی نکته مهم مقداردهی به خصیصه Version=X.X.X.X با توجه به نسخه اسمبلی Devart.Data.Oracle.Entity می‌باشد.
dotConnect for Oracle 
dotConnect for Oracle 
نظرات مطالب
درج یک باره چندین رکورد بصورت همزمان هنگام استفاده از ORMها
سلام
من خطای زیر رو دارم. برای ویرایش چند رکورد به صورت یکجا از Entity Framework Extended Library استفاده کردم. اما مشکلی دارم این است من یک جدول در پایگاه دارم که می‌خوام به صورت یکجا ویرایش کنم به این صورت که ابتدا شماره فیلد هایی که را قرار است تغییر دهم(فیلد ID) داخل یک لیست ریختم و سپس از جدول فقط اون فیلدها رو ویرایش کنم
چند روش برای join جدول با این لیست امتحان کردم یا join خطا می‌دهد اگر join نیز مشکلی نداشت مقدار برگشتی از نوع enumerable هست و زمانی که از update کلاس Entity Framework Extended Libraryاستفاده میکنم
خطا می‌دهد که لیستی که قرار است update شود باید از نوع dbquery باشد  
 
Unable to create a constant value of type 'extendeexample.MyClass'. Only primitive types or enumeration types are supported in this context.

سورس پروژه به همراه پایگاه داده
project.rar
مطالب
روش ایجاد پروژه‌ها‌ی کتابخانه‌ای کامپوننت‌های Blazor
جهت به اشتراک گذاشتن کامپوننت‌های سفارشی Blazor در پروژه‌های مختلف، امکان بسته بندی آن‌ها به صورت کتابخانه‌های کامپوننت‌ها نیز پیش‌بینی شده‌است که می‌توانند به همراه فایل‌های CSS ،JS و تصاویر هم باشند.


روش ایجاد یک پروژه‌ی کتابخانه‌ای، از کامپوننت‌های Blazor

اگر از ویژوال استودیو استفاده می‌کنید، نوع «Razor Class Library»، پروژه‌های مخصوص کتابخانه‌های کامپوننت‌های Blazor را آغاز می‌کند و اگر می‌خواهید از CLI استفاده کنید، باید از دستور «dotnet new razorclasslib» استفاده کرد؛ که قابلیت تبدیل به بسته‌های نیوگت را با دستور dotnet pack داشته و به این ترتیب می‌توان آن‌ها را به اشتراک گذاشت و یا حتی می‌توان ارجاعی از این پروژه را در سایر پروژه‌های شخصی، مورد استفاده قرار داد.
ساختار پیش‌فرض فایل csproj اینگونه پروژه‌ها به صورت زیر است:
<Project Sdk="Microsoft.NET.Sdk.Razor">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
    <SupportedPlatform Include="browser" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.6" />
  </ItemGroup>
</Project>

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

پروژه‌ی پیش‌فرضی که در اینجا ایجاد می‌شود، به همراه یک پوشه‌ی wwwroot نیز هست. این پوشه محلی است که باید فایل‌های ثابت اشتراکی پروژه را مانند فایل‌های CSS و JS مورد استفاده، قرار داد.
این نوع پروژه‌ها از ویژگی Isolated CSS و یا Isolated JS ارائه شد‌ه‌ی در Blazor 5x نیز پشتیبانی می‌کنند.


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

این فایل‌های ثابت به همراه بسته‌ی نهایی پروژه، به صورت خودکار توزیع می‌شوند (و نیازی به ارائه‌ی مجزای آن‌ها نیست) و برای استفاده‌ی از آن‌ها در پروژه‌های دیگر، باید از روش مسیردهی زیر استفاده کرد:
/_content/PackageId/MyImage.png
- content_ مسیر آغاز کننده‌ی دسترسی به منابع یک کتابخانه‌ی کامپوننت‌های Blazor است.
- PackageId عموما همان نام پروژه‌ی مورد استفاده‌است (نام فایل csproj مانند MyBlazorComponentLibrary). هرچند می‌توان آن‌را به صورت مجزایی در فایل csproj نیز مقدار دهی کرد.
- در این مثال MyImage.png، نام منبعی است که قرار است از آن استفاده کنیم و پیشتر در پوشه‌ی wwwroot کتابخانه، کپی شده‌است و یا حتی می‌توان زیر پوشه‌هایی را نیز در اینجا ایجاد و از آن‌ها استفاده کرد؛ مانند:
/_content/MyBlazorComponentLibrary/scripts/HelloWorld.js
همچنین باید دقت داشت که مداخل فایل‌های اسکریپتی کتابخانه را مانند:
<script src="_content/MyBlazorComponentLibrary/exampleJsInterop.js"></script>
در برنامه‌های Blazor WASM باید به فایل wwwroot/index.html و در برنامه‌های Blazor Server به فایل Pages/_Host.cshtml افزود و این مداخل باید پیش از یکی از فایل‌های framework/blazor.server.js_ و یا framework/blazor.webassembly.js_ تعریف شوند.


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

برای استفاده‌ی از کتابخانه‌ی نهایی تولید شده یا می‌توان ارجاعی را به فایل csproj آن، به پروژه‌ی خود افزود:
<ItemGroup>
   <ProjectReference Include="..\MyBlazorComponentLibrary\MyBlazorComponentLibrary.csproj" />
</ItemGroup>
و یا می‌توان بسته‌ی نیوگت آن‌را (در صورت وجود) نصب کرد.

پس از آن، جهت سهولت استفاده‌ی از این کامپوننت‌های اشتراکی، بهتر است فضای نام آن‌ها را به فایل Imports.razor_ پروژه‌ی خود افزود؛ تا نیازی به تعریف آن‌ها در تمام کامپوننت‌های مورد استفاده نباشد.
نظرات مطالب
اعمال توابع تجمعی بر روی چند ستون در Entity framework
چگونه توسط EF Core، چندین کوئری را یکجا به بانک اطلاعاتی ارسال کنیم؟

روشی را که در این مطلب مشاهده کردید، در موارد مشابه دیگری هم قابل استفاده‌است. برای مثال فرض کنید اطلاعات یک مشتری را قرار است به صورت زیر ذخیره کنیم:
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public CustomerType Type { get; set; }
}

public enum CustomerType
{
    Individual,
    Institution,
}

حالت عادی کوئری گرفتن از اطلاعات جدول آن که به همراه صفحه بندی، نمایش تعداد رکوردها و یک کوئری دلخواه دیگر باشد، به صورت زیر است:
void ManyQueriesManyCalls()
{
    using var scope = serviceProvider.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<CustomerContext>();

    var baseQuery = context.Customers.Select(customer => new
                                                         {
                                                             customer.Name,
                                                             customer.Type,
                                                             customer.Id,
                                                         });
    var total = baseQuery.Count();
    var types = baseQuery.GroupBy(x => x.Type)
                         .Select(x => x.Key).ToList();
    var pageSize = 10;
    var pageIndex = 0;
    var results = baseQuery
                  .OrderBy(x => x.Id)
                  .Skip(pageSize * pageIndex)
                  .Take(pageSize)
                  .ToList();
    Console.WriteLine($"Total:{total}, First Type: {types.First()}, First Item: {results.First().Name}");
}
که سبب می‌شود سه کوئری و سه بار رفت و برگشت را به بانک اطلاعاتی داشته باشیم:
      SELECT COUNT(*)
      FROM [Customers] AS [c]

      SELECT [c].[Type]
      FROM [Customers] AS [c]
      GROUP BY [c].[Type]
  
      SELECT [c].[Name], [c].[Type], [c].[Id]
      FROM [Customers] AS [c]
      ORDER BY [c].[Id]
      OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

اگر بخواهیم این سه کوئری را یکبار به سمت بانک اطلاعاتی ارسال کنیم، می‌توان از همان ترفند گروه بندی مطرح شده‌ی در این مثال برای ترکیب کوئری‌ها استفاده کرد:
void ManyQueriesOnCall()
{
    using var scope = serviceProvider.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<CustomerContext>();
    var baseQuery = context.Customers.Select(customer => new
                                                         {
                                                             customer.Name,
                                                             customer.Type,
                                                             customer.Id,
                                                         });
    var pageSize = 10;
    var pageIndex = 0;
    var allTogether = baseQuery
                      .GroupBy(x => 1)
                      .Select(bq => new
                                    {
                                        Total = baseQuery.Count(),
                                        Types = baseQuery.GroupBy(x => x.Type)
                                                         .Select(x => x.Key)
                                                         .ToList(),
                                        Results = baseQuery
                                                  .OrderBy(x => x.Id)
                                                  .Skip(pageSize * pageIndex)
                                                  .Take(pageSize)
                                                  .ToList(),
                                    })
                      .FirstOrDefault();

    Console.WriteLine($"Total:{allTogether.Total}, First Type: {allTogether.Types.First()}, First Item: {allTogether.Results.First().Name}");
}
که اینبار فقط یک کوئری outer apply دار را تولید می‌کند و فقط یکبار، رفت و برگشت به بانک اطلاعاتی را شاهد خواهیم بود:
      SELECT [t0].[Key], [t1].[Type], [t2].[Name], [t2].[Type], [t2].[Id]
      FROM (
          SELECT TOP(1) [t].[Key]
          FROM (
              SELECT 1 AS [Key]
              FROM [Customers] AS [c]
          ) AS [t]
          GROUP BY [t].[Key]
      ) AS [t0]
      OUTER APPLY (
          SELECT [c0].[Type]
          FROM [Customers] AS [c0]
          GROUP BY [c0].[Type]
      ) AS [t1]
      OUTER APPLY (
          SELECT [c1].[Name], [c1].[Type], [c1].[Id]
          FROM [Customers] AS [c1]
          ORDER BY [c1].[Id]
          OFFSET @__p_1 ROWS FETCH NEXT @__pageSize_2 ROWS ONLY
      ) AS [t2]
      ORDER BY [t0].[Key], [t1].[Type], [t2].[Id]

کدهای این مثال را از اینجا می‌توانید دریافت کنید: EF7ManyQueriesOneCall.zip
مطالب
Asp.Net Identity #2
پیشتر در اینجا در مورد تاریخچه‌ی سیستم Identity مطالبی را عنوان کردیم. در این مقاله می‌خواهیم نحوه‌ی برپایی سیستم Identity را بحث کنیم.
ASP.NET Identity مانند ASP.NET Membership به اسکیمای SQL Server وابسته نیست؛ اما Relational Storage همچنان واحد ذخیره سازی پیش فرض و آسانی می‌باشد. بدین جهت که تقریبا بین همه‌ی توسعه دهندگان جا افتاده است. ما در این نوشتار از LocalDB جهت ذخیره سازی جداول استفاده می‌کنیم. ذکر این نکته ضروری است که سیستم Identity از Entity Framework Code First جهت ساخت اسکیما استفاده می‌کند.
پیش از هر چیز، ابتدا یک پروژه‌ی وب را ایجاد کنید؛ مانند شکل زیر:

در مرحله‌ی بعد سه پکیج زیر را باید نصب کنیم :

- Microsoft.AspNet.Identity.EntityFramework

-   Microsoft.AspNet.Identity.OWIN

-  Microsoft.Owin.Host.SystemWeb

بعد از اینکه پکیج‌های بالا را نصب کردیم، باید فایل Web.config را بروز کنیم. اولین مورد، تعریف یک Connection String می‌باشد:
<connectionStrings>
 <add name="IdentityDb" 
      providerName="System.Data.SqlClient"
      connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/>
 </connectionStrings>
بعد از آن، تعریف یک کلید در قسمت AppSettings تحت عنوان Owin:AppStartup است:
<appSettings>
 <add key="webpages:Version" value="3.0.0.0" />
 <add key="webpages:Enabled" value="false" />
 <add key="ClientValidationEnabled" value="true" />
 <add key="UnobtrusiveJavaScriptEnabled" value="true" />
 <add key="owin:AppStartup" value="Users.IdentityConfig" />
 </appSettings>
Owin مدل شروع برنامه (Application Startup Model) خودش را تعریف می‌کند که از کلاس کلی برنامه (منظور Global.asax) جداست. AppSetting تعریف شده با نام owin:Startup شناخته می‌شود و مشخص کننده کلاسی است که Owin وهله سازی خواهد کرد. وقتی برنامه شروع به کار می‌کند، تنظیمات خودش را از این کلاس دریافت خواهد کرد (در این نوشتار کلاس IdentityConfig می‌باشد).

ساخت کلاس User
مرحله‌ی بعد ساخت کلاس User می‌باشد. این کلاس بیانگر یک کاربر می‌باشد. کلاس User باید از کلاس IdentityUser ارث بری کند که این کلاس در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد. کلاس IdentityUser فراهم کننده‌ی یک کاربر عمومی و ابتدایی است. اگر بخواهیم اطلاعات اضافی مربوط به کاربر را ذخیره کنیم باید آنها در کلاسی که از کلاس IdentityUser ارث بری می‌کند قرار دهیم. کلاس ما در اینجا AppUser نام دارد.
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models 
{
   public class AppUser : IdentityUser 
  {
      // پروپرتی‌های اضافی در اینجا
  }
}

ساخت کلاس Database Context برنامه
مرحله‌ی بعد ساخت کلاس DbContext برنامه می‌باشد که بر روی کلاس AppUser عمل میکند. کلاس Context برنامه که ما در اینجا آن را AppIdentityDbContext تعریف کرده‌ایم، از کلاس <IdentityDbContext<T ارث بری می‌کند که T همان کلاس User می‌باشد (در مثال ما AppUser) .
using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
namespace Users.Infrastructure {
 public class AppIdentityDbContext : IdentityDbContext<AppUser> 
{
   public AppIdentityDbContext() 
              : base("IdentityDb") { }

  static AppIdentityDbContext() 
 {
    Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
  }
 public static AppIdentityDbContext Create() {
 return new AppIdentityDbContext();
 }
 }
public class IdentityDbInit
 : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 protected override void Seed(AppIdentityDbContext context) {
 PerformInitialSetup(context);
 base.Seed(context);
 }
 public void PerformInitialSetup(AppIdentityDbContext context) {
 // initial configuration will go here
 }
 }
}

ساخت کلاس User Manager
یکی از مهمترین کلاسهای Identity کلاس User Manager (مدیر کاربر) می‌باشد که نمونه‌هایی از کلاس User را مدیریت می‌کند. کلاسی را که تعریف می‌کنیم، باید از کلاس <UserManager<T ارث بری کند که T همان کلاس User می‌باشد. کلاس UserManager خاص EF نمی‌باشد و ویژگی‌های عمومی بیشتری برای ساخت و انجام عملیات بر روی داده‌های کاربر را فراهم می‌نماید.

کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، می‌بینید که تمامی متدهای بالا به کلمه‌ی Async ختم می‌شوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیت‌ها را بلوکه نکنند.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;

namespace Users.Infrastructure 
{
 public class AppUserManager : UserManager<AppUser> 
{

 public AppUserManager(IUserStore<AppUser> store)
 : base(store) {
 }
 public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) 
{
 AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
 AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 return manager;
 }
 }
}

زمانی که Identity نیاز به وهله‌ای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق می‌افتد که عملیاتی بر روی داده‌های کاربر انجام گیرد. برای ساخت وهله‌ای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T  توسط  EF میباشد که وظیفه‌ی آن فراهم کننده‌ی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) می‌باشد. برای ساخت <UserStore<T نیاز به وهله‌ای از کلاس AppIdentityDbContext می‌باشد که از طریق Owin به صورت زیر قابل دریافت است:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده می‌شود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شده‌ی در کلاس شروع Owin می‌نماید.


ساخت کلاس شروع Owin

اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامه‌ی شماست و IdentityConfig بیانگر کلاس شروع می‌باشد. (البته شما می‌توانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف می‌کنید یکی باشد)

Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، می‌باشد. به طور پیش فرض این کلاس Start نام دارد و در پوشه‌ی App_Start تعریف می‌شود. این کلاس حاوی یک متد به نام Configuration می‌باشد که بوسیله زیرساخت Owin فراخوانی می‌شود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده می‌شود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
namespace Users 
{
 public class IdentityConfig 
{
 public void Configuration(IAppBuilder app) 
{
 app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
 app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
 app.UseCookieAuthentication(new CookieAuthenticationOptions {
 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 LoginPath = new PathString("/Account/Login"),
 });
 }
 }
}

رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شده‌اند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهله‌ای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین می‌کند که هر درخواست، به یک داده‌ی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity می‌گوید که چگونه از کوکی‌ها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص می‌شود. مهمترین قسمت در اینجا پروپرتی LoginPath می‌باشد و مشخص می‌کند که کلاینت‌های تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده می‌شود.

خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفاده‌ی از این سیستم خواهیم پرداخت.

مطالب
Cookie - قسمت دوم

کوکی در جاوا اسکریپت 

همانطور که در قسمت قبل اشاره کوتاهی شد، مدیریت کوکی‌های در دسترس در وضعیت جاری، در جاوا اسکریپت ازطریق پراپرتی cookie از شی document امکان‌پذیر است. این پراپرتی کاری همانند هدرهای Set-Cookie و Cookie (که در قسمت قبل درباره آن‌ها بحث شد) انجام می‌دهد. این پراپرتی یک مورد کاملا استثنایی و نسبتا عجیب در زبان جاوا اسکریپت است. در نگاه اول ظاهرا document.cookie از نوع رشته است، اما قضیه کاملا فرق می‌کند. برای روشن شدن مطلب به ادامه بحث توجه کنید.

افزودن کوکی
- برای افزودن یا ویرایش یک کوکی باید از ساختاری مانند ساختار هدر Set-Cookie که چیزی شبیه به عبارت زیر است، پیروی کرد:
document.cookie = "name=value; expires=date; domain=theDomain; path=thePath; secure";
 
نکته: با توجه به توضیحاتی که در قسمت قبل ارائه شد، بدیهی است که امکان ثبت یک کوکی با فلگ HttpOnly در جاوا اسکریپت وجود ندارد!
 
اجرای دستوری شبیه با ساختار نشان داده شده در بالا، موجب حذف کوکی‌های قبلی نمی‌شود. از این دستور برای ایجاد یک کوکی و یا ویرایش یک کوکی موجود استفاده می‌شود. کوکی‌های ایجادشده با این روش تفاوتی با کوکی‌های ایجادشده توسط هدر Set-Cookie ندارند و همانند آنها در درخواست‌های بعدی با توجه به خواص تنظیم شده، به سمت سرور ارسال خواهند شد.
همانطور که مشاهده می‌کنید خاصیت‌های کوکی به صورت جفت‌های نام-مقدار درون یک رشته به document.cookie نسبت داده می‌شوند. این خاصیت‌ها توسط یک کاراکتر ; از یکدیگر جدا می‌شوند. شرح ساختار فوق در  زیر آورده شده است:
1. همیشه اولین جفتِ نام-مقدار همانند مثال بالا باید «عنوان و مقدار» کوکی را مشخص سازد. این قسمت تنها عضو اجباری ساختار فوق است.
2. سپس یک سمی‌کالن و یک فاصله
3. تاریخ انقضا (expires) یا حداکثر طول عمر کوکی (max-age)
4. سپس یک سمی‌کالن و یک فاصله
5. دمین و یا مسیر مربوط به کوکی
6. سپس یک سمی‌کالن و یک فاصله
7. سایر خواص چون Secure
نکته: این ساختار عجیب معرفی شده را عینا رعایت کنید. بقیه کار توسط مرورگر انجام خواهد شد.
نکته: قسمت‌های مختلف این ساختار case-sensitive نیست، البته به‌جز نام کوکی که کاملا case-sensitive است.
مثلا برای ثبت یک کوکی با عنوان myCookie و مقدار myValue و دمین d.com و مسیر test و طول عمر 5 روزه باید از دستور زیر استفاده کرد:
document.cookie = 'myCookie=myValue; max-age=432000; domain=d.com; path=/test';
 
خواندن کوکی
- برای خواندن کوکی‌ها تنها کافی است مقدار پراپرتی document.cookie بررسی شود. با اینکه از دستور نشان داده شده در بالا اینگونه برمی آید که پراپرتی document.cookie به رشته معرفی شده مقداردهی شده است، اما به محض خواندن این پراپرتی چیزی شبیه به عبارت زیر برگردانده میشود:
myCookie=myValue 
از بقیه خواص اثری نیست! این رفتار به دلیل حفط امنیت کوکی‌ها در تمام مرورگرها رعایت می‌شود.
- برای ثبت کوکی دیگری در وضعیت جاری کافی است یکبار دیگر دستور بالا را برای کوکی جدید به کار ببریم. مثلا به صورت زیر:
document.cookie = 'mySecondCookie=mySecondValue; path=/'
اینار یک کوکی سشنی بدون دمین و با مقدار / برای مسیر کوکی ثبت می‌شود! در این حالت کوکی قبلی دوباره نویسی و یا حذف نمی‌شود و تنها یک کوکی جدید به لیست کوکیهای مرورگر اضافه می‌شود! این رفتار عجیب از ویژگی‌های جالب document.cookie است.
- اگر مقدار document.cookie در این حالت خوانده شود مقدار زیر برگشت داده می‌شود:
myCookie=myValue; mySecondCookie=mySecondValue
باز هم خبری از سایر خاصیت‌ها نیست. ولی همانطور که می‌بینید کوکی دوم به لیست کوکی‌های مرورگر اضافه شده است.

نکته: عبارت برگشت داده شده از پراپرتی document.cookie همانند مقداری است که در هدر Cookie هر درخواست توسط مرورگر گنجانده می‌شود، یعنی جفت نام-مقدار کوکی‌ها به همراه یک ; و یک فاصله بین مقادیر هر کوکی. بنابراین برای بدست آوردن مقدار یک کوکی یکسری عملیات جهت Parse کردن داده‌های آن نیاز است!

متدها
امروزه کتابخانه‌های متعددی با استفاده از زبان جاوا اسکریپت برای برنامه نویسی سمت کلاینت وجود دارد که بیشتر آنها قابلیت‌هایی برای کار با کوکی‌ها نیز دارند. ازجمله می‌توان به jQuery و YUI اشاره کرد. پلاگین مخصوص کوکی‌ها در jquery در اینجا بحث شده است. برای کسب اطلاعات بیشتر درباره قابلیت‌های کار با کوکی در YUI نیز به اینجا مراجعه کنید. مطالب زیر صرفا برای روشن شدن بحث ارائه می‌شوند. بدیهی است که برای کارهای عملی بهتر است از کتابخانه‌های موجود استفاده شود.
با توجه به اطلاعات بالا از متدهای زیر می‌توان برای خواندن، افزودن و حذف کوکی‌ها استفاده کرد.

نکته: متدهای زیر از ترکیب چندین ریفرنس مختلف بدست آمده است. هرچند برای موارد خاص‌تر می‌توانند بیشتر سفارشی شوند.

افزودن و یا ویرایش کوکی
function setCookie(data, value) {
  if (typeof data === "string") {
    data = { name: data, value: value };
  };
  if (!data.name) throw "Cookie's name can not be null.";

  var cookie = escape(data.name) + "=" + escape(data.value);

  var expDate = null;
  if (data.expDays) {
    expDate = new Date();
    expDate.setDate(expDate.getDate() + data.expDays);
  }
  else if (data.expYear && data.expMonth && data.expDay) {
    expDate = new Date(data.expYear, data.expMonth, data.expDay);
  }
  else if (data.expires) {
    expDate = data.expires;
  }
  else if (data.maxAge) {
    expDate = new Date();
    expDate.setSeconds(expDate.getSeconds() + data.maxAge);
  }
  if (expDate != null) cookie += "; expires=" + expDate.toGMTString();

  if (data.domain)
    cookie += "; domain=" + escape(data.domain);

  if (data.path)
    cookie += "; path=" + escape(data.path);

  if (data.secure)
    cookie += "; secure";

  document.cookie = cookie;
  return document.cookie;
}
در کد فوق برای انکد کردن رشته‌های مورد استفاده از متد escape استفاده شده است. برای آشنایی با این متد به اینجا مراجعه کنید.
هم‌چنین کار کردن با نوع داده تاریخ در جاوا اسکریپت کمی متفاوت است. بنابراین برای آشنایی بیشتر با این نوع داده به اینجا رجوع کنید.
 
نکته: در متد بالا بدلیل عدم پشتیبانی از خاصیت max-age در نسخه‌های قدیمی اینترنت اکسپلورر (نسخه 8 و قبل از آن) تنها از خاصیت expires استفاده شده است.
 
نحوه استفاده از متد بالا به صورت زیر است:
setCookie('cookie1', 'Value1');
setCookie({name:'cookie1', value:'Value1'});
setCookie({name:'cookie2', value:'Value2', expDays:10});
setCookie({name:'cookie3', value:'Value3', expires:new Date()});
setCookie({name:'cookie4', value:'Value4', expYear:2013, expMonth:0, expDay:13});
setCookie({name:'cookie3', value:'Value3', maxAge:365*24*60*60});
setCookie({name:'cookie5', value:'Value5', domain:'d.net', path:'/'});
setCookie({name:'cookie6', value:'Value6', secure:true});
setCookie({name:'cookie7', value:'Value7', expDays:100, domain:'dd.com', path:'/employee', secure:true});
 
حذف کوکی
همانطور که در قسمت قبل هم اشاره شد، برای حذف یک کوکی، کافی است تا تاریخ انقضای آن به تاریخی در گذشته مقداردهی شود. بنابراین برای اینکار می‌توان از متد زیر استفاده کرد:
function delCookie(data) {
  if (typeof data === "string") {
    data = { name: data };
  };
  data.expDays = -1;
  return setCookie(data);
}
در متد فوق از متد setCookie که در بالا معرفی شد، استفاده شده است. نحوه استفاده از این متد هم به صورت زیر است:
delCookie('myCookie');
delCookie({ name: 'myCookie', domain: 'd.com', path: '/test' });
 
خواندن کوکی
برای خواندن مقدار یک کوکی می‌توان از متد زیر استفاده کرد:
function getCookie(name) {
  var cookies = document.cookie.split(";");
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i].split("=");
    if (cookie[0].trim() == escape(name)) {
      return unescape(cookie[1].trim());
    }
  }
  return null;
}
برای آشنایی با متد unescape که در بالا از آن استفاده شده است به اینجا مراجعه کنید. در متد فوق از متد trim زیر استفاده شده است:
String.prototype.trim = function () {
  return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.trimStart = function () {
  return this.replace(/^\s+/, "");
};
String.prototype.trimEnd = function () {
  return this.replace(/\s+$/, "");
};
این متدها از اینجا گرفته شده است.
روش استفاده شده برای خواندن مقادیر کوکی‌ها در متد بالا بسیار ساده و ابتدایی است و صرفا برای آشنایی با نحوه Parse کردن رشته برگشت داده شده توسط document.cookie ارائه شده است. روش‌های مناسب‌تر و مطمئن‌تر با یک جستجوی ساده در دسترس هستند. البته همانطور که قبلا هم اشاره شد، استفاه از کتابخانه‌های موجود راه‌حل بهتری است.
هم‌چنین ازآنجاکه مقدار یک کوکی می‌تواند شامل کاراکتر = نیز باشد، بنابراین قسمت return متد فوق را می‌توان به صورت زیر تغییر داد:
cookie.shift(1);
return unescape(cookie.join('=').trim());
 
نکته: با توجه به مطالب ارائه شده در قسمت قبل  بدست آوردن مقادیر کوکی‌ها کمی پیچیده‌تر از دیگر عملیات‌هاست. ازآنجاکه راه مستقیمی با استفاده از جاوا اسکریپت برای یافتن سایر خواص کوکی وجود ندارد، بنابراین بدست آوردن مقدار دقیق کوکی موردنظر ممکن است غیرممکن باشد! (با توجه به اینکه کوکی‌های متفاوت می‌توانند نام‌های یکسانی داشته باشند).
 
با توجه به نکته بالا، حال اگر با یک نام بخصوص، چندین کوکی ثبت شده باشد (با خواص متفاوت)، یکی از راه‌حل‌ها این است که آرایه‌ای از مقادیر این کوکی‌های همنام برگشت داده شود. بنابراین متد فوق را می‌توان به صورت زیر تکمیل کرد:
function getCookie(name) {
  var foundCookies = [];
  var cookies = document.cookie.split(";");
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i].split("=");
    if (cookie[0].trim() == escape(name) && cookie.length >= 2) {
      cookie.shift(1);
      foundCookies.push(unescape(cookie.join('=').trim()));
    }
  }
  return foundCookies.length > 1 
            ? foundCookies
            : foundCookies.length == 1
                ? foundCookies[0]
                : null;
}

خلاصه‌ای از نحوه استفاده از متدهای بالا در IE8 (برای نمایش اجرای درست در مرورگری قدیمی!) در تصویر زیر  نشان داده شده است:

 
نکته: کار توسعه این متدها را میتوان برای پشتیبانی از SubCookieها نیز ادامه داد، اما به دلیل دورشدن از مبحث اصلی، این موضوع در این مطلب ارائه نمیشود (درباره این نوع از کوکی‌ها در قسمت قبل شرح کوتاهی داده شده است). اگر علاقه‌مند و نیازمند به این نوع کوکی‌ها هستید، کتابخانه YUI پشتیبانی کاملی از آنها ارائه میکند.
 
در قسمت بعدی به نکات کار با کوکی در ASP.NET میپردازیم.

منابع:
مطالب
کوئری نویسی در EF Core - قسمت پنجم - اعمال تجمعی - بخش دوم
کوئری‌های تجمعی این قسمت، کمی پیچیده‌تر هستند و برای حل آن‌ها باید از window functions استفاده کرد و چون این مفهوم توسط EF-Core پشتیبانی نمی‌شود (منظور توسط LINQ to Entities آن است و نه SQL نویسی مستقیم)، در بعضی از موارد مجبور خواهیم شد اطلاعات مورد نیاز گزارش را از بانک اطلاعاتی دریافت کرده و سپس در سمت کلاینت توسط LINQ to Objects شکل دهی کنیم.


مثال 12: محاسبه کنید در سال 2012 و به ازای هر ماه مجزای آن، چه تعداد slots رزرو شده‌اند؛ قسمت دوم.

این مثال را در قسمت قبل (مثال 6 آن) نیز بررسی کردیم. در اینجا می‌خواهیم در گزارش نهایی تولید شده، پس از اتمام ردیف‌های یک ماه به ازای یک امکان خاص، جمع کل آن نیز درج شود و همچنین در پایان تمام ردیف‌ها، جمع کل نهایی ذکر شود؛ چیزی شبیه به تصویر زیر که در آن 910، جمع کل slots ماه 8 است و 9191، جمع کل سال.


روش پیشنهادی حل این مساله استفاده از مفهومی به نام «GROUP BY ROLLUP» است:
SELECT   facid,
         DATEPART(month, [StartTime]) AS month,
         sum(slots) AS slots
FROM     bookings
WHERE    starttime >= '2012-01-01'
         AND starttime < '2013-01-01'
GROUP BY ROLLUP(facid, DATEPART(month, [StartTime]))
ORDER BY facid, month;
یک چنین گروه بندی توسط LINQ to Entities پشتیبانی نمی‌شود. اما خلاصه‌ی این گزارش به این صورت است:
ابتدا جمع slots را گروه بندی شده بر اساس هر ماه سال محاسبه می‌کنیم. این قسمت توسط LINQ to Entities قابل انجام است؛ همان مثال 6 قسمت قبل است.
سپس این اطلاعات که اکنون در سمت کلاینت (یعنی برنامه‌ی ما) در حافظه موجود هستند، نیاز دارند به ازای هر گروه، یک جمع کل (sub total) و به ازای کل سال نیز یک جمع کل (grand total یا total) پیدا کنند.

ROLLUP(facid, month) اطلاعات تجمعی سلسه مراتبی پارامترهای ارسالی به آن را تولید می‌کند. یعنی (facid, month), (facid) و (). پیاده سازی LINQ to Objects این تابع را در اینجا می‌توانید مشاهده کنید: Utils\GroupingExtensions.cs

بنابراین راه حل این مساله به صورت زیر خواهد بود:
var date1 = new DateTime(2012, 01, 01);
var date2 = new DateTime(2013, 01, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => new { booking.FacId, booking.StartTime.Month })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Month,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                        .ThenBy(result => result.Month)
                                    .ToList()
                            //This is new
                            .GroupByWithRollup(
                                item => item.FacId,
                                item => item.Month,
                                (primaryGrouping, secondaryGrouping) => new
                                {
                                    FacId = primaryGrouping.Key,
                                    Month = secondaryGrouping.Key,
                                    TotalSlots = secondaryGrouping.Sum(item => item.TotalSlots)
                                },
                                item => new
                                {
                                    FacId = item.Key,
                                    Month = -1,
                                    TotalSlots = item.SubTotal(subItem => subItem.TotalSlots)
                                },
                                items => new
                                {
                                    FacId = -1,
                                    Month = -1,
                                    TotalSlots = items.GrandTotal(subItem => subItem.TotalSlots)
                                });
تا جائیکه متد ToList فراخوانی شده، همان مثال 6 قسمت قبل است. پس از آن چون این لیست را درون حافظه داریم، اکنون متد الحاقی جدید GroupByWithRollup را به آن اعمال می‌کنیم تا اطلاعات گروه بندی اصلی، اطلاعات subTotal (همان ردیف اضافه‌ی تولید شده‌ی حاصل جمع هر گروه) و total (یا همان ردیف جمع کل گزارش) را تولید کند.
در اینجا سلول‌هایی که اطلاعاتی ندارند، با منهای یک مشخص شده‌اند؛ در گزارش اصلی با null مقدار دهی شده بودند.


مثال 13: به ازای نام هر کدام از امکانات موجود، جمع کل تعداد ساعات رزرو شده‌ی آن‌ها را محاسبه کنید.

هر slot تنها نیم ساعت است و گزارش نهایی باید به همراه ستون‌های facid, name, Total Hours باشد؛ مرتب شده بر اساس facid.
var items = context.Bookings
                                    .GroupBy(booking => new { booking.FacId, booking.Facility.Name })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Name,
                                        TotalHours = group.Sum(booking => booking.Slots) / 2M
                                    })
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در اینجا روش گروه بندی بر اساس FacId که از جدول Bookings تامین می‌شود و Facility.Name را که از جدول دیگری به نامFacilities  تامین می‌شود، ملاحظه می‌کنید که به صورت خودکار جوین لازم آن در کوئری نهایی تولید خواهد شد:



مثال 14: گزارشی را از اولین رزرو کاربران پس از September 1st 2012، تهیه کنید.

این گزارش باید به همراه ستون‌های surname, firstname, memid, starttime باشد؛ مرتب شده بر اساس memid.
var date1 = new DateTime(2012, 09, 01);
var items = context.Bookings
                                    .Where(booking => booking.StartTime >= date1)
                                    .GroupBy(booking => new
                                    {
                                        booking.Member.Surname,
                                        booking.Member.FirstName,
                                        booking.Member.MemId
                                    })
                                    .Select(group => new
                                    {
                                        group.Key.Surname,
                                        group.Key.FirstName,
                                        group.Key.MemId,
                                        StartTime = group.Min(booking => booking.StartTime)
                                    })
                                    .OrderBy(result => result.MemId)
                                    .ToList();
هدف از این مثال محاسبه‌ی حداقل StartTime‌ها به ازای اطلاعات گروه بندی شده‌ی بر اساس هر کاربر است که روش آن‌را با استفاده از متد group.Min مشاهده می‌کنید.



مثال 15: گزارشی را از کاربران تهیه کنید که هر ردیف آن، به همراه تعداد کل کاربران باشد.

این گزارش باید به همراه ستون‌های count, firstname, surname باشد؛ مرتب شده بر اساس joindate.
var members = context.Members
                        .OrderBy(member => member.JoinDate)
                        .Select(member => new
                        {
                            Count = context.Members.Count(),
                            member.FirstName,
                            member.Surname
                        })
                        .ToList();
EF-Core این گزارش به همراه یک sub-query را تبدیل به دو کوئری می‌کند؛ ابتدا مقدار ثابت تعداد اعضاء را محاسبه می‌کند و سپس این تعداد ثابت را در کوئری دوم بکار می‌گیرد:
SELECT COUNT(*)
FROM   [Members] AS [m];

SELECT   [m].[FirstName],
         [m].[Surname],
         @__Count_0 AS [Count]
FROM     [Members] AS [m]
ORDER BY [m].[JoinDate];


مثال 16:  گزارشی را از کاربران تهیه کنید که به همراه ستون شماره ردیف آن‌ها نیز باشد.

باید بخاطر داشت که ID کاربران پشت سرهم نیست و همچنین این گزارش باید به همراه ستون‌های row_number, firstname, surname باشد؛ مرتب شده بر اساس joindate.

هدف اصلی از این مثال، کار با مفهوم window function‌ها و تابع row_number است:
SELECT   row_number() OVER (ORDER BY joindate) AS row_number,
         firstname,
         surname
FROM     members
ORDER BY joindate;
اما چون چنین قابلیتی با LINQ to Entities قابل پیاده سازی نیست، در اینجا نیز ابتدا ردیف‌های گزارش را تولید می‌کنیم و سپس شماره ردیف را در سمت کلاینت (در سمت برنامه و توسط LINQ to Objects)، اضافه خواهیم کرد:
var members = context.Members
                        .OrderBy(member => member.JoinDate)
                        .Select(member => new
                        {
                            member.FirstName,
                            member.Surname
                        })
                        .ToList()
                        /*
                            SELECT [m].[FirstName], [m].[Surname]
                                FROM [Members] AS [m]
                                ORDER BY [m].[JoinDate]
                        */
                        // Now using LINQ to Objects
                        .Select((member, index) => new
                        {
                            RowNumber = index + 1,
                            member.FirstName,
                            member.Surname
                        })
                        .ToList();
تا قسمت ToList، یک کوئری LINQ to Entities استاندارد مشاهده می‌شود. پس از آن چون این اطلاعات درون حافظه هستند، می‌توان با استفاده از LINQ to Objects و قابلیت index ذاتی موجود در متد Select، شماره ردیف‌ها را که همان index + 1 هستند، تولید کرد.


مثال 17: کدامیک از امکانات موجود، بیشترین slots رزرو شده را دارد؟ قسمت دوم.

این مورد همان مثال 11 قسمت قبل است که پاسخ آن‌را یافتیم (و از تکرار مجدد آن صرفنظر می‌کنیم) و هدف اصلی آن رسیدن به کوئری window function دار زیر است که تنها از طریق اجرای یک raw sql در EF-Core قابل اجرا است:
SELECT facid,
       total
FROM   (SELECT   facid,
                 sum(slots) AS total,
                 rank() OVER (ORDER BY sum(slots) DESC) AS rank
        FROM     bookings
        GROUP BY facid) AS ranked
WHERE  rank = 1;


مثال 18: به کاربران بر اساس تعداد ساعات رزرو آن‌ها، امتیاز دهی (رتبه بندی) کنید.

این گزارش باید به همراه ستون‌های firstname, surname, hours, rank باشد؛ مرتب شده بر اساس rank, surname.

هدف اصلی از این مثال، رسیدن به کوئری rank دار زیر است:
SELECT   mems.firstname,
         mems.surname,
         ((sum(bks.slots) + 10) / 20) * 10 AS hours,
         rank() OVER (ORDER BY ((sum(bks.slots) + 10) / 20) * 10 DESC) AS rank
FROM     bookings AS bks
         INNER JOIN
         members AS mems
         ON bks.memid = mems.memid
GROUP BY mems.firstname,
         mems.surname
ORDER BY rank, mems.surname, mems.firstname;
هرچند نمی‌توان از window functions به همراه LINQ to Entities استفاده کرد، اما می‌توان نتیجه‌ای را که خواسته (تولید rank بر اساس تعداد ساعات استفاده شده) به صورت زیر نیز تولید کرد که شامل استفاده‌ی از LINQ to Objects هم نمی‌شود؛ یعنی برای تولید Rank، الزاما نیازی به Window Functions نیست:
var itemsQuery = context.Bookings
                                    .GroupBy(booking => new
                                    {
                                        booking.Member.FirstName,
                                        booking.Member.Surname
                                    })
                                    .Select(group => new
                                    {
                                        group.Key.FirstName,
                                        group.Key.Surname,
                                        Hours = (group.Sum(booking => booking.Slots) + 10) / 20 * 10
                                    })
                                    .OrderByDescending(result => result.Hours)
                                        .ThenBy(result => result.Surname)
                                        .ThenBy(result => result.FirstName);
                var rankedItems = itemsQuery.Select(thisItem => new
                {
                    thisItem.FirstName,
                    thisItem.Surname,
                    thisItem.Hours,
                    Rank = itemsQuery.Count(mainItem => mainItem.Hours > thisItem.Hours) + 1
                })
                .ToList();
در ابتدا یک کوئری متداول گروه بندی شده‌ی بر اساس کاربران را مشاهده می‌کنید که به ازای هر کاربر، جمع تعداد ساعات رزور شده‌ی او محاسبه شده‌است. البته itemsQuery یک IQueryable مرتب سازی شده‌است؛ یعنی چون هنوز ToList بر روی آن فراخوانی نشده، بر روی بانک اطلاعاتی اجرا نشده‌است و فقط یک LINQ Expression است. سپس این LINQ Expression را به صورت زنجیروار در یک کوئری دیگر استفاده کرده‌ایم که در آن sub-query دارای itemsQuery.Count، مقدار rank را تشکیل داده‌است. این ساب کوئری به این معنا است: چه تعداد ساعت حاصل از کوئری گروه بندی و مرتب شده، از مقدار ساعت ردیف جاری بیشتر است + 1 که رتبه‌ی هر ردیف را نسبت به ردیف‌های دیگر محاسبه می‌کند.

با این خروجی SQL نهایی:



مثال 19: سه امکانی را لیست کنید که بالاترین میزان فروش را داشته‌اند.

این گزارش باید به همراه ستون‌های name, rank باشد؛ مرتب شده بر اساس rank.

روش محاسبه‌ی این گزارش با مثال قبلی یکی است (البته اینبار رتبه بندی بر اساس TotalRevenue است) و فقط در انتهای آن یک Where(result => result.Rank <= 3) را بیشتر دارد:
var facilitiesQuery =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderBy(result => result.TotalRevenue);

                var rankedFacilities = facilitiesQuery.Select(thisItem => new
                {
                    thisItem.Name,
                    thisItem.TotalRevenue,
                    Rank = facilitiesQuery.Count(mainItem => mainItem.TotalRevenue > thisItem.TotalRevenue) + 1
                })
                .Where(result => result.Rank <= 3)
                .OrderBy(result => result.Rank)
                .ToList();
ابتدا به نحو متداولی گروه بندی بر اساس نام صورت گرفته و محاسبه‌ی میزان فروش هر گروه انجام شده‌است. سپس در کوئری زنجیروار دوم، ستون Rank، به نتیجه‌ی حاصل اضافه شده‌است و اگر این Rank کمتر از 3 باشد، پاسخ مساله‌است.



مثال 20: امکانات موجود را بر اساس میزان فروشی که دارند به گروه‌هایی با تعداد مساوی high, average, low تقسیم بندی کنید.

این گزارش باید به همراه ستون‌های name, revenue باشد؛ مرتب شده بر اساس revenue, name.

هدف اصلی از این گزارش کار با تابع ntile است که اطلاعات را بر اساس پارامتر ارسالی به آن تاجای ممکن به گروه‌های مساوی تقسیم می‌کند:
SELECT   name,
         CASE WHEN class = 1 THEN 'high' WHEN class = 2 THEN 'average' ELSE 'low' END AS revenue
FROM     (SELECT   facs.name AS name,
                   ntile(3) OVER (ORDER BY sum(CASE WHEN memid = 0 THEN slots * facs.guestcost ELSE slots * membercost END) DESC) AS class
          FROM     bookings AS bks
                   INNER JOIN
                   facilities AS facs
                   ON bks.facid = facs.facid
          GROUP BY facs.name) AS subq
ORDER BY class, name;
Ntile نیز در LINQ to Entities معادلی ندارد. بنابراین ابتدا رزروهای انجام شده را بر اساس نوع امکانات رزرو شده، گروه بندی کرده و میزان فروش هر گروه را پیدا می‌کنیم:
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderByDescending(result => result.TotalRevenue)
                                .ToList();
که یک چنین SQL ای را تولید می‌کند:
SELECT   [f].[Name],
         SUM(CASE WHEN [b].[MemId] = 0 THEN CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[GuestCost] ELSE CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[MemberCost] END) AS [TotalRevenue]
FROM     [Bookings] AS [b]
         INNER JOIN
         [Facilities] AS [f]
         ON [b].[FacId] = [f].[FacId]
GROUP BY [f].[Name]
ORDER BY SUM(CASE WHEN [b].[MemId] = 0 THEN CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[GuestCost] ELSE CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[MemberCost] END) DESC;
سپس با استفاده از LINQ to Objects، تابع ntile را شبیه سازی می‌کنیم:
var n = 3;
var tiledFacilities = facilities.Select((item, index) =>
                                        new
                                        {
                                            Item = item,
                                            Index = (index / n) + 1
                                        })
                                        .GroupBy(x => x.Index)
                                        .Select(g =>
                                            g.Select(z =>
                                                new
                                                {
                                                    z.Item.Name,
                                                    z.Item.TotalRevenue,
                                                    Tile = g.Key,
                                                    GroupName = g.Key == 1 ? "High" : (g.Key == 2 ? "Average" : "Low")
                                                })
                                                .OrderBy(x => x.GroupName)
                                                    .ThenBy(x => x.Name)
                                        )
                                        .ToList();

var flatTiledFacilities = tiledFacilities.SelectMany(group => group)
                                        .Select(tile => new { tile.Name, Revenue = tile.GroupName })
                                        .ToList();
هدف از این گزارش این است که در نتیجه‌ی مرتب سازی شده‌ی بر اساس TotalRevenue، به سه تای اول، برچسب High را بدهیم، به سه تای دوم برچسب average و به مابقی برچسب low. به همین جهت ردیف‌های حاصل را بر اساس ستون جدیدی به نام Index که بیانگر شماره ردیف گروه‌های سه تایی است، گروه بندی می‌کنیم و به هر گروه برچسبی را انتساب می‌دهیم. حاصل آن، گروه‌های تو در تویی است که با SelectMany، نسبت به مسطح سازی آن‌ها اقدام شده‌است.


مثال 21: چندماه طول می‌کشد تا هر کدام از امکانات موجود بر اساس فروشی که دارند، هزینه‌ی مالکیت ابتدایی خود را کسب کنند.

این گزارش باید به همراه ستون‌های name, months باشد؛ مرتب شده بر اساس name.
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    booking.Facility.InitialOutlay,
                                    booking.Facility.MonthlyMaintenance,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => new
                                {
                                    b.Name,
                                    b.InitialOutlay,
                                    b.MonthlyMaintenance
                                })
                                .Select(group => new
                                {
                                    group.Key.Name,
                                    RepayTime =
                                        group.Key.InitialOutlay /
                                                ((group.Sum(b => b.Revenue) / 3) - group.Key.MonthlyMaintenance)
                                })
                                .OrderBy(result => result.Name)
                                .ToList();
ابتدا رزروهای انجام شده را بر اساس نوع امکانات رزرو شده گروه بندی کرده و میزان فروش هر گروه را پیدا می‌کنیم. سپس بر روی این حاصل، محاسبات خاص RepayTime را انجام داده و نتیجه را بازگشت می‌دهیم:



مثال 22: گزارش میانگین متحرک فروش کل هر کدام از روزهای August 2012 را برای یک بازه‌ی 15 روزه‌ی قبل، محاسبه کنید.

این گزارش باید به همراه ستون‌های date, revenue باشد؛ مرتب شده بر اساس date. در این گزارش روزهای ماه 8 میلادی ردیف شده و به ازای هر ردیف، میانگین فروش 15 روز قبل از آن تاریخ، نمایش داده می‌شود. به همین جهت به آن میانگین متحرک نیز می‌گویند.

هدف اصلی از این گزارش، استفاده از توابع avg(revdata.rev) over است. اما چون نمی‌توان از آن‌ها در LINQ to Entities استفاده کرد، از روش دیگری که شامل جوین یک جدول با خودش است، استفاده می‌کنیم:
var startDate = new DateTime(2012, 08, 1);
var endDate = new DateTime(2012, 08, 31);
var period = 14;

var dailyRevenueQuery =
                        context.Bookings
                                .Select(booking =>
                                new
                                {
                                    StartDate = booking.StartTime.Date, // How to group by date (or TruncateTime) in EF-Core
                                    Revenue = booking.MemId == 0 ?
                                                           booking.Slots * booking.Facility.GuestCost
                                                           : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.StartDate)
                                .Select(group =>
                                new
                                {
                                    Date = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                });
ابتدا میزان کل فروش‌ها را بر حسب تاریخ هر روز ماه 8 میلادی، محاسبه می‌کنیم. برای این گروه بندی خاص نیاز خواهیم داشت تا از زمان یک تاریخ صرفنظر کنیم (چون StartTime به همراه تاریخ و ساعت است). برای اینکار فقط کافی است بجای  booking.StartTime از booking.StartTime.Date استفاده شود تا نتیجه‌ی حاصل به CONVERT(date, [b0].[StartTime]) ترجمه شده و قسمت زمان تاریخ از کوئری نهایی حذف شود.
اکنون که میزان کل فروش روزها را داریم، می‌خواهیم میانگین فروش 15 روز قبل شروع شده‌ی از از ابتدای ماه 8، تا انتهای آن‌را محاسبه کنیم. برای اینکار نیاز است کوئری فوق را یکبار دیگر با خودش جوین کنیم تا از یک سر آن تاریخ هر روز و از طرف دیگر، میانگین 15 روز قبل، تولید شود:
var movingAvgs =
                        dailyRevenueQuery
                                .Select(dr1 =>
                                new
                                {
                                    dr1.Date,
                                    MovingAvg = dailyRevenueQuery
                                        .Where(dr2 => dr2.Date <= dr1.Date && dr2.Date >= dr1.Date.AddDays(-period))
                                        .Average(dr2 => dr2.TotalRevenue)
                                })
                                .Where(result => result.Date >= startDate && result.Date <= endDate)
                                .OrderBy(result => result.Date)
                                .ToList();



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.