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();
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`) )
SELECT *, CONCAT(first_name, '', last_name) AS full_name FROM Users;
ALTER TABLE Users ADD COLUMN full_name TEXT GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name))
ALTER TABLE Users ADD COLUMN full_name TEXT GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) STORED
- 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 را نخواهند داشت.
ALTER TABLE productMetadata ADD COLUMN id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(data, '$.id'))) STORED NOT NULL
SELECT data ->> "$.description.shortDescription" FROM productMetadata WHERE id = 1;
همانطور که مشاهده میکنید مقدار type به ALL تنظیم شدهاست؛ همچنین مقدار rows نیز تعداد ردیفهای جدول است که در اینجا ۱۳ ردیف دیتا را داریم. قاعدتاً با اضافه شدن دیتای جدید به جدول، جستجو نیز به مراتب کندتر خواهد شد. بنابراین با اضافه کردن ایندکس میتوانیم مشکل این کند بودن را رفع کنیم. به همین جهت در ادامه یک ایندکس را براساس ستون id که یک generated column است ایجاد خواهیم کرد:
CREATE INDEX idx_json_data ON productMetadata (id);
اکنون اگر یکبار دیگر کوئری قبلی را اجرا کنیم، خواهیم دید که تعداد rows به ۱ و همچنین type به ref ست شدهاند:
SQL Antipattern #1
بخش اول : 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);
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[[:>:]]' ;
SELECT * FROM Products AS p JOIN Accounts AS a ON p.account_id REGEXP '[[:<:]]' || a.account_id || '[[:>:]]' WHERE p.product_id = 123;
SELECT product_id, LENGTH(account_id) - LENGTH(REPLACE(account_id, ',' , '' )) + 1 AS contacts_per_product FROM Products;
UPDATE Products SET account_id = account_id || ',' || 56 WHERE product_id = 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));
INSERT INTO Products (product_id, product_name, account_id) VALUES (DEFAULT, 'Visual TurboBuilder' , '12,34,banana' );
محدودیت طول لیست
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;
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
بدست آوردن محصولات برای کاربران و موارد مربوط به آن
ما براحتی میتوانیم تمامی محصولاتی که مختص به یک کاربر هستند را بدست آوریم. در این شیوه خاصیت ایندکس بودن شناسهی کاربران حفظ میشود به همین دلیل 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… )
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 تهیه شده، دستورات زیر را در خط فرمان اجرا کنید (فرض بر این است که فایل 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 سازگار است.
امکانات بیشتر و بهینه تر برای 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
کار بر روی C# 11.0 شروع شد
برای شروع کار ابتدا دو دیتابیس به اسمهای 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
حال اگر بخواهیم یک کپی از جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود :
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
نکته: هر وقت نیاز بود که فقط فیلدهای یک جدول را دریافت کنید، میتواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور Select میباشد. نمونهای از دستور فوق:
select top(0) * into databaseto.dbo.emp from databasefrm.dbo.emp
روش 2: ویزارد
جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده میکنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجرهی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب میکنیم (databaseto).
5- در پنجرهی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.
روش 3 : تولید اسکریپت
با استفاده از دو روش فوق فقط میتوانستیم ساختار جداول و دادههای آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... میبایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (databaseto) اجرا میکنیم.
و در پایان نهایت تشکر را از تمام عزیزان و دوستان نویسندهی سایت دارم. امیدوارم در سال 94 شاهد موفقیتهای خوبی در حوزهی نرم افزار باشیم.
یکی از قابلیتهای جالب 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();
}
}
}
- بر روی پایگاه داده مورد نظر راست کلیک کرده و از گزینه Tasks گزینه Copy Database را انتخاب کنید.
- پس از ظاهر شدن پنجره کپی گزینه Next را انتخاب و در مرحله مبدا و مقصد، سرور جاری را انتخاب کنید و به مرحله بعد بروید.
این برای زمانی است که شما میخواهید پایگاه داده را در سرور دیگری کپی نماید - در پنجره Transfer Method دو روش Detach and Attach و استفاده از SQL Management Object وجود دارد که با همان روش اول به مرحله بعد بروید
- در مرحله بعد نام پایگاه داده شما انتخاب شده به مرحله بعد بروید.
- مرحله بعد پیکریندی پایگاه داده مقصد میباشد که نام و مسیر پایگاه داده جدید را میتوانید مشخص نمایید.
- این عملیات با SQL Server Agent صورت میپذیرد به همین خاطر Agent میبایست نصب و Start شده باشد.
- با انتخاب گزینه Next مراحل بعد را رد کرده تا عملیات آغاز شود.
- در مرحله آخر پایگاه داده قبلی را حذف نمایید.