نظرات مطالب
روش‌هایی برای بهبود سرعت برنامه‌های مبتنی بر Entity framework
به نظر شما کوئری‌های پایین رو چطور میشه بهینه نوشت؟
List<Stat> allQuestion = (from a in TempClass.Stats
         where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
               a.Subject.SubjectID == tileNumber
         select a).AsParallel().ToList();

int allQuestionCount = allQuestion.Count;

int correctCount = (from a in allQuestion
                    where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                          a.Subject.SubjectID == tileNumber
                    select a.CorrectQuestionCount).Sum();

int totalTime = (from a in allQuestion
                 where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                       a.Subject.SubjectID == tileNumber
                 select a.TotalTime).Sum();

double score = (from a in allQuestion
                where a.Person.PersonID == TempClass.ActiveUser.PersonID && 
                      a.Subject.SubjectID == tileNumber
                select a.Score).Sum(); 
50 بار دستورات بالا اجرا میشه و یک مکث حدودا 20 ثانیه‌ای داره
مطالب
کار با دیتاتایپ JSON در MySQL - قسمت چهارم
MySQL قادر به ایندکس کردن ستون‌های JSON نمی‌باشد. برای حل این مشکل میتوانیم از generated columnها استفاده کنیم. منظور، ایجاد ستون‌هایی است که مقدارشان به صورت محاسبه شده و براساس ستون‌های دیگر میباشد؛ به عنوان مثال جدول کاربران زیر را در نظر بگیرید:
CREATE TABLE `Users` (
  id int NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(255) NOT NULL,
  last_name VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL,
  gender ENUM('Male','Female') NOT NULL,
  PRIMARY KEY (`id`)
)
برای کوئری گرفتن full name در حالت معمول میتوانیم از تابع CONCAT استفاده کنیم:
SELECT 
    *, CONCAT(first_name, '', last_name) AS full_name
FROM
    Users;
اما توسط generated columns میتوانیم یک ستون را به جدول کاربران اضافه کنیم که مقدارش براساس دو فیلد first_name و last_name محاسبه و مقدار دهی شود:
ALTER TABLE Users
ADD COLUMN full_name TEXT GENERATED ALWAYS 
AS (CONCAT(first_name, ' ', last_name))
همانطور که مشاهده میکنید از سینتکس GENERATE ALWAYS برای ایجاد generated column استفاده شده‌است. در MySQL دو نوع generated column وجود دارد: STORED و VIRTUAL؛ تفاوت آنها نیز در نحوه ذخیره‌سازی است. در حالت VIRTUAL که حالت پیش‌فرض است، مقادیر ذخیره نمیشوند؛ بلکه به صورت on the fly محاسبه و در خروجی نمایش داده خواهند شد. در حالیکه نوع STORED همانطور که از نامش پیداست، ذخیره خواهند شد؛ در نتیجه قابلیت ایندکس‌گذاری را دارد. برای تعیین نوع ستون نیز سینتکس آن اینگونه خواهد بود:
ALTER TABLE Users
ADD COLUMN full_name TEXT GENERATED ALWAYS 
AS (CONCAT(first_name, ' ', last_name)) STORED

همچنین لازم به ذکر است که حین استفاده از generated columns باید نکات زیر را در نظر داشته باشید:
  • generated columnsها نمیتوانند شامل subqueries, parameters, variables, stored procedure, user-defined functions باشند.
  • بر روی یک ستون generated نمیتوان AUTO_INCREMENT گذاشت یا اینکه از یک ستون AUTO_INCREMENT برای محاسبه generated column استفاده کرد.
  • کلیدهای خارجی‌ای که در generated columnsها استفاده میشوند، قابلیت استفاده از CASCADE, SET NULL, or SET DEFAULT as ON UPDATE or ON DELETE را نخواهند داشت.

در ادامه یک generated column را برای جدول productsMetadata تعیین خواهیم کرد: 
ALTER TABLE productMetadata
ADD COLUMN id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.id'))) STORED NOT NULL

بنابراین زمانیکه یک مقدار JSON را ذخیره میکنیم، کلید اصلی از path تعیین شده استخراج شده و به عنوان یک computed column برای این جدول تعیین خواهد شد. در ادامه میتوانید جزئیات تغییر فوق را مشاهده کنید: 

  
اکنون کوئری زیر را در نظر بگیرید که رکوردی با آی‌دی ۱ را بازیابی خواهد کرد:
SELECT data ->> "$.description.shortDescription" FROM productMetadata
WHERE id = 1;
از آنجائیکه هیچ ایندکسی برای این فیلد جدید لحاظ نشده است، MySQL کل ردیف‌ها را برای یافتن id موردنظر جستجو خواهد کرد. این مورد را میتوانید با دستور EXPLAIN نیز مشاهده کنید:


همانطور که مشاهده میکنید مقدار type به ALL تنظیم شده‌است؛ همچنین مقدار rows نیز تعداد ردیف‌های جدول است که در اینجا ۱۳ ردیف دیتا را داریم. قاعدتاً با اضافه شدن دیتای جدید به جدول، جستجو نیز به مراتب کندتر خواهد شد. بنابراین با اضافه کردن ایندکس میتوانیم مشکل این کند بودن را رفع کنیم. به همین جهت در ادامه یک ایندکس را براساس ستون id که یک generated column است ایجاد خواهیم کرد:

CREATE INDEX idx_json_data ON productMetadata (id);

اکنون اگر یکبار دیگر کوئری قبلی را اجرا کنیم، خواهیم دید که تعداد rows به ۱ و همچنین type به ref ست شده‌اند:



مطالب
SQL Antipattern #1
در این سلسله نوشتار قصد دارم ترجمه و خلاصه چندین فصل از کتاب ارزشمند ( SQL Antipatterns: Avoiding the Pitfalls of Database Programming (Pragmatic Programmers که حاصل تلاش گروه IT موسسه هدایت فرهیختگان جوان می‌باشد، را ارائه نمایم.

بخش اول : Jaywalking  


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

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

برنامه نویسان معمولا برای جلوگیری از ایجاد جدول واسطی [1] که رابطه‌های چند به چند زیادی دارد از یک لیست که با کاما داده‌هایش از هم جدا شده‌اند، استفاده می‌کنند. بدین جهت اسم این بخش jaywalking ,antipatten می‌باشد، زیرا jaywalking نیز عملیاتی است که از تقاطع جلوگیری می‌کند.

1.1  هدف:  ذخیره کردن چندین صفت  

طراحی کردن جدولی که ستون آن فقط یک مقدار دارد، بسیار ساده و آسان می‌باشد. شما می‌توانید نوع داده‌ایی که متعلق به ستون می‌باشد را انتخاب نمایید. مثلا از نوع (int,date…)

چگونه می‌توانیم مجموعه‌ایی از مقادیری که به یکدیگر مرتبط هستند را در یک ستون ذخیره نماییم ؟

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

CREATE TABLE Products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(1000),
account_id BIGINT UNSIGNED,
-- . . .
FOREIGN KEY (account_id) REFERENCES Accounts(account_id)
);

INSERT INTO Products (product_id, product_name, account_id)
VALUES (DEFAULT, 'Visual TurboBuilder' , 12);

2.1 Antipattern : قالب بندی لیست هایی که با کاما از یکدیگر جدا شده اند 
برای اینکه تغییرات در پایگاه داده را به حداقل برسانید، می‌توانید نوع شناسه‌ی کاربر را از نوع varchar قرار دهید. در این صورت می‌توانید چندین شناسه‌ی کاربر که با کاما از یکدیگر جدا شده‌اند را در یک ستون ذخیره نمایید. پس در جدول محصولات، شناسه‌ی کاربران را از نوع varchar قرار می‌دهیم.  
CREATE TABLE Products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(1000),
account_id VARCHAR(100), -- comma-separated list
-- . . .
);

INSERT INTO Products (product_id, product_name, account_id)
VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34' );

 اکنون مشکلات کارایی و جامعیت داده‌ها را در این راه حل پیشنهادی بررسی می‌نماییم . 

بدست آوردن محصولاتی برای یک کاربر خاص

بدلیل اینکه تمامی شناسه‌ی کاربران که بصورت کلید خارجی جدول Products می‌باشند به صورت رشته در یک فیلد ذخیره شده‌اند و حالت ایندکس بودن آنها از دست رفته است، بدست آوردن محصولاتی برای یک کاربر خاص سخت می‌باشد. به عنوان مثال بدست آوردن محصولاتی که کاربری با شناسه‌ی 12 خریداری نموده به‌صورت زیر می‌باشد: 

SELECT * FROM Products WHERE account_id REGEXP '[[:<:]]12[[:>:]]' ;
بدست آوردن کاربرانی که یک محصول خاص خریداری نموده اند
 Join کردن account_id با Accounts table  یعنی جدول مرجع نا‌مناسب و غیر معمول می‌باشد. مساله مهمی که وجود دارد این است که زمانیکه بدین صورت شناسه‌ی کاربران را ذخیره می‌نماییم، ایندکس بودن آنها از بین می‌رود. کد زیر کاربرانی که محصولی با شناسه‌ی 123 خریداری کرده‌اند را محاسبه می‌کند.
SELECT * FROM Products AS p JOIN Accounts AS a
ON p.account_id REGEXP '[[:<:]]' || a.account_id || '[[:>:]]'
WHERE p.product_id = 123;

  ایجاد توابع تجمیعی [2]
مبنای چنین توابعی از قبیل sum(), count(), avg() بدین صورت می‌باشد که بر روی گروهی از سطرها اعمال می‌شوند. با این حال با کمک ترفندهایی می‌توان برخی از این توابع را تولید نماییم هر چند برای همه آن‌ها این مساله صادق نمی‌باشد. اگر بخواهیم تعداد کاربران برای هر محصول را بدست بیاوریم ابتدا باید طول لیست شناسه‌ی کاربران را محاسبه کنیم، سپس کاما را از این لیست حذف کرده و طول جدید را از طول قبلی کم کرده و با یک جمع کنیم. نمونه کد آن در زیر آورده شده است:   
SELECT product_id, LENGTH(account_id) - LENGTH(REPLACE(account_id, ',' , '' )) + 1
AS contacts_per_product
FROM Products;

ویرایش کاربرانی که یک محصول خاص خریداری نموده‌اند
ما به راحتی می‌توانیم یک شناسه‌ی جدید را به انتهای این رشته اضافه نماییم؛ فقط مرتب بودن آن را بهم میریزیم. در نمونه اسکریپت زیر گفته شده که در جدول محصولات برای محصولی با شناسه‌ی 123 بعد از کاما یک کاربر با شناسه‌ی 56 درج شود:
UPDATE Products
SET account_id = account_id || ',' || 56
WHERE product_id = 123;
برای حذف یک کاربر از لیست کافیست دو دستور sql را اجرا کرد: اولی برای fetch کردن شناسه‌ی کاربر از لیست و بعدی برای ذخیره کردن لیست ویرایش شده. بطور مثال تمامی افرادی که محصولی با شناسه‌ی 123 را خریداری کرده‌اند، از جدول محصولات انتخاب می‌کنیم. برای حذف کاربری با شناسه‌ی 34 از لیست کاربران، بر حسب کاما مقادیر لیست را در آرایه می‌ریزیم، شناسه‌ی مورد نظر را جستجو می‌کنیم و آن را حذف می‌کنیم. دوباره مقادیر درون آرایه را بصورت رشته درمیاوریم و جدول محصولات را با لیست جدید کاربران برای محصولی با شناسه‌ی 123 بروز‌رسانی می‌کنیم.
<?php
$stmt = $pdo->query(
"SELECT account_id FROM Products WHERE product_id = 123");
$row = $stmt->fetch();
$contact_list = $row['account_id' ];


// change list in PHP code
$value_to_remove = "34";
$contact_list = split(",", $contact_list);
$key_to_remove = array_search($value_to_remove, $contact_list);
unset($contact_list[$key_to_remove]);
$contact_list = join(",", $contact_list);
$stmt = $pdo->prepare(
"UPDATE Products SET account_id = ?
WHERE product_id = 123");
$stmt->execute(array($contact_list));

 اعتبارسنجی شناسه‌ی  محصولات 
به دلیل آنکه نوع فیلد account_id،varchar  می‌باشد احتمال این وجود دارد داده‌ای نامعتبر وارد نماییم چون پایگاه داده‌ها هیچ عکس العملی یا خطایی را نشان نمی‌دهد، فقط از لحاظ معنایی دچار مشکل می‌شویم. در نمونه زیر banana یک داده‌ی غیر معتبر می‌باشد و ارتباطی با شناسه‌ی کاربران ندارد. 
INSERT INTO Products (product_id, product_name, account_id)
VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34,banana' );

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

محدودیت طول لیست 
در زمان طراحی جدول، برای هر یک از فیلد‌ها باید حداکثر طولی را قرار دهیم. به عنوان نمونه ما اگر (varchar(30 در نظر بگیریم بسته به تعداد کاراکتر‌هایی که داده‌های ورودی ما دارند می‌توانیم جدول را پر‌نماییم. اسکریپت زیر شناسه‌ی کاربرانی که محصولی با شناسه‌ی 123 را خریده‌اند، ویرایش می‌کند. با این تفاوت که با توجه به محدودیت لیست، در نمونه‌ی اولی شناسه‌ی کاربران دو کاراکتری بوده و 10داده بعنوان ورودی پذیرفته است در حالیکه در نمونه‌ی دوم بخاطر اینکه شناسه‌ی کاربران شش کاراکتری می‌باشد فقط 4 شناسه می‌توانیم وارد نماییم.
UPDATE Products SET account_id = '10,14,18,22,26,30,34,38,42,46'
WHERE product_id = 123;  
UPDATE Products SET account_id = '101418,222630,343842,467790'
WHERE product_id = 123;

3.1 موارد تشخیص این Antipattern:
سؤالات زیر نشان می‌دهند که Jaywalking antipattern  مورد استفاده قرار گرفته است:
• حداکثر پذیرش این لیست برای داده ورودی چه میزان است؟
• چه کاراکتری هرگز در داده‌های ورودی این لیست ظاهر نمی‌شود؟
4.1  مواردی که استفاده از این Antipattern مجاز است:
دی نرمال کردن کارایی را افزایش می‌دهد. ذخیره کردن شناسه‌ها در یک لیست که با کاما از یکدیگر جدا شده‌اند نمونه بارزی از دی نرمال کردن می‌باشد. ما زمانی می‌توانیم از این مدل استفاده نماییم که به جدا کردن شناسه‌ها از هم نیاز نداشته باشیم. به همین ترتیب، اگر در برنامه، شما یک لیست را از یک منبع دیگر با این فرمت دریافت نمایید، به سادگی آن را در پایگاه داده خود به همان فرمت بصورت کامل می‌توانید ذخیره و بازیابی نمایید و احتیاجی به مقادیر جداگانه ندارید. درهنگام استفاده از پایگاه داده‌های دی‌نرمال دقت کنید. برای شروع از پایگاه داده نرمال استفاده کنید زیرا به کدهای برنامه شما امکان انعطاف بیشتری می‌دهد و کمک می‌کند تا پایگاه داده‌ها تمامیت داده‌(Data integrity) را حفظ کند.
5.1  راه حل: استفاده کردن از جدول واسط
در این روش شناسه‌ی کاربران را در جدول جداگانهایی که هر کدام از آنها یک سطر را به خود اختصاص داده اند، ذخیره می‌نماییم.
CREATE TABLE Contacts ( product_id BIGINT UNSIGNED NOT NULL,
account_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (product_id, account_id),
FOREIGN KEY (product_id) REFERENCES Products(product_id),
FOREIGN KEY (account_id) REFERENCES Accounts(account_id)
);

جدول Contacts یک جدول رابطه ایی بین جداول Products,Accounts

جدول Contacts یک جدول رابطه ایی بین جداول Products,Accounts 


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

ما براحتی می‌توانیم تمامی محصولاتی که مختص به یک کاربر هستند را بدست آوریم. در این شیوه خاصیت ایندکس بودن شناسه‌ی کاربران حفظ می‌شود به همین دلیل queryهای آن برای خواندن و بهینه کردن راحت‌تر می‌باشند. در این روش به کاراکتری برای جدا کردن ورودی‌ها از یکدیگر نیاز نداریم چون هر کدام از آنها در یک سطر جداگانه ثبت می‌شوند. برای ویرایش کردن کاربرانی که یک محصول را خریداری نموده اند، کافیست یک سطر از جدول واسط را اضافه یا حذف نماییم. درنمونه کد زیر، ابتدا در جدول Contacts کاربری با شناسه‌ی 34 که محصولی با شناسه‌ی 456 را خریداری کرده، درج شده است و در خط بعد عملیات حذف با شرط آنکه شناسه‌ی کاربر و محصول به ترتیب 34،456 باشد روی جدول Contacts اعمال شده است.

INSERT INTO Contacts (product_id, account_id) VALUES (456, 34);

DELETE FROM Contacts WHERE product_id = 456 AND account_id = 34;

ایجاد توابع تجمیعی

به عنوان نمونه در مثال زیر براحتی ما می‌توانیم تعداد محصولات در هر حساب کاربری را بدست آوریم:

SELECT account_id, COUNT(*) AS products_per_account
FROM Contacts
GROUP BY account_id;

اعتبارسنجی شناسه  محصولات 

از آنجاییکه مقادیری که در جدول قرار دارند کلید خارجی می‌باشند میتوان صحت اعتبار آنها را بررسی نمود. بعنوان مثال Contacts.account_id به Account.account_id  اشاره می‌کند. در ضمن برای هر فیلد نوع آن را می‌توان مشخص کرد تا فقط همان نوع داده را بپذیرد.

محدودیت طول لیست 

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


مزیت‌های دیگر استفاده از جدول واسط

کارایی روش دوم بهتر از حالت قبلی می‌باشد چون ایندکس بودن شناسه‌ها حفظ شده است. همچنین براحتی میتوانیم فیلدی را به این جدول اضافه نماییم مثلا (time, date… ) 

  


[1] Intersection Table  
[2] Aggregate  
مطالب
تهیه بک آپ‌های خودکار از SQL Server Express

SQL Server express edition نگارش مجانی و ساده شده‌ی اس کیوال سرور است. این نگارش مجانی فاقد SQL Server agent برای زمان بندی انجام امور تکراری، برای مثال تهیه بک آپ‌های خودکار است. این مورد در کل ایرادی محسوب نمی‌شود زیرا می‌توان این عملیات را با استفاده از سیستم استاندارد scheduled tasks ویندوز نیز پیاده سازی کرد.
برنامه خط فرمان سورس بازی به نام ExpressMaint موجود است که می‌تواند از دیتابیس‌های اس کیوال سرور اکسپرس (و غیر اکسپرس) بک آپ تهیه کند. فقط کافی است این برنامه را به عنوان یک scheduled task ویندوز معرفی کنیم تا در زمان‌های تعیین شده در مکان‌هایی مشخص، بک آپ تهیه کند. همچنین این برنامه فایل‌های بک آپ تهیه شده را نیز تعیین اعتبار می‌کند.
با پارامترهای خط فرمان آن در این‌جا می‌توانید آشنا شوید. خلاصه کاربردی آن را به صورت چند دستور در ادامه مرور خواهیم کرد.

الف) یک فایل bat را با محتوای زیر درست کنید :

C:\backup\expressmaint.exe -S (local)\sqlexpress -D ALL_USER -T DB -R C:\backup -RU WEEKS -RV 2 -B C:\backup -BU DAYS -BV 2 -V -C
توضیحات: در این فایل bat ، مسیر فایل اجرایی برنامه حتما باید دقیقا ذکر شود و گرنه scheduled task ویندوز درست کار نخواهد کرد. همچنین instance اس کیوال سرور اکسپرس در اینجا (local)\sqlexpress فرض شده است. این دستور از تمامی دیتابیس‌های غیرسیستمی در مسیر C:\backup بک آپ می‌گیرد (به ازای هر دیتابیس یک پوشه مجزا درست خواهد کرد و هر فایل را بر اساس تاریخ و ساعت مشخص می‌سازد). همچنین لاگ عملیات را نیز در همان پوشه تهیه می‌کند (نتیجه اعتبار سنجی صورت گرفته بر روی بک آپ‌های تهیه شده در این فایل‌ها ثبت می‌شود). مطابق پارامترهای بکار گرفته شده، بک آپ‌های قدیمی‌تر از دو روز به صورت خودکار حذف شده و لاگ فایل‌ها به مدت 2 هفته نگهداری می‌شوند.

ب) برای اجرای زمان بندی شده‌ی این فایل bat تهیه شده، دستورات زیر را در خط فرمان اجرا کنید (فرض بر این است که فایل bat تهیه شده در مسیر مشخص شده C:\backup\backup.bat قرار دارد) :

AT 23:30 /EVERY:m,t,w,th,f,s,su C:\backup\backup.bat
AT 11:30 /EVERY:m,t,w,th,f,s,su C:\backup\backup.bat
به این صورت ویندوز هر روز، دوبار در طول روز از کلیه دیتابیس‌ها به صورت خودکار بک آپ تهیه می‌کند.

روشی که در این‌جا ذکر شد منحصر به نگارش express نیست و با کلیه نگارش‌های SQL Server سازگار است.


اشتراک‌ها
دریافت SQL Server 2014 Service Pack 2

Microsoft SQL Server 2014 service packs are cumulative updates and upgrade all editions and service levels of SQL Server 2014 to SP2. This service pack contains up to and including SQL Server 2014 SP1 Cumulative Update 7 (CU7).  

دریافت SQL Server 2014 Service Pack 2
اشتراک‌ها
امکانات بیشتر و بهینه تر برای EF

این ابزار در نسخه رایگان شامل قابلیت‌های batch، کش، بهینه سازی و ... میباشد.

EF Include

var orders = ctx.Orders
                .Where(x => x.OrderId == myOrderID) // 1 orders, 20 columns
                .Include(x => x.Items) // 20 items, 10 columns
                .Include(x => x.DeliveredItems) // 10 items, 10 columns
                .ToList();

// return 20 + 10  = 30 rows
// return 20 + 10 + 10 = 40 columns
// total: 30 rows * 40 columns = 1200 cells transferred

EF+ IncludeOptimized

// SELECT * FROM Order WHERE....
// SELECT * FROM OrderItem WHERE EXISTS (/* previous query */) AND ...
// SELECT * FROM DeliveryItems WHERE EXISTS (/* previous query */) AND ...

var orders = ctx.Orders
                .Where(x => x.OrderId == myOrderID) // 1 orders, 20 columns
                .IncludeOptimized(x => x.Items) // 20 items, 10 columns
                .IncludeOptimized(x => x.DeliveredItems) // 10 items, 10 columns
                .ToList();

// return 1 row * 20 columns = 20 cells
// return 20 rows * 10 columns = 200 cells
// return 10 rows * 10 columns = 100 cells
// total: 20 + 200 + 100 = 320 cells transferred


امکانات بیشتر و بهینه تر برای EF
مطالب
کپی کردن ساختار و داده‌های یک جدول از یک دیتابیس به دیتابیسی دیگر
در مواقعی ممکن است نیاز داشته باشیم که جدول یا جدول‌هایی از یک پایگاه داده را به یک پایگاه داده دیگر انتقال دهیم. در این مقاله قصد داریم روند انجام این کار را هم به صورت کوئری و هم به صورت ویزارد(گرافیکی) انجام دهیم.

برای شروع کار ابتدا دو دیتابیس به اسم‌های databasefrm و databaseto می‌سازیم. دیتابیس databasefrm شامل یک جدول به اسم emp با سه فیلد ID,Name,Address می‌باشد. قصد داریم جدول tmp از دیتابیس databasefrm را به دیتابیس databaseto انتقال دهیم. برای انجام این کار، یکی از روش‌های زیر را استفاده خواهیم کرد:

روش 1 : استفاده از کوئری

ساختار کلی انجام این عمل به صورت زیر خواهد بود:
Select * into DestinationDB.dbo.tableName from SourceDB.dbo.SourceTable
مثال :
select * into databaseto.dbo.emp from databasefrm.dbo.Emp
با اجرای دستور فوق یک کپی از جدول emp به همراه تمامی داده‌های آن به دیتابیس databaseto منتقل و ایجاد می‌شوند. اگر بخواهیم تمامی ایندکس‌ها، تریگر‌ها و قید‌ها (Constraint) نیز منتقل شوند، برای اینکار نیاز به تولید یک اسکریپت خواهد بود (در ادامه).

حال اگر بخواهیم یک کپی از  جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود  :
select * into newtable from SourceTable
که نمونه ای از آن برای دیتابیس ما به صورت زیرخواهد بود :
 select * into  emp1 from emp

می‌توانیم فقط فیلدهایی مشخص را به جدول دیگر کپی کنیم. برای انجا این کار کافیست به جای *  اسم فیلد‌های مورد نیاز را نوشت که ساختار دستوری آن به صورت زیر است :
select col1, col2 into <destination_table> from <source_table>
که برای مثال ما به صورت زیر خواهد بود :
select Id,Name into databaseto.dbo.emp1 from databasefrm.dbo.Emp

بعد از اجرای کوئری فوق نتیجه به صورت زیر خواهد بود :

کد فوق باعث کپی کردن فیلد‌های Id,Name شده است.

اگر بخواهیم فقط ساختار جدول را کپی کنیم روند کار به صورت زیر خواهد بود :

select *into <destination_database.dbo.destination table> from _
<source_database.dbo.source table> where 1 = 2
که نمونه ای از آن برای مثال ما به صورت زیر خواهد بود :
select * into databaseto.dbo.emp from 
databasefrm.dbo.emp where 1 = 2
کاربرد where در دستور فوق برای این است که عنوان فیلد‌ها را بگیریم و در جدول دیگری ذخیره کنیم.

نکته: هر وقت نیاز بود که فقط فیلد‌های یک جدول را دریافت کنید، می‌تواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور  Select می‌باشد. نمونه‌ای از دستور فوق:
select top(0) * into databaseto.dbo.emp from 
databasefrm.dbo.emp
همانطور که مشاهده می‌کنید دیگر در دستور فوق خبری از where نیست.

روش 2: ویزارد

جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده می‌کنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجره‌ی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب می‌کنیم (databaseto).
5- در پنجره‌ی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.

روش 3 : تولید اسکریپت

 
با استفاده از دو روش فوق فقط می‌توانستیم ساختار جداول و داده‌های آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... می‌بایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
Right click on datbase >>Task>>Generate script>>next

انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (databaseto) اجرا می‌کنیم.

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

یکی از قابلیت‌های جالب NHibernate امکان تعریف فیلدها به صورت پویا هستند. به این معنا که زیرساخت طراحی یک برنامه "فرم ساز" هم اکنون در اختیار شما است! سیستمی که امکان افزودن فیلدهای سفارشی را دارا است که توسط برنامه نویس در زمان طراحی اولیه آن ایجاد نشده‌اند. در ادامه نحوه‌ی تعریف و استفاده از این قابلیت را توسط Fluent NHibernate بررسی خواهیم کرد.

در اینجا کلاسی که قرار است توانایی افزودن فیلدهای سفارشی را داشته باشد به صورت زیر تعریف می‌شود:
using System.Collections;

namespace TestModel
{
public class DynamicEntity
{
public virtual int Id { get; set; }
public virtual IDictionary Attributes { set; get; }
}
}

Attributes در عمل همان فیلدهای سفارشی مورد نظر خواهند بود. جهت معرفی صحیح این قابلیت نیاز است تا نگاشت آن‌را از نوع dynamic component تعریف کنیم:
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;

namespace TestModel
{
public class DynamicEntityMapping : IAutoMappingOverride<DynamicEntity>
{
public void Override(AutoMapping<DynamicEntity> mapping)
{
mapping.Table("tblDynamicEntity");
mapping.Id(x => x.Id);
mapping.IgnoreProperty(x => x.Attributes);
mapping.DynamicComponent(x => x.Attributes,
c =>
{
c.Map(x => (string)x["field1"]);
c.Map(x => (string)x["field2"]).Length(300);
c.Map(x => (int)x["field3"]);
c.Map(x => (double)x["field4"]);
});
}
}
}
و مهم‌ترین نکته‌ی این بحث هم همین نگاشت فوق است.
ابتدا از IgnoreProperty جهت ندید گرفتن Attributes استفاده کردیم. زیرا درغیراینصورت در حالت Auto mapping ، یک رابطه چند به یک به علت وجود IDictionary به صورت خودکار ایجاد خواهد شد که نیازی به آن نیست (یافتن این نکته نصف روز کار برد ....! چون مرتبا خطای An association from the table DynamicEntity refers to an unmapped class: System.Collections.Idictionary ظاهر می‌شد و مشخص نبود که مشکل از کجاست).
سپس Attributes به عنوان یک DynamicComponent معرفی شده است. در اینجا چهار فیلد سفارشی را اضافه کرده‌ایم. به این معنا که اگر نیاز باشد تا فیلد سفارشی دیگری به سیستم اضافه شود باید یکبار Session factory ساخته شود و SchemaUpdate فراخوانی گردد و خوشبختانه با وجود Fluent NHibernate ، تمام این تغییرات در کدهای برنامه قابل انجام است (بدون نیاز به سر و کار داشتن با فایل‌های XML نگاشت‌ها و ویرایش دستی آن‌ها). از تغییر نام جدول که برای مثال در اینجا tblDynamicEntity در نظر گرفته شده تا افزودن فیلدهای دیگر در قسمت DynamicComponent فوق.
همچنین باتوجه به اینکه این نوع تغییرات (ساخت دوبار سشن فکتوری) مواردی نیستند که قرار باشد هر ساعت انجام شوند، بنابراین سربار آنچنانی را به سیستم تحمیل نمی‌کنند.
   drop table tblDynamicEntity

create table tblDynamicEntity (
Id INT IDENTITY NOT NULL,
field1 NVARCHAR(255) null,
field2 NVARCHAR(300) null,
field3 INT null,
field4 FLOAT null,
primary key (Id)
)

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

چند مثال جهت کار با این فیلدهای سفارشی یا پویا :

نحوه‌ی افزودن رکوردهای جدید بر اساس خاصیت‌های سفارشی:
//insert
object savedId = 0;
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var obj = new DynamicEntity();
obj.Attributes = new Hashtable();
obj.Attributes["field1"] = "test1";
obj.Attributes["field2"] = "test2";
obj.Attributes["field3"] = 1;
obj.Attributes["field4"] = 1.1;

savedId = session.Save(obj);
tx.Commit();
}
}

با خروجی
INSERT
INTO
tblDynamicEntity
(field1, field2, field3, field4)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'test1' [Type: String (0)], @p1 = 'test2' [Type: String (0)], @p2 = 1
[Type: Int32 (0)], @p3 = 1.1 [Type: Double (0)]

نحوه‌ی کوئری گرفتن از این اطلاعات (فعلا پایدارترین روشی را که برای آن یافته‌ام استفاده از HQL می‌باشد ...):
//query
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
//using HQL
var list = session
.CreateQuery("from DynamicEntity d where d.Attributes.field1=:p0")
.SetString("p0", "test1")
.List<DynamicEntity>();

if (list != null && list.Any())
{
Console.WriteLine(list[0].Attributes["field2"]);
}
tx.Commit();
}
}
با خروجی:
    select
dynamicent0_.Id as Id1_,
dynamicent0_.field1 as field2_1_,
dynamicent0_.field2 as field3_1_,
dynamicent0_.field3 as field4_1_,
dynamicent0_.field4 as field5_1_
from
tblDynamicEntity dynamicent0_
where
dynamicent0_.field1=@p0;
@p0 = 'test1' [Type: String (0)]

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

نحوه‌ی به روز رسانی و حذف اطلاعات بر اساس فیلدهای پویا:
//update
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
entity.Attributes["field2"] = "new-val";
tx.Commit(); // Persist modification
}
}
}

//delete
using (var session = sessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
var entity = session.Get<DynamicEntity>(savedId);
if (entity != null)
{
session.Delete(entity);
tx.Commit();
}
}
}

مطالب
تغییر نام پایگاه داده و فایل هایش در SQL Server 2012
بعضی وقت‌ها به هر علتی لازم است پایگاه داده و فایل هایش را تغییر نام دهیم. اگر در اینترنت جستجو کنیم روش‌های مختلفی برای تغییر نام مثل تغییر  با Management Studio  یا T-SQL یا روش‌های دیگری یافت می‌شود. اما اکثرا در بین انجام به مشکلی غیر قابل پیش بینی بر می‌خوریم. پایگاه داده در حالت آفلاین یا Pending قرار گرفته و به خطا‌های نا مفهومی بر می‌خوریم. حالا باید دوباره کلی جستجو کنیم تا مشکل بوجود آمده را حل نمائیم.

بهترین روش تغییر نام پایگاه داده
بهترین روش استفاده از قابلیت Copy Database خود SQL Server است است که به راحتی این کار را برای ما انجام می‌دهد.
  1. بر روی پایگاه داده مورد نظر راست کلیک کرده و از گزینه Tasks گزینه Copy Database را انتخاب کنید.
  2. پس از ظاهر شدن پنجره کپی گزینه Next را انتخاب و در مرحله مبدا و مقصد، سرور جاری را انتخاب کنید و به مرحله بعد بروید.
    این برای زمانی است که شما می‌خواهید پایگاه داده را در سرور دیگری کپی نماید
  3. در پنجره Transfer Method دو روش Detach and Attach  و استفاده از SQL Management Object  وجود دارد که با همان روش اول به مرحله بعد بروید

  4.  در مرحله بعد نام پایگاه داده شما انتخاب شده به مرحله بعد بروید.
  5. مرحله بعد پیکریندی پایگاه داده مقصد می‌باشد که نام و مسیر پایگاه داده جدید را می‌توانید مشخص نمایید.

  6. این عملیات با SQL Server Agent صورت می‌پذیرد به همین خاطر Agent می‌بایست نصب و Start شده باشد.
  7. با انتخاب گزینه Next مراحل بعد را رد کرده تا عملیات آغاز شود.
  8. در مرحله آخر پایگاه داده قبلی را حذف نمایید.