export * from './services/alertify.service';
بعد از فراخوانی سرویس در یک کامپوننت دیگر این نتیجه حاصل میشود :
و آدرس مطلق بجای @app با .. شروع میشود.
export * from './services/alertify.service';
بعد از فراخوانی سرویس در یک کامپوننت دیگر این نتیجه حاصل میشود :
و آدرس مطلق بجای @app با .. شروع میشود.
وب اسمبلی چیست؟
<BlazorMode> ... </BlazorMode> <WebAppDeploymentType> ... </WebAppDeploymentType>
@using BlazorWasm.Client.Pages.Authentication
@using System.Security.Claims @inject NavigationManager NavigationManager if(AuthState is not null) { <div class="alert alert-danger"> <p>You [@AuthState.User.Identity.Name] do not have access to the requested page</p> <div> Your roles: <ul> @foreach (var claim in AuthState.User.Claims.Where(c => c.Type == ClaimTypes.Role)) { <li>@claim.Value</li> } </ul> </div> </div> } @code { [CascadingParameter] private Task<AuthenticationState> AuthenticationState {set; get;} AuthenticationState AuthState; protected override async Task OnInitializedAsync() { AuthState = await AuthenticationState; if (!IsAuthenticated(AuthState)) { var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); if (string.IsNullOrEmpty(returnUrl)) { NavigationManager.NavigateTo("login"); } else { NavigationManager.NavigateTo($"login?returnUrl={Uri.EscapeDataString(returnUrl)}"); } } } private bool IsAuthenticated(AuthenticationState authState) => authState?.User?.Identity is not null && authState.User.Identity.IsAuthenticated; }
در ادامه برای استفاده از این کامپوننت، به کامپوننت ریشهای BlazorWasm.Client\App.razor مراجعه کرده و قسمت NotAuthorized آنرا به صورت زیر، با معرفی کامپوننت RedirectToLogin، جایگزین میکنیم:
<NotAuthorized> <RedirectToLogin></RedirectToLogin> </NotAuthorized>
using System; using BlazorServer.Common; using Microsoft.AspNetCore.Authorization; namespace BlazorWasm.Client.Utils { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RolesAttribute : AuthorizeAttribute { public RolesAttribute(params string[] roles) { Roles = $"{ConstantRoles.Admin},{string.Join(",", roles)}"; } } }
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]")] [Authorize(Roles = "Editor")] public class MyProtectedEditorsApiController : Controller { [HttpGet] public IActionResult Get() { return Ok(new ProtectedEditorsApiDTO { Id = 1, Title = "Hello from My Protected Editors Controller!", Username = this.User.Identity.Name }); } } }
namespace BlazorWasm.Client.Services { public class ClientHttpInterceptorService : DelegatingHandler { private readonly NavigationManager _navigationManager; private readonly ILocalStorageService _localStorage; private readonly IJSRuntime _jsRuntime; public ClientHttpInterceptorService( NavigationManager navigationManager, ILocalStorageService localStorage, IJSRuntime JsRuntime) { _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); _jsRuntime = JsRuntime ?? throw new ArgumentNullException(nameof(JsRuntime)); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // How to add a JWT to all of the requests var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken); if (token is not null) { request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token); } var response = await base.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { await _jsRuntime.ToastrError($"Failed to call `{request.RequestUri}`. StatusCode: {response.StatusCode}."); switch (response.StatusCode) { case HttpStatusCode.NotFound: _navigationManager.NavigateTo("/404"); break; case HttpStatusCode.Forbidden: // 403 case HttpStatusCode.Unauthorized: // 401 _navigationManager.NavigateTo("/unauthorized"); break; default: _navigationManager.NavigateTo("/500"); break; } } return response; } } }
@page "/unauthorized" <div class="alert alert-danger mt-3"> <p>You don't have access to the requested resource.</p> </div>
dotnet add package Microsoft.Extensions.Http
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); //... // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); /*builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")) });*/ // dotnet add package Microsoft.Extensions.Http builder.Services.AddHttpClient( name: "ServerAPI", configureClient: client => { client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")); client.DefaultRequestHeaders.Add("User-Agent", "BlazorWasm.Client 1.0"); } ) .AddHttpMessageHandler<ClientHttpInterceptorService>(); builder.Services.AddScoped<ClientHttpInterceptorService>(); builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI")); //... } } }
@* For being rendered on the server *@ <Counter @rendermode="@InteractiveServer" /> @* For running in WebAssembly *@ <Counter @rendermode="@InteractiveWebAssembly" />
@rendermode InteractiveServer @rendermode InteractiveWebAssembly
<Counter @rendermode="@InteractiveAuto" />
@attribute [StreamRendering(prerender: true)]
<head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <title>newTitle ...</title> <base href="/"> <meta name="description" content="Angular meta service"> <meta name="author" content="DNT"> <meta name="keywords" content="Angular, Meta Service"> <meta name="twitter:card" content="summary"> <meta name="twitter:site" content="@my_site"> <meta name="twitter:title" content="Front-end Web Development"> <meta name="twitter:description" content="Learn frontend web development..."> <meta name="twitter:image" content="https://site/images/image.png"> <meta name="author" content="Other Author"> </head>
import { Component, OnInit } from "@angular/core"; import { Meta, Title } from "@angular/platform-browser"; @Component({ selector: "app-seo-tests", templateUrl: "./seo-tests.component.html", styleUrls: ["./seo-tests.component.css"] }) export class SeoTestsComponent implements OnInit { constructor(private metaService: Meta, private titleService: Title) { }
// addTag & addTags this.metaService.addTag({ name: "description", content: "How to optimize your Angular App for search engine and other crawlers." }); this.metaService.addTag({ name: "author", content: "DNT" }); this.metaService.addTag({ name: "keywords", content: "Angular, Meta Service" }); // Or this.metaService.addTags([ { name: "description", content: "How to optimize your Angular App for search engine and other crawlers." }, { name: "author", content: "DNT" }, { name: "keywords", content: "Angular, Meta Service" } ], false); // --> forceCreation = false this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" }); this.metaService.addTag({ name: "twitter:site", content: "@my_site" }); this.metaService.addTag({ name: "twitter:title", content: "Front-end Web Development" }); this.metaService.addTag({ name: "twitter:description", content: "Learn frontend web development..." }); this.metaService.addTag({ name: "twitter:image", content: "https://site/images/image.png" }); // Or this.metaService.addTags([ { name: "twitter:card", content: "summary_large_image" }, { name: "twitter:site", content: "@my_site" }, ], false); // --> forceCreation = false
// getTag & getTags const viewport = this.metaService.getTag("name=viewport"); if (viewport) { console.log(viewport.content); // width=device-width, initial-scale=1 } const author = this.metaService.getTag("name=author"); if (author) { console.log(author.content); // DNT } this.metaService.addTag({ name: "author", content: "DNT" }); this.metaService.addTag({ name: "author", content: "Other Author" }, true); const authors = this.metaService.getTags("name=author"); console.log(authors[0]); // <meta name="author" content="DNT"> console.log(authors[1]); // <meta name="author" content="Other Author">
// updateTag this.metaService.addTag({ name: "twitter:card", content: "summary_large_image" }); this.metaService.updateTag({ name: "twitter:card", content: "summary" }, `name='twitter:card'`); this.metaService.updateTag({ name: "description", content: "Angular meta service" });
// removeTag & removeTagElement this.metaService.removeTag("charset"); // Or const chartsetTag = this.metaService.getTag("charset"); if (chartsetTag) { this.metaService.removeTagElement(chartsetTag); }
// Setting the browser page Title in an Angular app const currentTitle = this.titleService.getTitle(); console.log(currentTitle); this.titleService.setTitle("newTitle ...");
const routes: Routes = [ { path: "seo", component: SeoTestsComponent, data: { title: "Page Title", metaTags: { description: "Page Description or some content here", keywords: "some, keywords, here, separated, by, a comma" } } } ];
import { Injectable } from "@angular/core"; import { Title, Meta } from "@angular/platform-browser"; import { Router, NavigationEnd, ActivatedRouteSnapshot } from "@angular/router"; @Injectable() export class SeoService { constructor(private titleService: Title, private metaService: Meta, private router: Router) { } enableSeo() { this.router.events .filter(event => event instanceof NavigationEnd) .distinctUntilChanged() .subscribe(() => { this.addMetaData(this.router.routerState.snapshot.root); }); } private addMetaData(root: ActivatedRouteSnapshot): void { if (root.children && root.children.length) { this.addMetaData(root.children[0]); } else if (root.data) { this.setTitle(root.data); this.setMetaTags(root.data); } } private setMetaTags(routeData: { [name: string]: any; }) { const routeDataMetaTagsKey = "metaTags"; const metaTags = routeData[routeDataMetaTagsKey]; if (!metaTags) { return; } for (const tag in metaTags) { if (metaTags.hasOwnProperty(tag)) { const newTag = { name: tag, content: metaTags[tag] }; console.log("new tag", newTag); this.metaService.addTag(newTag); } } } private setTitle(routeData: { [name: string]: any; }) { const routeDataTitleKey = "title"; const title = routeData[routeDataTitleKey]; if (title) { console.log("new title", title); this.titleService.setTitle(title); } } }
this.router.events .filter(event => event instanceof NavigationEnd) .distinctUntilChanged() .subscribe(() => { this.addMetaData(this.router.routerState.snapshot.root); });
import { SeoService } from "./seo-service"; @NgModule({ providers: [ SeoService ] }) export class CoreModule {}
import { SeoService } from "./core/seo-service"; export class AppComponent { constructor(private seoService: SeoService) { this.seoService.enableSeo(); } }
@using Microsoft.Extensions.Hosting @inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment env @if (isDevelopment) { @ChildContent } @code { [Parameter] public RenderFragment ChildContent { get; set; } bool isDevelopment = false; protected override void OnInitialized() { isDevelopment = env.IsDevelopment(); } }
<DevOnly> <p>Show some debugging info here!</p> </DevOnly>
class LoginForm extends Component { state = { account: { username: "", password: "" }, errors: { username: "Username is required" } };
validate = () => { return { username: "Username is required." }; }; handleSubmit = e => { e.preventDefault(); const errors = this.validate(); console.log("Validation errors", errors); this.setState({ errors }); if (errors) { return; } // call the server console.log("Submitted!"); };
validate = () => { const { account } = this.state; const errors = {}; if (account.username.trim() === "") { errors.username = "Username is required."; } if (account.password.trim() === "") { errors.password = "Password is required."; } return Object.keys(errors).length === 0 ? null : errors; };
import React from "react"; const Input = ({ name, label, value, error, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
render() { const { account, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} error={errors.username} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} error={errors.password} /> <button className="btn btn-primary">Login</button> </form> ); }
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null at LoginForm.render (loginForm.jsx:55)
this.setState({ errors: errors || {} });
handleChange = ({ currentTarget: input }) => { const errors = { ...this.state.errors }; //cloning an object const errorMessage = this.validateProperty(input); if (errorMessage) { errors[input.name] = errorMessage; } else { delete errors[input.name]; } const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account, errors }); };
validateProperty = ({ name, value }) => { if (name === "username") { if (value.trim() === "") { return "Username is required."; } // ... } if (name === "password") { if (value.trim() === "") { return "Password is required."; } // ... } };
const Joi = require('@hapi/joi'); const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/), email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }); schema.validate({ username: 'abc', birth_year: 1994 });
> npm install @hapi/joi --save > npm i --save-dev @types/hapi__joi
import Joi from "@hapi/joi";
schema = { username: Joi.string() .required() .label("Username"), password: Joi.string() .required() .label("Password") };
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult);
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult); if (!validationResult.error) { return null; } const errors = {}; for (let item of validationResult.error.details) { errors[item.path[0]] = item.message; } return errors; };
validateProperty = ({ name, value }) => { const userInputObject = { [name]: value }; const propertySchema = Joi.object({ [name]: this.schema[name] }); const { error } = propertySchema.validate(userInputObject, { abortEarly: true }); return error ? error.details[0].message : null; };
<button disabled={this.validate()} className="btn btn-primary"> Login </button>
// ... builder.Services.AddRazorComponents().AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
// ... builder.Services.AddRazorComponents(); // ... app.MapRazorComponents<App>();
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router>
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
@page "/StatusCode/{responseCode}" <h3>StatusCode @ResponseCode</h3> @code { [Parameter] public string? ResponseCode { get; set; } }
if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); }
404.razor
@page "/StatusCode/404" <PageTitle>Not found</PageTitle> <h1>Not found</h1> <p role="alert">Sorry, there's nothing at this address.</p>
500.razor
@page "/StatusCode/500" <PageTitle>Unexpected error</PageTitle> <h1>Unexpected error</h1> <p role="alert">There was an unexpected error.</p>
401.razor
@page "/StatusCode/401" <PageTitle>Not Authorized</PageTitle> <h1>Not Authorized</h1> <p role="alert">Sorry, you are not authorized to access this page.</p>
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
dotnet new blazor --interactivity Server --all-interactive
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
مطابق با ویکی پدیا، سطوح
دسترسی مشخص میکند که کدام کاربران یا سیستم پردازش اجازه دسترسی به اشیاء را
دارند(Authentication)، همچنین چه عملیاتهایی بر روی اشیاء
مجازند که اجرا شوند(Authorization).
در مورد جوملا، ما دو جنبه جدا برای سطوح دسترسی
داریم:
1.
کدام کاربران به چه بخشهایی میتوانند دسترسی
داشته باشند؟ برای مثال، انتخاب یک منو برای کدام کاربر فعال خواهد بود؟
2.
چه عملیات (یا اقداماتی) کاربر میتواند بر روی اشیاء
داشته باشد؟ برای مثال، آیا کاربر میتواند یک مطلب را ارسال یا ویرایش کند؟
ماهیتهای موجود در سیستم :
·
کاربران
کاربر
میتواند به گروههای مختلفی اختصاص یابد.
·
گروهها کاربری
شامل
مجوزهایی به صورت پیش فرض میباشند که این مجوزها را از سطوح بالایی نیز به ارث میبرند.
·
سطوح دسترسی
شامل
یک یا چند گروه کاربری میباشد و سطوح دسترسی به محتواهای سایت نسبت داده میشود
یعنی اگر یک مطلب دارای سطح دسترسی عمومی باشد آنگاه تمامی گروههای کاربری
که در عمومی وجود دارند میتوانند مطلب را مشاهده کنند.
·
عملیات و مجوزها
به
صورت پیش فرض یک سری عملیات در سیستم تعریف شده است شامل ویرایش ، حذف و غیره که
برای هر گروه کاربری (تعدادی گروه کاربری به صورت پیش فرض در سیستم تعریف شده است)
به صورت پیش فرض مجوزهایی در نظر گرفته شده است که این مجوزها قابلیت ارث بری از
والد گروه به فرزند رانیز دارا میباشد پس با این حساب همیشه در جوملا والد از سطح
دسترسی پایینتری نسبت به فرزند برخوردار میباشد.
اما
باید گفت در جوملا به ازای هر
کامپوننت نیز میتوان این مجوزها را به ازای گروههای مختلف تغییر داد در این جا
هم هر کامپوننت دارای مجوزهای پیش فرضی میباشد که در هنگام نصب کامپوننت برای
آن در نظر گرفته میشود.
جداول این سیستم :
: users جدول کاربران
usergroups : جدول گروههای کاری یا همان نقشهای کاربری
user_usergroup_map : جدول واسط بین کاربران و گروههای کاری به منظور ایجاد
رابطهی چند به چند (n:n)
assets : این جدول که از جوملا 1.6 به بعد به دیتابیس جوملا افزوده
شده است مهمترین جدول در این سیستم میباشد . که در آن به ازای هر جز که سطح
دسترسی باید برای آن لحاظ گردد یک سطر در نظر گرفته میشود که این سطر باتوجه به
افزایش اجزا سیستم تغییر و به صورت داینامیک به جدول اضافه میگردد ضمنا این سطور
قابلیت ارث بری از یکدیگر را نیز دارا میباشند. در هر بک از سطرها فیلدی به نام rulsوجود دارد محتوای این فیلد از نوع داده ای json میباشد با یک
مثال شاید بهتر بتوان توضیح داد :
محتوای
فیلد کامپوننت بنر :
{"core.admin":{"9":1,"7":1},"core.manage":{"6":1},"core.create":[],"core.delete":[],"core.edit}
در این جا “core.admin” مجوز دسترسی مدیریتی به این کامپوننت میباشد که
گروههای کاری شماره 7 و 9 دارای چنین دسترسی میباشند . ضمنا عملیاتهای "core.create" از سطوح
بالاتر یا همان سطر والد خود ارث بری میکند.
Viewlevel : در این جدول سطوح دسترسی تعریف شده اند مهمترین فیلد این
جدول نیز rulsنام دارد و حاوی id گروه
هایی است که به این سطح دسترسی ، دسترسی دارند.
به طور مثال سطح دسترسی ثبت نام شده حاوی [6,2,8] میباشد یعنی گروههای کاری با idهای مورد نظر میتوانند به محتواهای با سطح دسترسی ثبت نام شده دسترسی داشته باشند.
دیاگرام جداول :
در این قسمت میخواهیم بیشتر در خصوص توابع مرتبط با ساختار سلسله مراتبی صحبت کنیم. برای آشنایی با این توابع و امکانات MDX Query، مقاله را با بررسی چندین Query دنبال خواهیم کرد.
بدست آوردن تمامی برادران یک سطح خاص :
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.children on rows From [Adventure Works]
در کوئری بالا تمامی مشتریانی را که دارای کد پستی مشابه با کد پستی [Crystal Zheng]. میباشند، واکشی کرده ایم.
به عبارت دیگر با اعمال [Crystal Zheng].parent، به کد پستی مشتری دسترسی پیدا کرده ایم (برای درک بیشتر در زیر ساختار سلسله مراتبی موقعیت جغرافیایی مشتریان را ببینید) و سپس با اعمال .children به تمامی مشتریان موجود در آن کد پستی رسیده ایم؛ که عملا همان برادران [Crystal Zheng] می باشند.
نتیجه کوئری بالا در زیر نمایش داده شده است
راه بهتر برای بدست آوردن تمامی برادران یک سطح، استفاده از تابع siblings میباشد.
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].siblings on rows From [Adventure Works]
کوئریهای بالا جواب یکسانی را بر میگردانند. به عبارت دیگر تابع siblings عملا کار دو تابع parent.children را انجام میدهد
برای بدست آوردن برادر ارشد به صورت زیر عمل میکنیم (اولین بچه در ساختار سلسله مراتبی)
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.firstchild on rows From [Adventure Works]
و یا از تابع زیر استفاده میکنیم
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].firstsibling on rows From [Adventure Works]
هر دو کوئری به جواب یکسان خواهند رسید.
و برای بدست آوردن آخرین برادر در ساختار سلسله مراتبی (برادر ته تغاری) از دو روش زیر میتوان استفاده کرد.
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].parent.lastchild on rows From [Adventure Works]
یا
Select [Measures].[Internet Sales Amount] on columns, [Customer].[Customer Geography].[Customer].[Crystal Zheng].lastsibling on rows From [Adventure Works]
برای توضیح بیشتر میتوان اضافه کرد که در کوئری بالا میزان فروش اینترنتی را برای آخرین مشتری در موقعیت جغرافیایی مشتری با نام [Crystal Zheng] واکشی شده است.
حال تصور کنید بخواهیم میزان فروش اینترنتی را برای تمامی مشتریان ایالت [Yveline] بدست بیاوریم. در این صورت MDX Query به شکل زیر خواهد بود
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[State-Province].[Yveline] ,[Customer].[Customer Geography].[Customer] )on rows From [Adventure Works]
تابع descendants دارای دو پارامتر میباشد. اولی برای مشخص نمودن شروع و مبدا در ساختار سلسله مراتبی و دومین برای مشخص کردن سطح واکشی در ساختار سلسله مراتبی میباشد. به عبارت دیگر در کوئری بالا تمامی زاد و رود ایالت [Yveline] در سطح شهر واکشی شده است و میزان فروش اینترنتی آن نمایش داده شده است.
در زیر یک کوئری ترکیبی با استفاده از دو تابع ancestor و descendants نوشته شده است.
Select [Measures].[Internet Sales Amount] on columns, descendants( ancestor( [Customer].[Customer Geography].[Customer].[Crystal Zheng], [Customer].[Customer Geography].[State-Province] ) ,[Customer].[Customer Geography].[Customer] )on rows From [Adventure Works]
در اینجا ابتدا جد یک مشتری در سطح ایالت بدست آمده سپس زاد و رود آن در سطح مشتری بدست می آید .
برای بدست آوردن فروش اینترنتی تمامی شهرهای کشور فرانسه میتوانیم به صورت زیر عمل کنیم.
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City] ) on rows From [Adventure Works]
تابع descendants دارای یک پارامتر سوم هم میباشد که مشخص کنندهی میزان واکشی سطوح میباشد و به صورت پیش فرض Self میباشد. بنابر این کوئری بالا و پایین ، نتیجه یکسان خواهند داشت
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], self )on rows From [Adventure Works]
حال اگر بخواهیم فروش اینترنتی را برای تمامی زاد و رود کشور فرانسه از سطح شهر به پایین واکشی کنیم داریم :
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], self_and_after ) on rows From [Adventure Works]
در این حالات تمامی زاد و رود کشور فرانسه از سطح شهر به پایین در خروجی قرار می گیرد .به این صورت که ابتدا اولین شهر می آید؛ سپس اولین کد پستی در آن شهر و بعد تمامی مشتری های آن کد پستی و بعد کد پستی بعدی و ...
به دست آوردن تمامی زاد و رود فرانسه از سطح بعد از شهر .
به عبارت دیگر ، خروجی باتوجه به ساختار سلسله مراتبی تعریف شده عبارت است از کد پستی و تمام مشتریان آن کد پستی و سپس کد پستی بعدی .
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], after )on rows From [Adventure Works]
در کوئری فوق، خود شهر در خروجی نمایش داده نمیشود.
به دست آوردن زاد و رود فرانسه تا یک سطح قبل از شهر .
در این حالت فرانسه و تمامی ایالت های آن در خروجی آورده می شود .
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], [Customer].[Customer Geography].[City], before )on rows From [Adventure Works]
همچنین میتوان دومین پارامتر تابع را به صورت عدد وارد کرد و این عدد بیانگر تعداد سطح پایینتر از پارامتر اول در ساختار سلسله مراتبی میباشد.
به عنوان مثال :
Select [Measures].[Internet Sales Amount] on columns, descendants( [Customer].[Customer Geography].[Country].[France], 2, before ) on rows From [Adventure Works]
در این حالت فرانسه و تمامی ایالت های آن در خروجی قرار می گیرد .
در ابتدا دو سطح ار کشور پایین می رویم و به شهر می رسیم و بعد زاد و رود فرانسه تا یکی قبل از شهر را بر می گرداند .
در قسمتهای بعدی در خصوص دیگر توابع مرتبط با ساختارهای سلسله مراتبی، توضیحاتی را ارایه خواهم کرد.