اشتراک‌ها
ایجاد فونتهای تحت وب با استفاده از icon

برای اینکه بتوانید از فونت‌های وبی بجای icon‌های طراحی شده استفاده کنید میتوانید آنها را با استفاده از این وب سایت تبدیل نمائید . استفاده از فونت بجای تصویر در وب مزیتهای بسیاری را بدنبال دارد.

ایجاد فونتهای تحت وب با استفاده از icon
مطالب
SQL Antipattern #2

بخش دوم : Naive Trees  

فرض کنید یک وب سایت حرفه‌ای خبری یا علمی-پژوهشی داریم که قابلیت دریافت نظرات کاربران را در مورد هر مطلب مندرج در سایت یا نظرات داده شده در مورد آن مطالب را دارا می‌باشد. یعنی هر کاربر علاوه بر توانایی اظهار نظر در مورد یک خبر یا مطلب باید بتواند پاسخ نظرات کاربران دیگر را نیز بدهد. اولین راه حلی که برای طراحی این مطلب در پایگاه داده به ذهن ما می‌رسد، ایجاد یک زنجیره با استفاده از کد sql زیر می‌باشد:

CREATE TABLE Comments (
comment_idSERIAL PRIMARY KEY,
parent_idBIGINT UNSIGNED,
comment TEXT NOT NULL,
FOREIGN KEY (parent_id) REFERENCES Comments(comment_id)
);

البته همان طور که پیداست بازیابی زنجیره‌ای از پاسخ‌ها در یک پرس‌وجوی sql کار سختی است. این نخ‌ها معمولا عمق نامحدودی دارند و برای به دست آوردن تمام نخ‌های یک زنجیره باید پرس‌وجوهای زیادی را اجرا نمود.

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

1.2 هدف: ذخیره و ایجاد پرس‌وجو در سلسله‌مراتب

وجود سلسله مراتب بین داده‌ها امری عادی محسوب می‌گردد. در ساختار داده‌ای درختی هر ورودی یک گره محسوب می‌گردد. یک گره ممکن است تعدادی فرزند و یک پدر داشته باشد. گره اول پدر ندارد، ریشه و گره فرزند که فرزند ندارد، برگ و گره‌ای دیگر، گره‌های غیربرگ نامیده می‌شوند.

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

Organizational chart: در این ساختار برای مثال در ارتباط بین کارمندان و مدیر، هر کارمند یک مدیر دارد که نشان‌دهنده‌ی پدر یک کارمند در ساختار درختی است. هر مدیر هم یک کارمند محسوب می‌گردد.

Threaded discussion: در این ساختار برای مثال در سیستم نظردهی و پاسخ‌ها، ممکن است زنجیره‌‌ای از نظرات در پاسخ به نظرات دیگر استفاده گردد. در درخت، فرزندان یک گره‌ی نظر، پاسخ‌های آن گره هستند.

در این فصل ما از مثال ساختار دوم برای نشان دادن Antipattern و راه حل آن بهره می‌گیریم.

2.2 Antipattern : همیشه مبتنی بر یکی از والدین

راه حل ابتدایی و ناکارآمد  

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

 CREATE TABLE Comments (  comment_idSERIAL PRIMARY KEY,
parent_idBIGINT UNSIGNED,
bug_idBIGINT UNSIGNED NOT NULL,
author BIGINT UNSIGNED NOT NULL,
comment_dateDATETIME NOT NULL,
comment TEXT NOT NULL,
FOREIGN KEY (parent_id)REFERENCES Comments(comment_id),
FOREIGN KEY (bug_id)         REFERENCES Bugs(bug_id),
FOREIGN KEY(author)          REFERENCES Accounts(account_id)
);

مثالی از پرس‌وجوی فوق را می‌توانید در زیر ببینید: 

لیست مجاورت :

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

• با استفاده از پرس‌وجوی زیر می‌توان فرزند بلافاصله‌ی یک "نظر" را برگرداند: 

SELECT c1.*, c2.*
FROM Comments c1 LEFT OUTER JOIN Comments c2
ON c2.parent_id = c1.comment_id;

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

SELECT c1.*, c2.*, c3.*, c4.*
FROM Comments c1                         -- 1st level
LEFT OUTER JOIN Comments c2
ON c2.parent_id = c1.comment_id  -- 2nd level
LEFT OUTER JOIN Comments c3
ON c3.parent_id = c2.comment_id  -- 3rd level
LEFT OUTER JOIN Comments c4
ON c4.parent_id = c3.comment_id; -- 4th level

این پرس‌وجو به این دلیل که با اضافه شدن ستون‌های دیگر، نوه‌ها را سطوح عمیق‌تری برمی‌گرداند، پرس‌وجوی مناسبی نیست. در واقع استفاده از توابع تجمیعی ، مانند COUNT() مشکل می‌شود.

راه دیگر برای به دست آوردن ساختار یک زیردرخت از لیست مجاورت برای یک برنامه، این است که سطرهای مورد نظر خود را از مجموعه بازیابی نموده و سلسه‌مراتب مورد نظر را در حافظه بازیابی نماییم و از آن به عنوان درخت استفاده نماییم:

   SELECT * FROM Comments WHERE bug_id = 1234;


نگهداری کردن یک درخت با استفاده از لیست مجاورت
البته برخی از عملکردها با لیست مجاورت به خوبی انجام می‌گیرد. برای مثال اضافه نمودن یک گره  (نظر)، مکان‌یابی مجدد برای یک گره یا یک زیردرخت .
INSERT INTO Comments (bug_id, parent_id, author, comment)
VALUES (1234, 7, 'Kukla' , 'Thanks!' );

بازیابی دوباره مکان یک نود یا یک زیردرخت نیز آسان است: 
UPDATE Comments SET parent_id = 3 WHERE comment_id = 6;

با این حال حذف یک گره از یک درخت در این روش پیچیده است. اگر بخواهیم یک زیردرخت را حذف کنید باید چندین پرس‌وجو برای پیدا کردن تمام نوه‌ها بنویسیم و سپس حذف نوه‌ها را از پایین‌ترین سطح شروع کرده و تا جایی که قید کلید خارجی برقرار شود ادامه دهیم. البته می‌توان از کلید خارجی با تنظیم ON DELETE CASCADE  استفاده کرد تا این کارها به طور خودکار انجام گیرد.
حال اگر بخواهیم یک نود غیر برگ را حذف کرده یا فرزندان آن را در درخت جابجا کنیم، ابتدا باید parent_id فرزندان آن نود را تغییر داده و سپس نود مورد نظر را حذف می‌کنیم:
SELECT parent_id FROM Comments WHERE comment_id = 6; -- returns 4
UPDATE Comments SET parent_id = 4 WHERE parent_id = 6;
DELETE FROM Comments WHERE comment_id = 6;


3.2 موارد تشخیص این Antipattern:
سؤالات زیر نشان می‌دهند که Naive Trees antipattern مورد استفاده قرار گرفته است:
  • چه تعداد سطح برای پشتیبانی در درخت نیاز خواهیم داشت؟
  • من همیشه از کار با کدی که ساختار داده‌ی درختی را مدیریت می‌کند، می‌ترسم
  • من باید اسکریپتی را به طور دوره‌ای اجرا نمایم تا سطرهای یتیم موجود در درخت را حذف کند.

4.2 مواردی که استفاده از این Antipattern مجاز است:
قدرت لیست مجاورت در بازیابی پدر یا فرزند مستقیم یک نود می‌باشد. قرار دادن یک سطر هم در لیست مجاورت کار ساده‌ای است. اگر این عملیات، تمام آن چیزی است که برای انجام کارتان مورد نیاز شما است، بنابراین استفاده از لیست مجاورت می‌تواند مناسب باشد.
برخی از برندهای RDBMS از افزونه‌هایی پشتیبانی می‌کنند که قابلیت ذخیره‌ی سلسله مراتب را در لیست مجاورت ممکن می‌سازد. مثلا SQL-99، پرس‌وجوی بازگشتی را تعریف می‌کند که مثال آن در ادامه آمده است:
  WITH CommentTree (comment_id, bug_id, parent_id, author, comment, depth)
AS (
SELECT *, 0 AS depth FROM Comments
WHERE parent_id IS NULL
UNION ALL
SELECT c.*, ct.depth+1 AS depth FROM CommentTreect
JOIN Comments c ON (ct.comment_id = c.parent_id)
)
SELECT * FROM CommentTree WHERE bug_id = 1234;

Microsoft SQL Server 2005، Oracle 11g، IBM DB2 و PostgreSQL 8.4 نیز از پرس‌وجوی بازگشتی پشتیبانی می‌کنند.Oracle 9i و 10g از عبارت WITH استفاده می‌کنند، ولی نه برای پرس‌وجوهای بازگشتی. در عوض می‌توانید از پرس‌وجوی زیر برای ایجاد پرس‌وجوی بازگشتی استفاده نمایید: 
SELECT * FROM Comments
START WITH comment_id = 9876
CONNECT BY PRIOR parent_id = comment_id;


5.2 راه حل: استفاده از مدل‌های درختی دیگر
جایگزین‌های دیگری برای ذخیره‌سازی داده‌های سلسله مراتبی وجود دارد. البته برخی از این راه حل‌ها ممکن است در لحظه‌ی اول پیچید‌تر از لیست مجاورت به نظر آیند، ولی برخی از عملیات درخت که در لیست مجاورت بسیار سخت یا ناکارآمد است، را آسان‌تر می‌کنند.
شمارش مسیر :
مشکل پرهزینه بودن بازیابی نیاکان یک گره که در روش لیست مجاورت وجود داشت در روش شمارش مسیر به این ترتیب حل شده است: اضافه نمودن یک صفت به هر گره که رشته‌ای از نیکان آن صفت در آن ذخیره شده است.
در جدول Comments به جای استفاده از parent_id، یک ستون به نام path که توع آن varchar است تعریف شده است. رشته‌ای که در این ستون تعریف شده است، ترتیبی از فرزندان این سطر از بالا به پایین درخت است. مانند مسیری که در سیستم عامل UNIX، برای نشان دادن مسیر در سیستم فایل استفاده شده است. شما می‌توانید از / به عنوان کاراکتر جداکننده استفاده نمایید. دقت کنید برای درست کار کردن پرس‌وجوها حتما در آخر مسیر هم این کاراکتر را قرار دهید. پرس‌وجوی تشکیل چنین درختی به شکل زیر است:
  CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY,
path VARCHAR(1000),
bug_id BIGINT UNSIGNED NOT NULL,
author BIGINT UNSIGNED NOT NULL,
comment_date DATETIME NOT NULL,
comment TEXT NOT NULL,
FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id),
FOREIGN KEY (author) REFERENCES Accounts(account_id)

در این روش، هر گره مسیری دارد که شماره خود آن گره هم در آنتهای آن مسیر قرار دارد. این به دلیل درست جواب دادن پرس‌وجوهای ایجاد شده است.
می‌توان نیاکان را با مقایسه‌ی مسیر سطر کنونی با مسیر سطر دیگر به دست آورد. برای مثال برای یافتن نیاکان گره (نظر) شماره‌ی 7 که مسیر آن 1/4/6/7/ می‌باشد، می‌توان چنین نوشت:
  SELECT * FROM Comments AS c
WHERE '1/4/6/7/' LIKE c.path || '%' ;

این پرس‌وجو الگوهایی را می‌یابد که از مسیرهای 1/4/6/%، 1/4/% و 1/% نشأت می‌گیرد.
همچنین فرزندان (نوه‌های) یک گره، مثلا گره‌ی 4 را که مسیرش 1/4/ است را می‌توان با پرس‌وجوی زیر یافت:
  SELECT * FROM Comments AS c
WHERE c.path LIKE '1/4/' || '%' ;

الگوی 1/4/% با مسیرهای 1/4/5/، 1/4/6/ و 1/4/6/7/ تطابق می‌یابد.
همچنین می‌توان پرس‌وجوهای دیگری را نیز در این مسیر به سادگی انجام داد؛ مانند محاسبه‌ی مجموع هزینه‌ی گره‌ها در یک زیردرخت یا شمارش تعداد گره‌ها.
اضافه نمودن یک گره هم مانند ساختن خود مدل است. می‌توان یک گره‌ی غیر برگ را بدون نیاز به اصلاح هیچ سطری اضافه نمود. برای این کار مسیر را را از گره‌ی پدر کپی کرده و در انتها شماره‌ی خود گره را به آن اضافه می‌کنیم.
از مشکلات این روش می‌توان به عدم توانایی پایگاه داده‌ها در تحمیل این نکته که مسیر یک گره درست ایجاد شده است و یا تضمین وجود گره‌ای در مسیری خاص، اشاره نمود. همچنین نگهداری رشته‌ی مسیر یک گره مبتنی بر کد برنامه است و اعتبارسنجی آن کاری هزینه‌بر است. این رشته اندازه‌ای محدود دارد و درخت‌هایی با عمق نامحدود را پشتیبانی نمی‌کند.

مجموعه‌های تودرتو :
مجموعه‌های تودرتو، اطلاعات را با هر گره‌ای که مربوط به مجموعه‌ای از نوه‌هایش است، به جای این که تنها مربوط به یک فرزند بلافصلش باشد، ذخیره می‌کنند.

 این اطلاعات می‌توانند به وسیله‌ی هر گره‌ای که در درخت با دو شماره‌ی nsleft و nsright ذخیره شده، نمایش داده شوند:
  CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY,
nsleft INTEGER NOT NULL,
nsright INTEGER NOT NULL,
bug_id BIGINT UNSIGNED NOT NULL,
author BIGINT UNSIGNED NOT NULL,
comment_date DATETIME NOT NULL,
comment TEXT NOT NULL,
FOREIGN KEY (bug_id) REFERENCES Bugs (bug_id),
FOREIGN KEY (author) REFERENCES Accounts(account_id)
);

شماره‌ی سمت چپ یک گره از تمام شماره‌های سمت چپ فرزندان آن گره کوچک‌تر و شماره‌ی سمت راست آن گره از تمام شماره‌های سمت راست آن گره بزرگ‌تر است. این شماره‌ها هیچ ارتباطی به comment_id مربوط به آن گره ندارند.

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

با اختصتص شماره‌ها به هر گره، می‌توان از آن‌ها برای یافتن نیاکان و فرزندان آن گره بهره جست. برای مثال برای بازیابی گره‌ی 4 و فرزندان (نوه‌های) آن باید دنبال گره‌هایی باشیم که شماره‌های آن گره‌ها بین nsleft و nsright گره‌ی شماره‌4 باشد:

  SELECT c2.* FROM Comments AS c1
JOIN Comments as c2
ON c2.nsleft BETWEEN c1.nsleft AND c1.nsright
WHERE c1.comment_id = 4;

همچنین می‌توان گره‌ی شماره‌ی 6 و نیاکان آن را با دنبال نمودن گره‌هایی به دست آورد که شماره‌های آن‌ها در محدوده‌ی شماره‌ی گره‌ی 6 باشد: 
SELECT c2.*
FROM Comments AS c1
JOIN Comment AS c2
ON c1.nsleft BETWEEN c2.nsleft AND c2.nsright
WHERE c1.comment_id = 6;

یک مزیت مهم روش مجموعه‌ای تودرتو، این است که هنگامی که یک گره را حذف می‌کنیم، نوه‌های آن به طور مستقیم به عنوان فرزندان پدر گره‌ی حذف شده تلقی می‌شوند.
برخی از پرس‌وجوهایی که در روش لیست مجاورت ساده بودند، مانند بازیابی فرزند یا پدر بلافصل، در روش مجموعه‌های تودرتو پیچیده‌تر می‌باشند. برای مثال برای یافتن پدر بلافصل گره‌ی شماره‌ی 6 باید چنین نوشت: 
  SELECT parent.* FROM Comment AS c
JOIN Comment AS parent
ON c.nsleft BETWEEN parent.nsleft AND parent.nsright
LEFT OUTER JOIN Comment AS in_between
ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsright
AND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright
WHERE c.comment_id = 6
AND in_between.comment_id IS NULL;

دست‌کاری درخت، اضافه، حذف و جابجا نمودن گره‌ها در آن درروش مجموعه‌های تودرتو از مدل‌های دیگر پیچیده‌تر است. هنگامی که یک گره‌ی جدید را اضافه می‌کنیم، باید تمام مقادیر چپ و راست بزرگ‌تر از مقدار سمت چپ گره‌ی جدید را مجددا محاسبه کنیم؛ که این شامل برادر سمت راست گره‌ی جدید، نیاکان آن و برادر سمت راست نیاکان آن می‌باشد. همچنین اگر گره‌ی جدید به عنوان گره‌ی غیربرگ اضافه شده باشد، شامل فرزندان آن هم می‌شود. برای مثال اگر بخواهیم گره‌ی جدیدی به گره‌ی 5 اضافه نماییم، باید چنین بنویسیم: 
-- make space for NS values 8 and 9
UPDATE Comment
SET nsleft = CASE WHEN nsleft >= 8 THEN nsleft+2 ELSE nsleft END,
nsright = nsright+2
WHERE nsright >= 7;

-- create new child of comment #5, occupying NS values 8 and 9
INSERT INTO Comment (nsleft, nsright, author, comment)
VALUES (8, 9, 'Fran' , 'Me too!' );

تنها مزیت این روش نسبت به روش‌های قبلی ساده‌تر و سریع‌تر شدن ایجاد پرس‌وجوها برای پیدا کردن فرزندان یا پدران یک درخت است. اگر هدف استفاده از درخت شامل اضافه نمودن متعدد گره‌ها است، مجموعه‌های تودرتو انتخاب خوبی نیست.

Closure Table
راه حل closure table روشی دیگر برای ذخیره‌ی سلسه‌مراتبی است. این روش علاوه بر ارتباطات مستقیم پدر- فرزندی، تمام مسیرهای موجود در درخت را ذخیره می‌کند.

این روش علاوه بر داشتن یک جدول نظرها، یک جدول دیگر به نام TreePaths با دو ستون دارد که هر کدام از این ستون‌ها یک کلید خارجی به جدولComment هستند:
  CREATE TABLE Comments ( comment_id SERIAL PRIMARY KEY,
bug_id BIGINT UNSIGNED NOT NULL,
author BIGINT UNSIGNED NOT NULL,
comment_date DATETIME NOT NULL,
comment TEXT NOT NULL,
FOREIGN KEY (bug_id) REFERENCES Bugs(bug_id),
FOREIGN KEY (author) REFERENCES Accounts(account_id)
);
CREATE TABLE TreePaths (
ancestor BIGINT UNSIGNED NOT NULL,
descendant BIGINT UNSIGNED NOT NULL,
PRIMARY KEY(ancestor, descendant),
FOREIGN KEY (ancestor) REFERENCES Comments(comment_id),
FOREIGN KEY (descendant) REFERENCES Comments(comment_id)
);

به جای استفاده از جدول Comments برای ذخیره‌ی اطلاعات مربوط به یک درخت از جدول TreePath استفاده می‌کنیم. به ازای هر یک جفت گره در این درخت یک سطر در جدول ذخیره می‌شود که ارتباط پدر فرزندی را نمایش می‌دهد و الزاما نباید این دو پدر فرزند بلافصل باشد. همچنین یک سطر هم به ازای ارتباط هر گره با خودش به جدول اضافه می‌گردد.

پرس‌وجوهای بازیابی نیاکان و فرزندان (گره‌ها) از طریق جدول TreePaths ساده‌تر از روش مجموعه‌های تودرتو است. مثلا برای بازیابی فرزندان (نوه‌های) گره‌ی شماره‌ی 4، سطرهایی که نیاکان آن‌ها 4 است را به دست می‌آوریم:

   SELECT c.*  FROM Comments AS c
JOIN TreePaths AS t ON c.comment_id = t.descendant
WHERE t.ancestor = 4;

برای به دست آوردن نیاکان گره‌ی شماره‌ی 6، سطرهایی از جول TreePaths را به دست می‌آوریم که فرزندان آن‌ها 6 باشد:
SELECT c.*
FROM Comments AS c
JOIN TreePaths AS t ON c.comment_id = t.ancestor
WHERE t.descendant = 6;

برای اضافه کردن گره‌ی جدید، برای مثال به عنوان فرزند گره‌ی شماره‌ی 5، ابتدا سطری که به خود آن گره برمی‌گردد را اضافه می‌کنیم، سپس یک کپی از سطوری که در جدول TreePaths، به عنوان فرزندان (نوه‌های) گره‌ی شماره‌5 هستند (که شامل سطری که به خود گره‌ی 5 به عنوان فرزند اشاره می‌کند) به جدول اضافه نموده و فیلد descendant آن را با شماره‌ی گره‌ی جدید جایگزین می‌کنیم:
  INSERT INTO TreePaths (ancestor, descendant) SELECT t.ancestor, 8
FROM TreePaths AS t
WHERE t.descendant = 5
UNION ALL
SELECT 8, 8;

در این جا می‌توان به اهمیت ارجاع یک گره به خودش به عنوان پدر (یا فرزند) پی برد.
برای حذف یک گره، مثلا گره‌ی شماره‌ی 7، تمام سطوری که فیلد descendant آن‌ها در جدول TreePaths برابر با 7 است حذف می‌کنیم:
   DELETE FROM TreePaths WHERE descendant = 7;

برای حذف یک زیردرخت کامل، برای مثال گره‌ی شماره‌ی 4 و فرزندان (نوه‌های) آن، تمام سطوری که در جدول TreePaths دارای فیلد descendant با مقدار 4 هستند، حذف می‌کنیم. علاوه بر این باید نودهایی که به عنوان descendant به فیلد descendant گره‌ی 4، ارجاع داده می‌شوند نیز باید حذف گردد: 

DELETE FROM TreePaths
WHERE descendant IN (SELECT descendant
FROM TreePaths
WHERE ancestor = 4);

دقت کنید وقتی گره‌ای را حذف می‌کنیم، بدان معنی نیست که خود گره (نظر) را حذف می‌کنیم. البته این برای مثال نظر و پاسخ آن مقداری عجیب است ولی در مثال کارمندان در چارت سازمانی امری معمول است. هنگامی که ارتباطات یک کاربر را تغییر می‌دهیم، از حذف در جدول TreePaths استفاده می‌کنیم و این قضیه که ارتباطات کارمندان در جدول جداگانه‌ای ذخیره شده است به ما انعطاف‌پذیری بیشتری می‌دهد. 
برای جابجایی یک زیردرخت از مکانی به مکان دیگری در درخت، سطرهایی که ancestor گره‌ی بالایی زیردرخت را برمی‌گردانند و فرزندان آن گره را حذف می‌کنیم. برای مثال برای جابجایی گره‌ی شماره‌ی 6 به عنوان فرزند گره‌ی شماره‌ی 4 و قرار دادن آن به عنوان فرزند گره‌ی شماره‌ی 3، این چنین عمل می‌کنیم. فقط باید حواسمان جمع باشد سطری که گره‌ی شماره‌ی 6 به خودش ارجاع داده است را حذف نکنیم:
DELETE FROM TreePaths
WHERE descendant IN (SELECT descendant
                                         FROM TreePaths
                                         WHERE ancestor = 6)
AND ancestor IN (SELECT ancestor
                             FROM TreePaths
                             WHERE descendant = 6
                                 AND ancestor != descendant);

آن‌گاه این زیردرخت جدا شده را با اضافه کردن سطرهایی که با ancestor مکان جدید و descendant زیردرخت، منطبق هستند، به جدول اضافه می‌کنیم:
INSERT INTO TreePaths (ancestor, descendant)
SELECT supertree.ancestor, subtree.descendant
FROM TreePaths AS supertree
CROSS JOIN TreePaths AS subtree
WHERE supertree.descendant = 3
AND subtree.ancestor = 6;

روش Closure Table آسان‌تر از روش مجموعه‌های تودرتو است. هر دوی آن‌ها روش‌های سریع و آسانی برای ایجاد پرس‌وجو برای نیاکان و نوه‌ها دارند. ولی Closure Table برای نگهداری اطلاعات سلسله مراتب آسان‌تر است. در هر دو طراحی ایجاد پرس‌وجو در فرزندان و پدر بلافصل سرراست‌تر از روش‌ای لیست مجاورت و شمارش مسیر می‌باشد.
می‌توان عملکرد Closure Table را برای ایجاد پرس‌وجو روی فرزندان و پدر بلافصل را آسان‌تر نیز نمود. اگر فیلد path_length را به جدول TreePaths اضافه نماییم این کار انجام می‌شود. path_length گره‌ای که به خودش ارجاع می‌شود، صفر است. path_length فرزند بلافصل هر گره 1، path_length نوه‌ی آن 2 می‌باشد و به همین ترتیب path_lengthها را در هر سطر مقداردهی می‌کنیم. اکنون یا فتن فرزند گره‌ی شماره‌ی 4 آسان‌تر است:   
SELECT *
FROM TreePaths
WHERE ancestor = 4 AND path_length = 1;


از کدام طراحی استفاده نماییم؟
در این جا این سؤال مطرح است که ما باید از کدام طراحی استفاده نماییم. در پاسخ به این سؤال باید گفت که هر کدام از این روش‌ها نقاط قوت و ضعفی دارند که ما باید نسبت به عملیاتی که می‌خواهیم انجام دهیم از این طراحی‌ها استفاده کنیم. جدولی که در ادامه آمده است، مقایسه‌ای است میان میزان سهولت اجرای این طراحی‌ها در استفاده از پرس‌وجوهای متفاوت.

 لازم به ذکر است در اینجا ستون سوم (Query Child) به معنای پرس‌وجوهایی است که با فرزندان کار می‌کند و ستون چهارم  (Query Tree)  به معنای پرس‌وجوهایی است که با کل درخت کار می‌کنند، می‌باشد. 
نظرات مطالب
بررسی مشکلات AngularJS 1.x
مسلما واکشی اطلاعات از صفحات وب انکارناپذیر است ولی وقتی که کل وب سایت با WebApi پیاده سازی می‌شود و از فریم ورک‌های تحت کلاینت استفاده شود، افراد دیگر به اطلاعات با ساختار دست پیدا می‌کنند و خیلی راحت‌تر از وب سایت‌های دیگر امکان واکشی اطلاعات وجود دارد.
راه حلی برای این موضوع وجود دارد؟
مطالب
ASP.NET Web API - قسمت اول
بخش هایی از کتاب "مرجع کامل ASP.NET MVC (با پوشش کامل ASP.NET MVC 4)"
ترجمه و تالیف: بهروز راد
وضعیت: در دست چاپ


Web API چیست؟

Web API، نوع قالب جدیدی برای پروژه‌های مبتنی بر وب در NET. است که بر مبنای اصول و الگوهای موجود در ASP.NET MVC ساخته شده است و همراه با ASP.NET MVC 4 وجود دارد. Web API توسعه گران را قادر می‌سازد تا با استفاده از یک الگوی ساده که در Controllerها پیاده سازی می‌شود، وب سرویس‌های مبتنی بر پروتوکل HTTP را با کدها و تنظیمات کم ایجاد کنند. این سبک جدید برای ایجاد وب سرویس ها، می‌تواند در انواع پروژه‌های NET. مانند ASP.NET MVC، ASP.NET Web Forms، Windows Application و ... استفاده شود.
یک سوال کاملاً منطقی در اینجا به وجود می‌آید. چرا نیاز به بستری جدید برای ایجاد وب سرویس داریم؟ آیا در حال حاضر مایکروسافت بستری محبوب و فراگیر برای توسعه‌ی وب سرویس هایی که بتوانند با پروتوکل SOAP تعامل داشته باشند در اختیار ندارد؟ مگر وب سرویس‌های ASMX از زمان معرفی ASP.NET وجود نداشته اند؟ آیا تکنولوژی WCF مایکروسافت، بیشترین انعطاف پذیری و قدرت را برای تولید وب سرویس‌ها در اختیار قرار نمی‌دهد؟ وب سرویس‌ها جایگاه خود را یافته اند و توسعه گران با تکنولوژی‌های موجود به خوبی آنها را پیاده سازی و درک می‌کنند. چرا Web API؟

چرا Web Api؟
برای پاسخ به این سوال، باید برخی مشکلات را بررسی کنیم و ببینیم ابزارهای موجود چه راه حلی برای آنها در نظر گرفته اند. اگر با گزینه هایی که در ادامه می‌آیند موافق هستید، خواندن این مطلب را ادامه دهید، و اگر اعتقادی به آنها ندارید، پس نیازهای شما به خوبی با بسترهای موجود پاسخ داده می‌شوند.
  • من معتقد هستم که راه بهتری برای ایجاد وب سرویس‌ها وجود دارد.
  • من معتقد هستم که روش‌های ساده‌تری برای ایجاد وب سرویس‌ها وجود دارد و WCF بیش از حد پیچیده است.
  • من معتقد هستم که تکنولوژی‌های پایه‌ی وب مانند اَفعال GET، POST، PUT و DELETE برای انجام اَعمال مختلف توسط وب سرویس‌ها کافی هستند.
اگر همچنان در حال خواندن این مطلب هستید، توضیحات خود را با شرح تفاوت میان Web API و تکنولوژی‌های دیگر هم حوزه‌ی آن ادامه می‌دهیم و خواهید دید که استفاده از Web API چقدر آسان است.

تفاوت Web API و WCF
وب سرویس‌های ASMX تا چندین سال، انتخاب اول برای ایجاد وب سرویس‌های مبتنی بر پروتوکل SOAP با استفاده از پروتوکل HTTP بودند. وب سرویس‌های ASMX، از وب سرویس‌های ساده که نیاز به قابلیت تعامل پایین داشتند و در نتیجه به پروتوکل SOAP نیز وابسته نبودند پشتیبانی نمی‌کردند. WCF جای وب سرویس‌های ASMX را گرفت و خود را به عنوان آخرین و بهترین روش برای ایجاد وب سرویس‌ها در بستر NET. معرفی کرد. نمونه ای از یک سرویس WCF بر مبنای پروتوکل HTTP در NET. به صورت ذیل است.
[ServiceContract]
public interface IService1                       
{
    [OperationContract]                                 
    string GetData(int value);
    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);
}
...
public class Service1 : IService1                         
{
    public string GetData(int value)
    {
        return string.Format("You entered: {0}", value);
    }

    public CompositeType GetDataUsingDataContract(CompositeType composite)
    {
        if (composite == null)
        {
            throw new ArgumentNullException("composite");
        }
        if (composite.BoolValue)
        {
            composite.StringValue += "Suffix";
        }
        return composite;
    }
}
در WCF، پایه و اساس وب سرویس را یک interface تشکیل می‌دهد. در حقیقت اجزای وب سرویس را باید در یک interface تعریف کرد. هر یک از متدهای وب سرویس در interface تعریف شده که صفت OperationContract برای آنها در نظر گرفته شده باشد، به عنوان یکی از اَعمال و متدهای قابل فراخوانی توسط استفاده کننده از وب سرویس در دسترس هستند. سپس کلاسی باید ایجاد کرد که interface ایجاد شده را پیاده سازی می‌کند. در قسمت بعد، با مفاهیم پایه‌ی Web API و برخی کاربردهای آن در محیط ASP.NET MVC آشنا می‌شوید.

نتیجه گیری
Web API، یک روش جدید و آسان برای ایجاد وب سرویس ها، بر مبنای مفاهیم آشنای ASP.NET MVC و پایه‌ی وب است. از این روش می‌توان در انواع پروژه‌های NET. استفاده کرد.
مطالب
MIME Sniffing و استاندارد OWASP

 یکی از نکات امنیتی که استاندارد  Owasp  بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایل‌های نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی  قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایت‌هایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارش‌ها با استفاده از ابزار FastReport  ایجاد شده بود و با توجه به اینکه در این ابزار از فایل‌های axd استفاده می‌گردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.


Mime Sniffing

زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناسایی‌ها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایت‌های ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایل‌هایی با پسوند Gif، الگوی بایت‌های ابتدایی شامل  47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایل‌ها، بایت‌های ابتدایی الگوی یکسانی ندارند، پس نمی‌توان به این روش نیز بسنده کرد.


روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config  به شکل زیر است:

 <httpProtocol>
      <customHeaders>
  ...
        <remove name="X-Content-Type-Options"/>
        <add name="X-Content-Type-Options" value="nosniff" />
        ..
      </customHeaders>
    </httpProtocol>

در پروژه ما به دلیل اینکه بخشی از گزارش‌ها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل می‌کرد و در گزارش نمایش داده نمی‌شد و در کنسول پیام‌هایی به شکل زیر دریافت میکردیم:

Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.

با نگاهی به Response  دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:

 پس باید این هدر را برای بعضی از آدرس‌ها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخش‌ها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:

  <location path="WebResource.axd" >
    <system.webServer>
      <httpProtocol>
        <customHeaders>
          <remove name="X-Content-Type-Options" />
        </customHeaders>
      </httpProtocol>
    </system.webServer>
  </location>

 نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:

 
اشتراک‌ها
تحلیل سرعت بارگذاری یک سایت از نقاط مختلف جهان به صورت گرافیکی و برروی نقشه جهان.

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

تحلیل سرعت بارگذاری یک سایت از نقاط مختلف جهان به صورت گرافیکی و برروی نقشه جهان.
اشتراک‌ها
جشنواره وب ایران

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

جشنواره وب ایران