# برنامه نویسی با محصولات مایکروسافت > برنامه نویسی مبتنی بر Microsoft .Net Framework > VB.NET > آموزش: برنامه نویسی پایگاه داده به صورت گام به گام

## فرید نجفلو

فهرست مطالب : همین صفحه پست دوم

توجه:

فعلا تاپیک در حال ایجاد است لطفا تا تکمیل مراحل اولیه ایجاد تاپیک از ارسال هرگونه پستی خودداری نمایید


Busy.png 

اگر از بین دوستان و اساتید کسانی علاقمند به شرکت و پیشبرد موضوع تاپیک بودند می توانند با پیام خصوصی با بنده در ارتباط باشند


در ضمن چون ممکن است به دلیل مشغله یا هر دلیل دیگری کامل شدن تاپیک طولانی شود از دوستان خواهشمندم از هرگونه بحث انحرافی و متلک پرانی خوداری کنند

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






باسلام و عرض خسته نباشید!

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

تصمیم بر آن است در هر مرحله پروژه نمونه طبق توضیحات داده شده تکمیل و جهت دانلود گذاشته شود



نکته:

ابزار طراحی مورد استفاده:

Visual Studio *2010*
Dot Net FramWork *3.5*--جهت توانایی تبدیل به VS 2008
SQL Server *2005* (جهت قابلیت اجرا در VS2008و در نظر گرفتن همه دوستان)
در صورت داشتن امکانات و توانایی اگر اقدام به طراحی گزارش شود از *Crystal Report 2010* استفاده خوهد شد

در صورت هرگونه تغییر ابزار ، در همین قسمت به اطلاع خواهد رسید


هینجا هم جا دارد از دوست خوبم *ali190* بدلیل کمک های شایان در پیشبرد اهداف تاپیک از صمیم قلب از تشکر کنم


*ضمیمه اصلاح شد 18/01/91 ساعت 20:00 اعمال تغییرات تا* *پست شماره 21*

----------


## فرید نجفلو

*فهرست مطالب:*

*کاربران همکار*
*پرسش و پاسخ*
*سایر تاپیک های مفید ما*
*اخبار و اطلاعیه ها*
*سخن اول*
*طرح مثال عملی*
*ایجاد اشیاء پایگاه داده*
*اصطلاحات و نام ها مورد استفاده در برنامه نویسی*
*رشته اتصال(Connection String) چیست؟*
*قوانینی را برای عدم گسستگی کد های خود داشته باشید*
*ایجاد پروژه و کلاس های اولیه*
*ایجاد توابع و متغیر های عمومی و زیر بنایی*
*رشته اتصال(Connction String)و اتصال (Connection) را چگونه و کجا تعریف و مقدار دهی کنیم؟*
*ایجاد لایهADL کار با داده ها (Data Access Layer)*
*ساخت فرم دریافت رشته اتصال(اولین فرم کاربردی در لایه PL)*
*ایجاد فرم اعتبار سنجی و ورود کاربر*
*ذخیره رشته اتصال*
*مدیریت کاربران بخش اول:ایجاد،ویرایش،حذف کاربران لایه رابط کابری(PL)*
*مدیریت کاربران بخش دوم:ایجاد،ویرایش،حذف کاربران لایه میانی و لایه دسترسی به داده(DAL,BLL)*

----------


## فرید نجفلو

*کاربران همکار:*
همین جا از دوستانی که مرا در تکمیل این تاپیک یاری می کنند نهایت تشکر را می کنم:

*ali190*

----------


## فرید نجفلو

*پرسش و پاسخ:*
(پرسش هایی که از طریق پیغام خصوصی دریافت می شوند در صورت نیاز در این قسمت مطرح و پاسخ داده خواهند شد)




> *ali190*:من پروژه رو دانلود کردم ،اسکریپت دیتابیس رو هم اجرا کردم  ، منتها روابط میان جداول رو در بخش دیاگرام نمیبینم!


شما باید در SSMS یک دیاگرام جدید ایجاد و از پنجره Add Table کلیه جدول ها را اضافه کنید تا روابط موجود نمایش داده شود

----------


## فرید نجفلو

*سایر تاپیک های مفید ما:*

*پشتیبان گیری و بازیابی(بدون دستورات SQL ، درصد پیشرفت و...)*

*سورس:ProgressBar(درصد پیشرفت کار) در پشتیبان گیری SQl Server بدون استفاده از SMO*

*سورس:ذخیره ، بازیابی و چاپ عکس در پایگاه داده(دانلود کنید!)* 
*سورس:ذخیره و بازیابی فایل در SQL Server و Access با درصد پیشرفت(دانلود کنید!)*

----------


## فرید نجفلو

*اخبار و اطلاعیه ها:*

*اولین سورس مثال عملی در تاریخ 08/01/91 به پست اول اضافه شد!*

----------


## فرید نجفلو

*سخن اول:* 

 هر کسی هر نظر و پیشنهادی میده باتوجه به میزان دانش ، تجربه و ... خودشه و هرکس هر نظری داد کسی حق توهین ، تمسخر و بی ادبی به  اون رو نداره(تا این تاپیک هم به سرنوشت بعضی از تاپیک ها که فرهنگ ما رو هم زیر سوال برده نشه!) ولی باید به صورت کاملا مودبانه انتقادات و پیشنهادات گفته بشه  تا موجب پیشرفت و دانش آموزی ما بشه 

دوستانی هم که نظر و پیشنهاد می دن سعی کنن اگه در مقوله اشی دانش کافی نداشتن از از اظهار نظر فنی (که ممکنه درست نباشه) خود داری کنن 
از دوستان هم درخواست دارم هرگونه نظر و پیشنهادی دارند ابتدا آن را از طریق پیام خصوصی به ایشان یا بنده اطلاع و پس از آن اقدام به درج در تاپیک نمایند تا از منحرف شدن تاپیک جلوگیری شود

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

*نکته ای درباره روند کار:*

همان طور دوستان می دانند بعد از به وجود آمدن اشیا DataSet  و بعد ها (VS2008 و فریم ورک 3.5) با روی کار آمدن تکنولوژی منحصر به فرد *LINQ* کم کم استفاده از روش های سنتی ADO.NET و TSQL رنگ باخته و رو به منسوخ شدن دارند

با این حال طبق قرائن و شواهد(سوالات و تاپیک ها) دوستان فعال در سایت و با تعمیم بیشتر ، ما و هم وطنان ارجمند همچنان بر استفاده از TSQL پا فشاری داریم(در واقع ما همیشه سعی داریم چند سالی از پیشرفت و تنکنولوژی ها عقب باشیم!!!)

به همین دلیل برای جا افتادن تکنولوژی ها روز مطالب ارائه شده با همان روش DataSet انجام شده و در صورتی که امکانات محیا و عمر و توانایی باقی باشد با TSQL و ADO.Net و سپس وقتی  TSQL جا افتاد  به وسیله  LINQ حل خواهد شد
(پس لطفا درباره اینکه روش های بهتر وجود دارد و... بحث را به انحراف نکشید و بدانید که ما خود به این قضیه واقف هستیم ولی به دلیل مذکور روش ها با اولویت بحث می شوند)

بعد از اتمام مسائل اصلی پایگاه داده وارد بحث های عمیق تر و پیشرفته تر خواهیم شد از جمله:
بهینه سازی ، پیشگیری از خطا ها ، حفره های مخوف SQL Injection ، برنامه شبکه ، ایجاد دیتابیس در زمان اجرا (ران تایم) به همراه درصد پیشرفت مربوطه ، کار با چند پایگاه داده (سوئیچ) ، نهان گذاری (Cashing)  و ... 

در ضمن مطالب و مثال از SQLServer می باشد چون نمی شود از هر دو نوشت و اینکه تقریبا هرشئی در SqlClient معادلی درOleDb  به همان اسم دارد


در آخر همه دوستان را به رعایت قوانین مکتوب و عرفی سایت و رفتار های انسانی دعوت می کنم

----------


## فرید نجفلو

*طرح مثال عملی:*
مثال زیر رو جهت ادامه مطلب مطرح و طبق آن پایگاه داده خود را طراحی و به تکمیل پروژه خود می پردازیم

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

اجزا موثر بر پروژه:

*- کالاها:
کالاها مهمترین جز هستند که هر کدام از آنها نیز به نوبه خود در *گروه کالا* قرار می گیرند
اطلاعات هر کالا متشکل شده از : کد کالا ، نام کالا ، گروه کالا ، موجودی فعلی ، حداکثر موجودی قابل انبار 
لازم به ذکر است کالا های مشابه بدون تفاوت در نظر گرفته می شوند یعنی کالای تحول گرفته شده الف از مو سسه 1 با کالای الف موسسه 2 تفوتی نخواهد داشت!

*- انبارها :
این بخش دارای تعدای انبار می باشد که کالا را در آنها نگه داری می کند، تعدادی از انبار ها متعلق به خود و و تعدادی نیز استیجاری می باشد. در نتیجه تعداد انبار ها ثابت نبوده و ممکن است طی دوره مالی کم و زیاد شوند هر انبار ظرفیت ذخیره سازی خود را داشته و همچنین هر انباری دارای امکاناتی می باشد که فقط توانایی انبار کردن گروه های خاصی از کالا ها(و نه همه گروه ها) دارا می باشد (برای مثال انباری که دارای سردخانه نباشد گروه کالا های فاسد شدنی را نمی تواند انبار کند)
اطلاعات انبار ها: کد انبار ،نام انبار،حجم ذخیره سازی ، گروه کالا های قابل انبار ، آدرس ، شماره تلفن
نکته:حجم انبار را جهت عدم پیچیدگی مثال عددی بدون واحد در نظر خواهیم گرفت یعنی کالای به ندازه 2 متر به اندازه کالایی به به وزن 2 کیلو گرم از ظرفیت انبار خواهد کاست

*- مشتری ها :
همان طرف های مقابل می باشند که کالا ها را از آنها دریافت و به آنها تحویل می دهیم
اطلاعات هر مشتری: کد مشتری،نام مشتری ، زمینه فعالیت ، آدرس دفتر ،شماره تماس مدیریت ، آدرس گارگاه (جهت ارسال کالا) ، شماره تلفن کارگاه

*- کارکنان:
موسسه داری چند کارمند می باشد که هر کدام قسمتی از کار را بر عهده دارند و مدیریت نیز سیاست خود را چنین در نظر گرفته کارکنان در بخش های غیر مرتبط به فعالیت نپردازند
در ضمن ممکن است از مدیران یا مسولان موسسه در نظر داشته باشند از طریق شبکه کامپیوتری موسسه به اطلاعات دستیابی و بر روند اجرا و آمارها نظارت داشته باشند!
اطلاعات کارکنان: نام کاربری ، کلمه عبور ، نام ، نام خانوادگی، تاریخ ایجاد کاربر، آخرین ورود به سیستم ، مجوز های دسترسی

*- بخش آماری:
موجودیت فیزیکی نبوده و به این معنی است که سیستم باید توانایی آمار گیری از کالا ها ، انبار ها و مشتریان را داشته باشد
از جمله:مقدار کل موجود از هر کالا ، مقدار کل وارد شده مقدار کل صادر شده ، مقدار هر کالا در هر انبار ، مقدار دریافتی هر کلا از هر مشتری ، مقدار فرستاده شده از هر کالا به هر مشتری ، مقدار مجاز قابل ارسال به هر مشتری از هر کالا و...


با در نظر گرفتن همین مثال و مفروضات به طراحی پایگاه داده خواهیم پرداخت

----------


## فرید نجفلو

*ایجاد اشیاء پایگاه داده:*


طبق اطلاعاتی که در اختیار داریم (از مثال مذکور) هدف نهایی ما رسیدن به ترکیبی از اشیائ با اجزا و روابط زیر(تصاویر) خواهد بود(در حالت اکسپلورر گره های غیر مفید حذف شده است):

 WorkWithDbDiagram.jpg 
ضمیمه 84726 

WorkWithDbObjectsExplorer.jpgهمانطور که می بینید نام هر جدول وظیفه خود را توضیح می دهد(نام جداول فارسی نوشته شده است تا قابل فهم تر باشد)
فقط ممکن است جدول tblDbVer کمی به نظر مبهم بیرسد
همان طور که در ادامه و در هنگام کد نویسی خواهید دید از طریق این جدول اقدام به شناسایی و اعتبار سنجی پایگاه داده جهت اتصال به آن استفاده خواهد شد! 
جهت ایجاد اشیا (یا در اصل جداول و ارتباطات بین آنها) پایگاه داده ابتدا SSMS(*S*QL *S*erver *M*anagment *S*tudio) i رو باز کنید (در صورتی ندارید می توانید نسخه Express را برای 2005 و 2008 به صورت رایگان دانلود کنید)

پس از ظاهر شدن پنجره Connect To Server ، سرور خود را (برای مثال SQLEXPRESS) انتخاب کنید

ایجاد پایگاه داده جدید:

از سمت چپ Object Explorer (تصویر دوم بالا) را باز و روی گره Databases کلیک راست و گزینه New Database را انتخاب کنید
از پنجره باز شده در کادر Database name نام پایگاه داده (WorkWithDb_Sample1) را وارد و دکمه OK را کلیک کنید

اگر مراحل بالا را درست طی کرده باشید پایگاه داده ما به سرور اضافه و آماده ایجاد جداول هستیم

ایجاد جداول:

جهت نمونه دو جدول tblAnbar و tblAnbarKalaGroup را به صورت گام به گام ایجاد می کنیم:

تحلیل:
هدف جدول:ذخیره اطلاعات مربوط به انبار ها
چه اطلاعاتی باید در این جدول ذخیره شوند( تعیین ستون ها یا فیلد ها)
طبق متن مثال حداقل به فیلد های زیر نیاز داریم


کد انبار =Code
نام انبار=Name
حجم ذخیره سازی =MaxCapacity
گروه کالا های قابل انبار=KalaGroup
آدرس=Address
شماره تلفن=Tell

همانطور که ملاحظه می شود می توان یک جدول به نام tblAnbar ایجاد و تمام اطلاعات را در آن قرار داد.
ولی در این میان مشکلی وجود دارد!
همانطور که از ظواهر هم پیداست یک انبار می تواند بیش از یک گروه از کالا ها را بپذیر!

اولین راه حل آن است که اطلاعات را در همین جدول ذخیره کنیم و به ازای هر گروه کالا اطلاعات انبار را تکرار و فقط گروه کالای مورد نظر را تغییر داد!

اما این کار برخلاف اصول طراحی پایگاه داده(*اصل نرمال سازی*) بوده و همچننین موجب افزونگی اطلاعات خواهد بود!

راه حل:

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

در نتیجه هر انبار می تواند فقط یک رکورد در جدول اصلی( tblAnbar ) و هیچ ، یک ، یا چند رکورد در جدول فرزند (tblAnbarKalaGroup ) داشته باشد

انتخاب کلید اصلی:

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

تعیین کلید اصلی جدول فرزند :

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

راه حل :

ما یک فیلد سوم به نام ID از نوع عددی ایجاد و به SQL Server اجازه می دهیم به صورت خودکار در هر ایجاد رکورد جدید مقدار آن را فزایش دهد(خاصیت* Identity*)

طبق قانون (SQL Server) این فیلد فقط می تواند افزایش یابد یعنی یک عدد فقط یک بار می تواند یک بار ظاهر شود اگر بزرگترین عدد موجود در این فیلد از جدل 3 باشد مقدار بعدی 4 خواهد بود حتی اگر اعداد 1و2 وجود نداشته باشند
پس ما همین فیلد را کلید اصلی جدول فرزند قرار می دهیم

درنتیجه ترکیب جداول به شکل زیر خواهد بود:
 
tblAnbar :

Code
Name
MaxCapacity
Address
Tell


tblAnbarKalaGroup :

ID
Code
KalaGroup


ایجاد جدول tblAnbar :

از SMSS و Object Explorer سپس گره Databases گر مربوط به WorkWithDb_Sample1 را باز و روی آیکن پوشه با نام Tables کلیک راست و گزینه New Table را انتخاب کنید
اکنون جدول در حالت طراحی ظاهر می شود
در زیر ستون Column Name نام فیلد و در Data Type نوع داده و جعبه انتخاب زیر ستون Allow Null نیز نشانگر قبول یا عدم قبول مقدار پوچ (NULL) می باشد

برای نمونه در سطر اول و ستون اول Code و در ستون دوم Int (همانطور که در تصویر دوم بالا و در زیر گره tblAnbar و مقابل مورد Code و داخل پرانتز قابل مشاهده است) در ستون سوم جعبه انتخاب را خالی کنید(در تصویر NotNull یعنی باید جعبه انتخباب باید خالی شود)
حالا فیلد های دیگر را طبق تصویر و با این توضیحات ایجاد کنید
باز همانطور که در تصویر می بینید در کنار نام Code تصویر یک کلید زرد رنگ وجود دارد که مؤید کلید اصلی بودن آن است جهت اعمال این خصوصیت رو سطر مربوط به Code راست کلیک و گزینه Set Primary Key را انتخاب کنید

کار شما با این جدول تمام شد حالا دکمه Save یا کلید های Ctrl+S را زده و در پنجره ظاهر شده tblAnbar را وارد و تایید کنید تا جدول با همین نام ذخیره شود

شما با موفقیت یک جدول جدید ایجاد کردید!

حالا جدول tblAnbarKalaGroup را با توضیحات بالا تا قبل از مرحله ذخیره پیش ببرید
یک تفاوت عمده این جدول با جدول قبلی در فیلد ID می باشد زیرا این فیلد را باید تنظیم نماییم تا به صورت خود کار افزایش یابد
نگران نباشید این کار فوق العاده آسان می باشد

سطر ID را انتخاب و از قسمت زیرین پنجره طراحی(Column Properties) از گره Table Designer و زیر گره Identity Specification مقدار Is Identity را از No به Yes تغییر دهید
حالا می توانید جدول را با نام tblAnbarKalaGroup ذخیره کنید!

حالا خود جهت تمرین هم که شده سایر جداول را ایجاد کنید

ایجاد رابطه ها:

همان طور که دید بعضی از جداول با یکدیگر رابطه دارند برای مثال دو جدول tblAnbar و tblAnbarKalaGroup داری رابطه پدر فرزندی و بر اساس کد انبار هستند(زمانی که جدول انبارها را دو تکه کردیم این رابطه ایجاد شد!)

چرا رابطه ها را ایجاد می کنیم؟

اولین دلیل و مهمترین آنها حفظ *جامعیت داده ها* است که یکی از اصول طراحی پایگاه داده می باشد
یعنی شما فرض کنید انباری ایجاد کرده اید که دو گروه کالا را می تواند قبول کند
پس ما برای این انبار یک رکورد در جدول tblAnbar و دو رکورد در جدول tblAnbarKalaGroup داریم
حالا کاربری اقدام به حذف رکورد انبار از جدول tblAnbar می کند اما رکورد های tblAnbarKalaGroup حذف نمی کند
پس الان ما چه داریم! دو رکورد در tblAnbarKalaGroup که پدری در tblAnbar ندارند(یتیم هستند!)
این یعنی نقض *اصل جامعیت!*

رابطه ایجاد شده چه کاری انجام می دهد؟

وقتی ما رابطه ای بین دو جدول ایجاد نمودیم DBMS یا سیستم مدیریت پایگاه داده (مخفف *D*ata*B*ase *M*anagment *S*ystem) که در اینجا همان SQL Server می باشد برطبق رابطه تعریف شده جداول مورد بحث چه در پی عملی عمدی یا سهوی اجازه نخواهد رکورد بدون پدری(یتیم) وجود داشته باشد

چگونه اطلاعاتی را رابطه دارند حذف کنیم؟

این کار به این صورت امکان پذیر خواهد بود که ابتدا رکورد(های) فرزند حذف و سپس رکورد پدر حذف شوند(که معمولا این عملیات در داخل یک تراکنش انجام می شود . تراکنش در ادامه مطالب و در *بخش اصطلاحات* توضیح داده خواهد شد)

چند نوع رابطه وجود دارد؟

روابط بین جدول ها در اصول پایگاه داده سه نوع می باشند:

1:رابطه یک به یک : در این رابطه یک فیلد از جدول باید به یک و فقط یک فیلد از جدول دیگر وابسته باشد(که رابطه است دو طرفه و معمولا بین کلید های اصلی یا ستون های منحصر به فرد برقرار می گردد)

2:رابطه یک به چند: این همان رابطه ای است که درمثال بالا به آن اشاره و آشنا شدید

3:رابطه چند به چند: در این رابطه چند فیلد از یک جدول به یک یا جند فیلد از جدول دیگر و بالعکس با هم رابطه دارند
برای مثال در یک دانشگاه در جدول دانشجویان یک دانشجو می تواند در جدول اساتید یک یا چند استاد داشته باشد و همچنین یک استاد می تواند در جدول دانشجویان یک یا چند دانشجو داشته باشد!
نکته:رابطه چند به چند در SQL Server پشتیبانی *نمی شود*!

چگونه روابط را در SQL Server ایجاد کنید؟

این کار بسیار راحت می باشد

از گره های WorkWithDb_Sample1 رو Database Diagrams کلیک راست و گزینه New Database Diagram را انتخاب کنید

اگر پنجره Add Table ظاهر نشد در جایی خالی از دیاگرام(ناحیه سفید رنگ بزرگ) کلیک راست و گزینه Add Table را انتخاب و جدول های مورد نظر (در این مثال همه جداول) را به دیاگرام اضافه کنید

برای نمونه جهت ایجاد ارتباط بین دو جدول انبار جدول tblAnbar را که به شکل یک پنجره کوچک است(تصویر اول بالا) انتخاب کنید حالا با موس فیلد Code گرفته کشیده و در جدول tblAnbarKalaGroup روی فیلد Code رها کنید
در این زمان دو پنجره همزمان باز می شوند

در پنجره بالایی (Table And Column) بررسی نمایید که Primary Key Table با tblAnbar و Foeign Key Table با tblAnbarKalaGroup برابر باشند اینها دوجدولی هستند که ارتباط بین آنها برقرار خواهد شد که Primary Key Table جدول پدر و Foeign Key Table فرزند خواهد بود

حالا باید فیلد های رابط انتخاب شوند که قبلا به این نتیجه رسیده ایم که فیلد های Code خواهند بود پس بررسی نمایید در جدول (دوستونی) پایینی در هر دو طرف Code انتخاب شده باشد. اگر چنین نبود روی آن کلیک و از لیست باز شو Code انتخاب نماییید
برای اعمال رابطه در هردو پنجره دکمه Ok را بزنید
جهت تکمیل عملیات Ctrl+S را زده و دیگرام را با نام dgrWorkWithDB ذخیره کنید

سایر رابطه ها را برطبق لیست زیر ایجاد کنید

برای نمونه رابطه فوف که به صورت از tblAnbar به tblAnbarKalaGroup و فیلدهای Code را به صورت زیر نوشته ایم:

tblAnbar.Code >> tblAnbarKalaGroup.Code 

لیست روابط:

 
tblAnbar.Code >> tblAnbarKalaInput.ToAnbar
tblAnbar.Code >> tblAnbarKalaOutput.FromAnbar
tblKalaGroup.Code >> tblAnbarKalaGroup.KalaGroup
tblKalaGroup.Code >> tblAnbarKala.KalaGroup
tblKala.Code >>tblAnbarKalaInput.KalaCode
tblKala.Code >>tblAnbarKalaOutput.KalaCode
tblMoshtari.Code >> tblAnbarKalaInput.Moshtari.Code 
tblMoshtari.Code >> tblAnbarKalaOutput.Moshtari.Code 
tblUser.UserName >> tblUserDetail.UserName
tblUser.UserName >> tblUserPremission.UserName
tblPremissionList.Premission >> tblUserPremission.Premission


اگر تمام مراحل فوق را با موفقیت انجام داده باشید تقریبا کار با پایگاه داده تکمیل و قابل استفاده است
و باید خود را کم کم آماده آشنای با اصطلاحات مورد استفاده در کدنویسی کرده و کار را با کد نویسی در VB.NET ادامه دهیم.

----------


## فرید نجفلو

*اصطلاحات و نام ها مورد استفاده در برنامه نویسی:*

TSQL:
(Transact Structured Query Language)

به معنی : زبان پرس و جوس ساخت یافته
در حالت کلی می توان این زبان پایگاه داده برای ارتباط با آن است پس باید گفت برای ایجاد رابطه با هر کس و هر چیزی ابتدا باید زبان او را دانست
این شامل دستور یا دستوراتی است ما آن را بر روی پایگاه داده اجرا و اطلاعاتی را به پایگاه داده ارسال یا از آن دریافت می کنیم

اشیاء دات نت:

Connetion:
مسول ایجاد پل ارتباطی بین برنامه ما و پایگاه داده است که می توان آن را به تونلی تشبیه کرد که اطلاعات توسط آن بین برنامه و 
پایگاه داده رد و بدل می شود

Connection String:

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

Transaction:

تراکنش در واقع بیشتر در جاهایی کاربرد دارد که نیاز است تا مجموعه ای از دستورات و کد ها پشت سر هم انجام شده و همه آنها باید با موفقت همرا باشند
برای مثال (در حات خیلی ساده) برای حذف یک کاربر ابتدا باید مجوز های آن از جدول مجوز کاربران حذف و سپس کاربر از جدول اصلی حذف شود
پس عملیات در صورتی موفق است که هر دو عملیات با موفقت همرا باشند 
برای همین هر دو در داخل یک تراکنش اجرا و تغییرات بر پایگاه داده زمانی به صورت قطعی اعمال می شود که هر دو با موفقیت اجرا شده باشند

Commit-RollBack:

در یک تراکنش چنانچه کل عملیات موفق آمیز بوده باشد با صدا زدن Commit تغییرات به صورت قطعی بر پایگاه داده اعمال می شود
اما چنانچه یک یا چند عملیات ناموفق باشند یا ما به هر دلیلی نخواهیم تغییرات اعمال شود تغییرات را لغو(RollBack) خواهیم کرد
توجه داشته باشید اگر یک تراکنش ایجاد شده و Commit نشود با بسته شد ارتباط RollBack اتفاق خواهد افتاد

DataTable:

محفظه ای است که اطلاعات یک جدول را استخراج و در داخل آن نگه خواهیم داشت(توجه داشته باشید که این شئی در صورت نیاز می تواند با کد نویسی ایجاد و پر شود)

DataVeiw:

همانند یک View پایگاه داده عمل کرده و نما هایی سفارشی از یک DataTable را ایجاد می کند
نکته: ما از این شیئ در کد نویسی استفاده نخواهیم کرد و فقط جهت آشنایی شما بیان گردید

DataRow:
معادل یک رکورد در پایگاه داده بوده و یک DataTable از مجموع آنها حاصل می شود

DataColumn:

معادل یک فیلد در پایگاه داده بود و Row از مجموع یک یا چند ستون(Column) تشکیل می شود

DataSet:
محفظه ای است برای نگهداری DataTable ها

Command:
دستورات خود را در هنگام کد نویسی با یک Command و یک اتصال موجود بر روی پایگاه داده اجرا خواهم کرد

DataAdapter:

همانند یک Command عمل می کند ولی دارای امکانات بیشتری و توابع و متد های مفیدی زیادی می باشد


Datareader:
توسط این تابع اطلاعات جدولی را می توانید از پایگاه داده بخواند
اما این شیئ دو ایراد مهم دارد
1-با آن اطلاعات را *رکورد به رکورد* و فقط به *سمت جلو* می توان خواند که معمولا در داخل یک حلقه مورد استفاده قرار می گیرد(در واقع خواصیتی همانند متد ReadLine در کار با فایل ها دارد!)

2-در هنگام استفاده از این شئی از لحظه ارتباط با پایگاه داده تا اتمام دریافت و پردازش اطلاعات اتصال باید همچنان با پایگاه داده برقرار باشد(اتلاف منابع!)

نکته: ما از این شیئ در کد نویسی استفاده نخواهیم کرد و فقط جهت آشنایی شما بیان گردید

----------


## فرید نجفلو

*رشته اتصال(Connection String) چیست؟*

رشته اتصال یکی از مشکلات حاد برنامه نویسان مبتی است حتی طبق سوال هایی که در سایت بعضی از دوستان دارند حالت سردر گمی و عدم درک 
را القا می کند

در واقع رشته اتصال یکی از مهمترین و در عین حال ساده ترین مسائل برنامه نویسی پایگاه داده است

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

برای مثال چند خاصیت از شئی SqlClint.SqlConnectionStringBiulder را که ابزاری جهت ایجاد ، تغییر و مدیریت  رشته اتصال است را بررسی می کنیم

-- DataSource:

این خاصیت را می توان مهمترین خاصیت بیان نمود که اکثر ابهامات هم از همین مورد نشئات می گیرد

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

* محل و نام سرور چیست؟

منظور از محل سرور رایانه ای است که سرور برروی آن نصب شده است.
محل سرور به دو دسته تقسیم می شود:

1-محلی:
یعنی سرور روی همان رایانه ای قرار دارد که هم اکنون برنامه ما در آن اجرا می شود
تعریف محل سرور محلی روش های گوناگونی دارد که به چند مورد آنها عبارتند از:

*** استفاده از کلمه (LOCAL)
*** استفاده از نقطه "."
*** استفاده از نام رایانه (برای مثال MY-PC-NAME)
*** استفاده از IP لوپ بک 127.0.0.1 (توصیه نمی شود)
*** استفاده از IP واقعی رایانه در شبکه(برای نمونه IP مشهور 192.168.0.1) (توصیه نمی شود)

2-راه دور(Remote):

این سرور در جایی غیر از رایانه ای که برنامه ما در حال اجرا است قرار دارد
تعیین محل سرور راه دور نیز متنوع است که چند نمونه ذکر می شود:
*** استفاده از نام رایانه در شبکه (REMOTE-PC-NAME) (ارجه ترین گزینه)
*** استفاده از IP(مثال 192.168.0.2)
*** استفاده از آدرس (مثال www.Barnamenevis.org) البته این مورد معمولا آدرس سایت نیست بلکه سرور یک آدرس به  ناکاربری و گذرواژه ارئه می کند تا از آن استفاده شود 
2-نام سرور:

سرور به صورت بی نام (گاها پیش فرض نیز گفته می شود) و با نام نصب می گردد(نام حین نصب به سرور داده شده و بعد از نصب نیز قابل تغییر است اما تغییر نام یک سرور عملی غیر متعارف ولی مجاز تلقی می شود )

در هر رایانه فقط یک سرور بی نام می تواند وجود داشته باشد و برای سرور دوم به بعد باید نامی اختصاص داده شود
اگر سرور بدون نام باشد آدرس آن فقط از محل سرور تشکیل خواهد شد برای مثال جهت ارتباط با سرور پیش فرض رایانه جاری محل آدرس آن بصورت LOCAL نوشته می شود

اگر سرور داری نام باشد آدرس آن عبارت است از محل سرور باضافه یک ممیز وارون ( *\* ) و نام سرور برای مثال نام پیش فرض جهت نسخه Express به این صورت است 

(LOCAL)\SQLEXPRESS
 و یا 
.\SQLEXPRESS

-- InitialCatalog:

پایگاه داده ای است که قصد ارتباط با آن را داریم
در صورتی که فعلا پایگاه داده ما موجود نیست(در ادامه اجرای برنامه ساخته خواهد شد) و یا فعلا مشخص نیست(کاربر از بین چند پایگاه 
داده یکی از آنها را معرفی خواهد کرد) و یا هر دلیل دیگری میتوان آن را خالی گذاشت یا در حالت بهتر نام پایگاه داده پیش فرض و 
همیشه موجود یعنی *master* را وارد نمود (البته در مواردی همچون اتصال به پیاگاه داده موجود در اینترنت معمولا دسترسی به پایگاه داده master مجاز نمی باشد در چنین مواردی جنبه احتیاط را فراموش نکنید زیرا ممکن است با اختلاط کمی اشتباه برنامه شما قبل از شروع قفل شود!!!)

-- UserID و Password:

اگر برای ورود به سرور نیاز به نام کاربری و کلمه عبور باشد مقادیر این پارامتر ها را با نام کاربری و کلمه عبور معتبر مقدار دهی 
می کنیم(در حالت MIXED MODE)

-- IntegratedSecurity:

چنانچه نوع اعتبار سنجی سرور از نوع ویندوزی باشد یعنی کاربر سرور همان کاربری است که به ویندوز وارد شده است و نیازی به ارائه نام کاربری و کلمه عبور نمی باشد
در این حالت مقدار این پارامتر TRUE و در غیر این صورت FALSE خواهد بود

توجه کنید در صورتی که این پارامتر TRUE باشد از بکارگیری پارامتر های UserID و Password چشم پوشی خواهد شد!

توصیه: قویا توصیه میشود در حالت شبکه(سرور راه دور) از حالت MIXED MODE  جهت *امنیت* از کلمات عبور *مطمئن* و *طولانی* استفاده شود

ConnectTimeout:

مدت زمانی است به ثانیه که مهلت اجرای یک دستور ارسالی را مشخص می کند
اگر دستور در این مدت پاسخی را برنگرداند با یک خطای Timeout مواجه خواهید شد
این مدت زمان باید طوری تنظیم شود که نه دستورات زمان بر(کارهایی مثل انتقال فایل) مهلت اجرا نداشته باشند و نه اینکه در هر ایجاد خطا کاربر را به مدت طولانی منتظر نگه داید!

نکته:اگر این مقدار با *صفر* تنظیم شود به معنی این است که اجرای دستورات محدویت زمانی *ندارند* و در اینصورت ممکن شرایطی پیش آید که مجبور باشید *تا ابد* منتظر بمانید!

-- ConnectionString:

بعد از کلی تلاش و تعیین پارامتر ها با این خصوصیت می توانید رشته اتصال مورد نظر خود را استخراج و استفاده نمایید


شیئ SqlConnectionStringBiulder دارای خصوصیات و متدهای بیشری است ولی موارد بالا مهمترین و پر کاربرترین آنا هستند

(در فصل بعد کد نویسی را باهم از همین رشته اتصال شروع خواهیم کرد)

----------


## فرید نجفلو

*قوانینی را برای عدم گسستگی کد     های خود داشته باشید:*

به این فکر کنید که هر فرم ، تابع ، متد ،     روال رویدتد و ... برای خود دستورات TSQL رو ایجاد و اجرا کند شما چگونه می     توانید اتصالات به پایگاه داده را مدیریت کنید؟

پس ما توابع متد هایی را     تعریف می کنیم که تنها مجاری ارتباط با پایگاه داده باشند
یعنی:
1- هیچ کدی حق باز کردن ارتباط با پایگاه داده را ندارد مگر     توسط تابع معرفی شده

یعنی به جز در این توابع ما نباید هیچ جایی از     برنامه کد هایی شبیه این را ببینیم:


       SqlConnection.Open()
        SqlDataAdapter1.Fill(X)
        SqlCommand1.ExecuteNonQuery()
        SqlCommand1.ExecuteReader()
        SqlCommand1.ExecuteScalar() 


البته موارد استثنایی مثلا در مورد SqlConnection.Open که فعلا جای بحث آن     نیست!

2-هر کد دستورات TSQL خود را تولید می     کند(تقسیم وظایف)

برای مثال در صفحه تعریف مشتری وجود دستوری که     کاربری را حذف یا  اضافه می کند غیر قابل قبول است
البته اصول کار اینه که     همه کاری های ما شئی گرا بوده و هر بخش کلاس(های) خود با توبع و متد های لازم     را داشته باشه

3-سعی شود توابع و متد های عمومی     اعلانی طوری تعریف شوند که همه نیاز های ما را پوشش دهند در واقع سایر کد های برنامه را     پوشش دهند

برای مثال اگر ما تابعی را برای اجرای دستورات SQL  در نظر     گرفته باشیم که در داخل آن یک تراکنش ایجاد و پس از اجرا اعمال(Commit) می شود     همه دستورات (از جمله دستورات ایجاد پایگاه داده نخواهند توانست از آن استفاده     کنند چون این نوع دستورات در داخل تراکنش قابل اجرا نیستند!)

----------


## فرید نجفلو

*ایجاد پروژه و کلاس های اولیه :*

ایجاد پروژه:

جهت ایجاد پروژه ، ویژوال استدیو را اجرا و در صفحه Start Page بر روی New Project کلیک کنید(یا از منوی File گزینه  New Project را انتخاب کنید)
در پنجره باز شده از لیست باز شو بالا .Net Framework 3.5 را انتخاب کنید
بررسی نمایید که از گره های سمت چپ گره  *Visual Basic* انتخاب شده باشد.
از قسمت میانی پنجره نوع پروژه را Windows Forms Application انتخاب کنید
در مقابل Name نام پروژه یعنی WorkWithDB را نوشته و بر روی دکمه OK کلیک کنید

ایجاد پوشه های گروه بندی:

برای ایجاد یک پوشه در پروژه روی نام پروژه در Solution Explorer کلیک راست و از منوی بازشده Add و سپس New Folder را انتخاب می نماییم
برای ایجاد یک پوشه فرعی روی پوشه در Solution Explorer کلیک راست و از منوی بازشده Add و سپس New Folder را انتخاب می نماییم

به روش های بالا پوشه های زیر را به پروژه اضافه نمایید:

Class
Class\App
Class\DB
Form
Form\App
Form\DB


از این پو شه ها جهت گروه بندی کلاس ها و فرم های پروژه جهت راحتی در مدیریت و بکارگیری از آنها استفاده خواهد شد
نکته:جهت جابجایی یک کلاس یا فرم در بین پوشه ها در Solution Explorer کلاس یا فرم مورد نظر را توسط موس گرفته ، کشیده و بر روی پوشه مورد نظر رها کنید


ایجاد کلاس های اولیه :

دو کلاس به نام های SqlMgr و Publics به پروزه اضافه کنید
جهت انجام این کار کلید های Ctrl+Shift+A را فشرده و از پنجره باز شده گزینه Class را انتخاب نام کلاس را وارد و تایید کنید
کلاس SqlMgr را به پوشه Class\DB و کلاس Publics را به پوشه Class\App انتقال دهید

تنظیمات پروژه:

در پنجره Slotution Explorer بر روی نام پروژه کلیک راست و Properties را انتخاب نمایید
در پنجره باز شده وارد برگه Application شوید
از لیست باز شو ShutDownMode گزینه When last form closes را انتخاب نمایید

از  برگ Refrence  این کلاس     را در سطح پروژه Import کنید
در جعبه متن WorkWithDB.SqlMgrرو نوشته و دکمه Add User     Import  رو بزنید دوباره در جعبه متن WorkWithDB.Publicsرو نوشته و دکمه Add User     Import  رو بزنید

در نهایت کلیدهای Ctrl+Shift+S را فشرده و پروژه را در مسیر دلخواه ذخیره کنید

----------


## فرید نجفلو

*ایجاد توابع و متغیر های عمومی و زیر بنایی:*

برای شروع کار برنامه نویسی نیازمند تعدادی توابع و متغیر های عمومی هستیم که باید ایجاد شوند(یکی از دلایل این کار همان توضیحات قسمت "قوانینی را برای عدم گسستگی کد     های خود داشته باشید" می باشد)

سعی شده توابع رو طوری نوشته شود که  علاوه بر امکان استفاده در همه     شرایط استفاده از آنها راحت باشد (به عنوان مثال به SqlRunCmd توجه کنید که می     تونید با یک دستور TSQL از اون استفاده کنید و هم می تونید از سایر امکانات هم     به صورت اختیاری استفاده کنید)

ممکن است این توابع در آینده اصلاح شوند و تعدادی نیز در مکان مناسبی از مطالب اضافه خواهند شد
توابع کاندیدای اصلاح با "غیر فعال" علامت گذاری شده اند (برای نمونه تابع بسیار مهم و حیاتی Loader)
توابع در حال حاضر به دو قسمت (کلاس) تقسیم شده اند که یک کلاس جهت کار های کاملا مربوط به پایگاه داده بود و دیگری توابع و متغیر های  عمومی  می باشد
تا حد امکان توابع و متغیر ها به صورت خود توضیح (Self-document) می باشند (که از XML-Documention استفاده شده است)

هر یک از متغیر ها و توابع در صورت نیاز در محل مناسب توضیح داده خواهد شد

کد های زیر را در کلاس های مربوطه وارد کنید:


''' <summary>
''' کلاس متد ها و توابع عمومی مورد نیاز
''' </summary>
''' <remarks></remarks>
Public Class Publics
#Region "متغیر ها"
    ''' <summary>
    ''' نام کاربر جاری
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared CurrentUserName As String = ""
    ''' <summary>
    '''ذخیره کننده خطا های برای انتقال بین روال ها و توابع
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ErrorCache As String = ""
    ''' <summary>
    ''' نام پرونده ثبت خطا ها
    ''' </summary>
    ''' <remarks></remarks>
    Public Const ErrorLogFileName As String = "WorkWithDB_LogFile.Log"
#End Region
#Region "توابع و متد ها"
    ''' <summary>
    ''' این متد اجرای برنامه را از صفحه نخست دریافت و ادامه کارها را بر عهده می گیرد
    ''' </summary>
    ''' <remarks>غیر فعال است</remarks>
    Public Shared Sub Loader()
        Try
            'انجام کار های پیش نیاز
 
            'فراخوانی فرم دریافت اطلاعات پایگاه داده
 
            'فراخوانی فرم ورود کاربران
 
            'فراخوانی فرم اصلی
 
            'انجام کار های مورد نیاز بعدی
        Catch ex As Exception
            MsgBox("خطا در بارگذاری برنامه!" & vbNewLine & ex.Message, _
                    MsgBoxStyle.Critical + MsgBoxStyle.MsgBoxRtlReading + MsgBoxStyle.MsgBoxRight, _
                    "خطلای جدی")
            AddError(ex.Message)
            End
        End Try
    End Sub
    ''' <summary>
    ''' کدگذاری یک رشته متنی
    ''' </summary>
    ''' <param name="InputStr">رشته جهت کدگذاری</param>
    ''' <returns></returns>
    ''' <remarks>غیر فعال است</remarks>
    Public Shared Function StringCoding(InputStr As String) As String
        Return InputStr
    End Function
    ''' <summary>
    ''' بازیابی رشته کد شده
    ''' </summary>
    ''' <param name="InputStr">رشته کد شده</param>
    ''' <returns></returns>
    ''' <remarks>غیر فعال است</remarks>
    Public Shared Function StringDecoding(InputStr As String) As String
        Return InputStr
    End Function
    ''' <summary>
    ''' بررسی صحت نام کاربری و کلمه عبور
    ''' </summary>
    ''' <param name="UserName">نام کاربری وارد شده</param>
    ''' <param name="PassWord">کلمه عبور وارد شده</param>
    ''' <param name="CostumeConnStr">رشته اتصال سفارشی غیر از رشته اتصال عمومی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CheckUserNamePassWord(ByVal UserName As String, _
                                                 ByVal PassWord As String, _
                                                 Optional ByVal CostumeConnStr As String = "") As Boolean
        Return True
    End Function
    ''' <summary>
    ''' بررسی مجوز های کاربر
    ''' </summary>
    ''' <param name="Premissions">لیست مجوز های درخواستی</param>
    ''' <param name="UserName">نام کاربری-درصورت خالی بودن کاربر جاری</param>
    ''' <param name="CostumeConnStr">رشته اتصال سفارشی غیر از رشته اتصال عمومی</param>
    ''' <returns></returns>
    ''' <remarks>غیر فعال است</remarks>
    Public Function CheckUserPremission(ByVal Premissions As List(Of String), _
                                        Optional ByVal UserName As String = "", _
                                        Optional ByVal CostumeConnStr As String = "") As Boolean
        Return True
    End Function
    ''' <summary>
    ''' اضافه كردن يك خطا به محفظه خطاها
    ''' </summary>
    ''' <param name="strError">خطاي مورد نظر - الزامي</param>
    ''' <remarks></remarks>
    Public Shared Sub AddError(ByVal strError As String)
        Try
            If strError.Length = 0 Then Exit Try
            If ErrorCache.Length = 0 Then
                ErrorCache = strError
            Else
                ErrorCache = ErrorCache & vbNewLine & strError
            End If
            'نوشتن دلیل خطا در یک فایل سابقه جهت استفاده های بعدی
            'این فایل در عملیات پشتیبانی نرم افزار فوق العاده مفید خواهد بود
            Dim LogStr As String = ""
            LogStr &= PersianDate
            LogStr &= vbTab & TimeOfDay.ToString("HH:mm:ss")
            If GlobalConnectionString IsNot Nothing Then
                LogStr &= vbTab & GlobalConnectionString.DataSource
                LogStr &= "\" & GlobalConnectionString.InitialCatalog
            End If
            LogStr &= vbTab
            If CurrentUserName <> "" Then
                LogStr &= CurrentUserName
            Else
                LogStr &= "No User"
            End If
            LogStr &= vbTab & strError
            LogStr &= vbNewLine
            Dim ErrorFilePath As String = ""
            Dim LogFile As New Logging.FileLogTraceListener
            LogFile.BaseFileName = ErrorLogFileName
            LogFile.MaxFileSize = (2 ^ 20) * 10 ' 10 Megabyte
            LogFile.Location = Logging.LogFileLocation.Custom
            LogFile.CustomLocation = Application.StartupPath
            LogFile.Write(LogStr)
            LogFile.Close()
        Catch ex As Exception
        End Try
    End Sub
    ''' <summary>
    ''' برگردان تاریخ میلادی به شمسی
    ''' </summary>
    ''' <param name="InDate">تاریخ میلادی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function PersianDate(Optional InDate As Date = Nothing) As String
        Try
            If InDate.Year <= 1 Then InDate = Now
            Dim PClndr As New Globalization.PersianCalendar
            Dim DateInIran As String = PClndr.GetYear(InDate)
            DateInIran &= "/" & Strings.Right(("0" & PClndr.GetMonth(InDate)), 2)
            DateInIran &= "/" & Strings.Right(("0" & PClndr.GetDayOfMonth(InDate)), 2)
            Return DateInIran
        Catch ex As Exception
            Return ""
        End Try
    End Function
 
    ''' <summary>
    ''' برگردان تاریخ شمسی به میلادی
    ''' </summary>
    ''' <param name="InDate">تاریخ شمسی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GregorianDate(InDate As String) As String
        If InDate.Length <> 10 Then Return ""
        Dim Year As Integer = Mid(InDate, 1, 4)
        If Year < 1300 OrElse Mid(InDate, 1, 4) > 1500 Then Return ""
        Dim Month As Integer = Mid(InDate, 6, 2)
        If Not IsNumeric(Month) OrElse Month > 12 OrElse Month < 1 Then Return ""
        Dim Day As Integer = Mid(InDate, 9, 2)
        If Day < 1 OrElse Day > 31 Then Return ""
        Try
            Dim PClndr As New Globalization.PersianCalendar
            Return PClndr.ToDateTime(Year, Month, Day, 1, 1, 1, 1).ToString
        Catch ex As Exception
            Return ""
        End Try
    End Function
    ''' <summary>
    ''' تعیین اینکه تاریخ شمسی معتبر است یا نه
    ''' </summary>
    ''' <param name="InDate">تاریخ شمسی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CheckDate(InDate As String) As Boolean
        Try
            Return GregorianDate(InDate) <> ""
        Catch ex As Exception
            Return False
        End Try
    End Function
#End Region
End Class


Imports System.Data.SqlClient
''' <summary>
''' کلاس مدیریت کار با پایگاه داده
''' </summary>
''' <remarks></remarks>
Public Class SqlMgr
    ''' <summary>
    ''' ابزار مدیریت رشته اتصال عمومی
    ''' </summary>
    ''' <remarks>فراموش نکنید که اگر تفییری در پارامتر ها دادید رشته اتصال جدید را به نمونه اتصال عمومی نسبت دهید</remarks>
    Public Shared ReadOnly GlobalConnectionString As New SqlConnectionStringBuilder
    ''' <summary>
    ''' نمونه اتصال عمومی و پیش فرض جهت استفاده در ل برنامه
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly GlobalConnection As New SqlConnection
    ''' <summary>
    '''DataTable و پر کردن یک SQL دریافت دستور 
    ''' </summary>
    ''' <param name="TSqlCmd">دستور انتخاب - الزامی</param>
    ''' <param name="Conn">یک اتصال سفارشی پایگاه داده - اختیاری</param>
    ''' <param name="Transaction">تراكنش موجود براي اتصال فرستاده شده - اختیاری</param>
    ''' <returns>ِجدول پر شده</returns>
    ''' <remarks></remarks>
    Public Shared Function SqlFillTable(ByVal TSqlCmd As String, _
                                        Optional ByRef Conn As SqlConnection = Nothing, _
                                        Optional ByRef Transaction As SqlTransaction = Nothing, _
                                        Optional ByRef Commander As SqlCommand = Nothing, _
                                        Optional ByVal DontCloseConnection As Boolean = False, _
                                        Optional ByVal DontSetCommanderText As Boolean = False) As DataTable
        Try
            'مقدار دهی به پارامتر ها رد صورتی که ارسال نشده باشند
            '////////////////////////////////////////////////////////////////
            If Conn Is Nothing Then Conn = GlobalConnection
            If Not OpenConn(Conn) Then Throw New Exception("")
            If Conn.State <> ConnectionState.Open Then Throw New Exception("")
            Dim Result As New DataTable
            If Commander Is Nothing Then
                Commander = Conn.CreateCommand
            Else
                Commander.Connection = Conn
            End If
            If Not DontSetCommanderText Then
                Commander.CommandText = TSqlCmd
            End If
            If Transaction IsNot Nothing Then
                Commander.Transaction = Transaction
            End If
            '////////////////////////////////////////////////////////////////
            Dim DtAdp As New SqlDataAdapter
            DtAdp.SelectCommand = Commander
            DtAdp.Fill(Result)
            Return Result
        Catch ex As Exception
            AddError(ex.Message)
            Return Nothing
        Finally
            If Not DontCloseConnection Then CloseConn(Conn)
        End Try
    End Function
    ''' <summary>
    '''و اجرای آن SQL دریافت دستور 
    ''' </summary>
    ''' <param name="TSqlCmd">دستور انتخاب - الزامی</param>
    ''' <param name="Conn">یک اتصال سفارشی پایگاه داده - اختیاری</param>
    ''' <param name="Transaction">تراكنش موجود براي اتصال فرستاده شده - اختیاری</param>
    ''' <param name="Commander">يك مجري دستور - اختياري</param>
    ''' <param name="DontCloseConnection">بسته نشدن اتصال پس از اجراي دستور - اختیاری</param>
    ''' <param name="DontCommit">عدم تاييد - اعمال تراكنش - اختیاری</param>
    ''' <param name="DontSetCommanderText">دستور به مجري الصاق نشود - زماني كاربرد دارد كه مجري فرستاده شود- -اختياري</param>
    ''' <returns>ِیک مقدار بولی که نشانگر موفقت یا عدم موفقیت اجرا خواهد بود</returns>
    ''' <remarks></remarks>
    Public Shared Function SqlRunCmd(ByVal TSqlCmd As String, _
                                     Optional ByRef Conn As SqlConnection = Nothing, _
                                     Optional ByRef Transaction As SqlTransaction = Nothing, _
                                     Optional ByRef Commander As SqlCommand = Nothing, _
                                     Optional ByVal DontCloseConnection As Boolean = False, _
                                     Optional ByVal DontCommit As Boolean = False, _
                                     Optional ByVal DontSetCommanderText As Boolean = False) As Boolean
        Try
            'مقدار دهی به پارامتر ها رد صورتی که ارسال نشده باشند
            '////////////////////////////////////////////////////////////////
            If Conn Is Nothing Then Conn = GlobalConnection
            If Not OpenConn(Conn) Then Throw New Exception("")
            If Conn.State <> ConnectionState.Open Then Throw New Exception("")
            Dim Trans As SqlClient.SqlTransaction
            If Transaction Is Nothing Then
                Trans = Conn.BeginTransaction
            Else
                Trans = Transaction
            End If
            If Commander Is Nothing Then
                Commander = Conn.CreateCommand
            Else
                Commander.Connection = Conn
            End If
            If Not DontSetCommanderText Then
                Commander.CommandText = TSqlCmd
            End If
            '////////////////////////////////////////////////////////////////
            Commander.Transaction = Trans
            Commander.ExecuteNonQuery()
            If Not DontCommit Then Trans.Commit()
            Return True
        Catch ex As Exception
            AddError(ex.Message)
            Return False
        Finally
            If Not DontCloseConnection Then CloseConn(Conn)
        End Try
    End Function
    ''' <summary>
    '''و اجرای آن بدون تراکنش SQL دریافت دستور 
    ''' </summary>
    ''' <param name="TSqlCmd">دستور انتخاب - الزامی</param>
    ''' <param name="Conn">یک اتصال سفارشی پایگاه داده - اختیاری</param>
    ''' <param name="DontCloseConnection">بسته نشدن اتصال پس از اجراي دستور - اختیاری</param>
    ''' <returns>ِیک مقدار بولی که نشانگر موفقت یا عدم موفقیت اجرا خواهد بود</returns>
    ''' <remarks></remarks>
    Public Shared Function SqlRunCmdwithoutTrans(ByVal TSqlCmd As String, _
                                                 Optional ByRef Conn As SqlConnection = Nothing, _
                                                 Optional ByVal DontCloseConnection As Boolean = False) As Boolean
        Try
            If Conn Is Nothing Then Conn = GlobalConnection
            If Not OpenConn(Conn) Then Throw New Exception("")
            If Conn.State <> ConnectionState.Open Then Throw New Exception("")
            Dim Cmd = Conn.CreateCommand
            Cmd.CommandText = TSqlCmd
            Cmd.ExecuteNonQuery()
            Return True
        Catch ex As Exception
            AddError(ex.Message)
            Return False
        Finally
            If Not DontCloseConnection Then CloseConn(Conn)
        End Try
    End Function
    ''' <summary>
    ''' باز کردن اتصال
    ''' </summary>
    ''' <param name="Conn">اتصال مورد نظر</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function OpenConn(ByRef Conn As SqlConnection) As Boolean
        Try
            If Not (Conn.State = ConnectionState.Connecting OrElse Conn.State = ConnectionState.Open) Then
                Conn.Open()
            End If
            Return True
        Catch ex As Exception
            AddError(ex.Message)
            Return False
        End Try
    End Function
    ''' <summary>
    ''' بستن اتصال
    ''' </summary>
    ''' <param name="Conn">اتصال مورد نظر</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CloseConn(ByRef Conn As SqlConnection) As Boolean
        Try
            If Conn.State = ConnectionState.Connecting OrElse Conn.State = ConnectionState.Open Then
                Conn.Close()
            End If
            Return True
        Catch ex As Exception
            AddError(ex.Message)
            Return False
        End Try
    End Function
    ''' <summary>
    '''SQL آماده کردن متن براي استفاده در دستورات
    ''' </summary>
    ''' <param name="strIn">متن برای تبدیل</param>
    ''' <returns>متن با تک نقل قول در دو طرف آن</returns>
    ''' <remarks></remarks>
    Public Shared Function PrepStrToSql(ByVal strIn As String) As String
        Try
            Dim Result As String
            Result = "'" & strIn.Trim & "'"
            Return Result
        Catch ex As Exception
            AddError(ex.Message)
            Return strIn
        End Try
    End Function
End Class

----------


## فرید نجفلو

*رشته اتصال(Connction String)و اتصال (Connection) را چگونه و کجا تعریف و مقدار دهی  کنیم؟*

قبل از ادامه بحث ممکن است سوالی برای دوستان پیش آید که چرا اول رشته اتصال تعریف شد سپس  این همه کد ارائه و دوباره به این مرحله وارد شدیم (چیزی شبیه به خانه اول)

اول : اینکه مبحث قبلی رشته اتصال مربوط به تعریف و اصطلاحات بوده و نیازی به کد نداشتند و از این قسمت به بعد است که با برنامه نویسی واقعی و مشکلات آن درگیر خواهیم شد

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




با توجه به اینکه رشته اتصال یکی از زیر بناهای سایر قسمت ها است ابتدا آن را بررسی و به برنامه اضافه خواهیم کرد
نکته:رشته اتصال به قدری مهم است که به تنهایی یک فرم را در برنامه فقط برای خود اختصاص داده و مهمتر از آن این فرم اولین فرم کاربردی خواهد بود که کاربر می بیند!

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

بدلیل اینکه رشته اتصال باید در تمام نقاط برنامه در دسترس باشد آن را به صورت Public و جهت عدم نیاز به نمونه سازی از کلاس(NEW) به همراه Shared در کلاس SqlMgr تعریف و اعلان می شود!

''' <summary>
''' کلاس مدیریت کار با پایگاه داده
''' </summary>
''' <remarks></remarks>
Public Class SqlMgr
    ''' <summary>
    ''' ابزار مدیریت رشته اتصال عمومی
    ''' </summary>
    ''' <remarks>فراموش نکنید که اگر تغییری در پارامتر ها دادید رشته اتصال جدید را به نمونه اتصال عمومی نسبت دهید</remarks>
    Public Shared ReadOnly GlobalConnectionString As New SqlConnectionStringBuilder


کجا رشته اتصال را مقدار دهی     کنیم؟

در بیشتر موارد دیده می شود که دوستان به این روش برنامه رو آغاز می     کنند :
آغاز برنامه توسط کاربر>> نمایش Splash >> فرم اصلی
که     این روش مناسبی نیست
یک پیشنهاد برای نرم افزار های مربوط به پایگاه داده به     این صورت است:
اجرا توسط کاربر>> Splash>>اجرای یک متد (Sub) به     عنوان Loader >> نمایش صفحه اتصال و انتخاب پایگاه داده >> اعتبار     سنجی(Login) >> ورود به فرم اصلی

Loader :

جهت بارگذاری     تنظیمات مهم و پیش فرض و امورات پیش اجرا که یکی از مهمترین این کارها دریافت     یا بررسی شماره سریال محصول است که ممکن است مجبور به عدم ادامه عملیات اجرا یا     اجرای آزمایشی شویم!
در همین متد صفحه (مرحله) بعدی شروع می شود

نمایش صفحه     اتصال و انتخاب پایگاه داده :

 این محل همان جایی است که رشته اتصال رو باید     دریافت کنیم که در وهله اول از تنظیمات ذخیره شده اجرا های قبلی (بعدا توضیح     داده خواهد شد) خوانده شده و به کاربر اجازه تغییر آنها را می دهیم:
حداقل     اطلاعاتی که باید در این مرحله دریافت شود:

*-نام     سرور

*-نام کاربری و کلمه عبور: جهت ورود به سرور(برای اینکه باید حالت     MixedMode هم مد نظر     باشد)

*-نام پایگاه     داده
این مورد یا مستقیم توسط کاربر وارد می شود یا توسط برنامه  برای مثال     تمام دیتابیس هایی که نام آنها با WorkWithDB شروع می شود متعلق به برنامه ماست که در این صورت هم، باید به کاربر اجازه بدهیم دیتابیس خودشو انتخاب     کنه. 
ما در ابتدا از یک پایگاه داده که دارای نام ثابتی است استفاده و در ادامه و آموزش های پیشرفته از چند پایگاه داده با قابلیت سوئیچ استفاده خواهیم کرد)

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

اعتبار     سنجی(Login):

در این مرحله رشته اتصال ایجاد شده به فرم ورود کاربر     ارسال می شود تا کاربر نام کاربری و کلمه عبور خود را برای ورود به نرم افزار     وارد کند(توجه کنید که این نام کاربری و کلمه عبور جدا از کاربر متعلق به     پایگاه داده بوده و شما آن را در پایگاه داده  بر جدولtblUsers     تعریف و ایجاد کرده اید)

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

در غیر اینصورت     پیغام (های) لازم نمایش و دوباره به صفحه قبل(دریافت رشته اتصال) برگشته یا کلا     از برنامه خارج می شویم!

ورود به فرم اصلی:

در این مرحله اتصال به     پایگاه داده انجام شده رشته اتصال آماده استفاده می باشد و عملیات مختص نرم     افزار شروع می شود (که معمولا نقطه آغازین از رویداد FormLoad فرم اصلی شروع     می شود!)                        

*اتصال (Connection):*
چون اتصال (Connection) و استدلال های بالا می باشد و وابسته به رشته اتصال است این نیز همانند رشته اتصال و در کنار آن تعریف می شود البته ذکر این نکته مهم است که اتصال همانند رشته اتصال تعریف می شود ولی نسبت به آن در مکان هایی بیشتری ظاهر خواهد شد!

  ''' <summary>
    ''' نمونه اتصال عمومی و پیش فرض جهت استفاده در ل برنامه
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly GlobalConnection As New SqlConnection

----------


## فرید نجفلو

*ایجاد لایهADL کار با داده ها (Data Access Layer):*

این لایه با دستکاری داده ها مثل اضافه ، حذف و به روز رسانی آن ها سر و کار دارد که به لطف فناوری های ADO.NET بسیار آسان گردیده 
است
به دلیل اینکه قرار بر عدم استفاده از LINQ در این بحث است!!! ما از یک کلاس DataSet جهت ایجاد این لایه استفاده خواهیم کرد
توجه کنید همانطور که دید ما یک تعدا توابع کار با پایگاه داده را نیز ایجاد کرده ایم ولی بیشتر کار های این لایه با استفاده از 
DataSet مذکور خواهد بود و در جاهاییی که انجام عملیات با این کلاس دشوار و گاها غیر ممکن می شود از توبع استفاده خواهد شد 
از جمله این کار ها دریافت ، ایجاد و بررسی صحت رشته اتصال است چون تا زمانی که اتصال و رشته اتصال مشخص نباشد امکان استفاده از Dataset وجود ندارد
یک مثال دیگر عملیاتی نظیر حذف و ایجاد پایگاه داده ها است(در آموزش های پیشرفته)

ایجاد DataSet:

جهت انجام این کار با استفاده از کلید های Ctrl+Shift+A پنجره Add New Item را باز و گره Data را از سمت چپ انتخاب کنید
از قسمت میانی پنجره Datasetانتخاب و با نام dsWorkWithDb تایید کنید
پروژه را ذخیره کنید

نکته:به این Dataset که امکان کار با جداول را به صورت شیئ گرا می باشد در اصطلاح Typed DataSets می گویند.

افزودن اشیا پایگاه داده:

از Solutin Explorer بر روی Dataset ایجاد شده دوبار کلیک کنید تا وارد حالت طراحی شود
از منوی Tools گزینه Connect to Database را انتخاب کرده از پنجره باز شده Microsoft SQL Server را انتخاب و تایید نمایید
حالا شما می بایست در پنجرهAdd Coonection نام سرور و در صورت نیاز نام کاربری و کلمه عبور به سرور را وارد نمایید سپس از لیست 
باز شو select or enter database name  پایگاه داده WorkWithDb_Sample1 را که قبلا توسط اسکریپ همراه پروژه ایجاد کرده اید را انتخاب کنید و تایید نمایید

اکنون یک اتصال به این پایگاه داده به Server Explorer اضافه شده است
گره مربوط به پایگاه داده خود را باز و گره Tables را نیز گسترش دهید
تمام جداول را (با استفاده از Ctrl یا Shift ) انتخاب نمایید
جداول انتخاب شده را با موس گرفته و داخل پنجره طراحی Dataset رها کنید

پروژه را ذخیره کنید

مقدار زیادی کار طراحی ین لایه که قبل از ظهور این تکنولوژی کار طاقت فرسایی بود انجام شده است
دستورات TSQL مورد نیاز نیز در مکان های مناسب مطلب در همین پنجره اضافه خواهد شد

قبل از ادامه مطلب همانطور که در قسمت های قبلی آموختید دو کلاس و فضای نامی زیر را از Properties پروژه Import عمومی نمایید

در پنجره فوق نام کلاس WorkWithDb.dsWorkwithDb رو داخل تکست باکس نوشته و دکمه Add User Import رو بزنید
کلاس WorkWithDb.dsWorkwithDbTableAdapters که با افزودن و ذخیره نمودن دیتاست ایجاد شده است را از لیست (معمولا در انتهای آن است) یافته  تیک بزنید 
 پروژه رو ذخیره کنید.



WorkWithDb.dsWorkwithDb
WorkWithDb.dsWorkwithDbTableAdapters

تعریف و نمونه سازی ابزار مدیریت اتصالات این لایه:

اعلان زیر را به کلاس SqlMgr اضافه نمایید:

    ''' <summary>
    ''' مدیریت ابزار لایه دسترسی به اطلاعات
''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly AdpMgr As New TableAdapterManager


نکته: این متغیر چون یک بار نمونه سازی شده و در ادامه نیازی به تعریف مجدد ندارد به همین دلیل به صورت ReadOnly تعریف گردید تا از تغییر آن در سایر کد ها جلوگیری شود

نمونه سازی ابزار ها:

چون نمونه اشیائ موجود در AdpMgr  نمونه سازی نشده اند متدی را جهت نمونه سازی و تنظیم اتصالی باید که از آن  استفاده کنند (اتصال عمومی ساخته شده  در مراحل قبل) ایجاد می کنیم
نکته:این متد بعد از ایجاد رشته اتصال و نسبت دادن آن به نمونه اتصال عمومی فراخوانی خواهد شد که اولین نقطه آن متد Loader خواهد بود
این کد ها را به کلاس SqlMgr اضافه نمایید


    ''' <summary>
    '''مقدار دهی به ابزار ها
''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub InitAdapters()
        Try
            AdpMgr.Connection = GlobalConnection
            AdpMgr.tblAnbarKalaGroupTableAdapter = New tblAnbarKalaGroupTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblAnbarTableAdapter = New tblAnbarTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblDbVerTableAdapter = New tblDbVerTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaGroupTableAdapter = New tblKalaGroupTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaInputTableAdapter = New tblKalaInputTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaOutputTableAdapter = New tblKalaOutputTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaTableAdapter = New tblKalaTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblMoshtariTableAdapter = New tblMoshtariTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblPremissionListTableAdapter = New tblPremissionListTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserDetailTableAdapter = New tblUserDetailTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserPremissionTableAdapter = New tblUserPremissionTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserTableAdapter = New tblUserTableAdapter With {.Connection = GlobalConnection}
        Catch ex As Exception
        End Try
    End Sub


توضیحی در باره تابع:

در هنگام ایجاد dataset برای هر جدول یک TableAdapter ایجاد می شود که وظیفه اجرای دستورت بروی هر جدول را دارد 
در ادامه مطلالب و در در جای خود دستور TSQL  را به این اشیا اضافه و بعنوان متدی از داخل آنها قابل استفاده خواهیم نمود(در واقع شبیه تابع جلوه خواهند کرد)

این اشیا به صورت تجمیع شده داخل یک کلاس دیگر به نام TableAdapterManager تعریف شده اند ولی نمونه سازی (با NEW) نشده اند 
به همین دلیل ما قبل از استفاده از آنها توسط کلمه کلیدی New آنها را نمونه سازی کرده و در همین خط هم جهت راحتی و خلا صه نویسی نمونه اتصال عمومی را به عنوان اتصالی که باید توسط آن با پایگاه داده ارتباط برقرا کنند معرفی می کنیم
 در نتیجه ما اتصال عمومی را با رشته اتصال عمومی به سمت هر پایگاه داده که نشانه رویم Dataset نیز از همان استفاده خواهد نمود (یکی از دلایلی که ما رشته اتصال و اتصال عمومی را مورد تاکید زیاد قرار داده و به صورت عمومی معرفی نمودیم در اینجا نمایان است) 

DataTable های قویا نام گذاری شده یا Strongly Named DataTable:

وقتی شما جداول را داخل Dataset انداختید در همان حین یک DataTable نیز برای آنها با نام خودشان ایجاد شد 
ولی چند تفاوت عمده میان آنها و یک DataTable های عادی وجود دارد و آن دستیابی به فیلد های جدول بع عنوان یکی از خاصیت های این 
DataTableمی باشد برای مثال وقتی شما یک پرسجو از جدول tblUser را توسط AdpMgr.tblUserTableAdapter و تابع GetData انجام دادید 
نوع بازگشتی آن tblUserDataTable  و هردیف(Item) از این شیئ tblUserRow می باشد و نکته در همین آیتم است
برای مثال برای دریافت نام اولین کاربر موجود در این مقدار برگشتی کافی است به صورت زیر بنویسید(به استفاده از نام فیلد به صورت 
یک خاصیت توجه کنید):

MytblUserDataTable.Item(0).UserName

همه این کارها بدون یک خط کدنویسی شما و در کمتر 5 دقیقه انجام شده است!

نکته: این لایه رفته رفته تکمیل خواهد شد به این صورت که در برنامه نویسی هر بخش زمانی که احساس نیاز به عملیات پاگاه داده ای غیر موجود دید شد TSQL مورد نظر به TableAdapter مربوطه اضافه و مورد استفاده قرار خواهد گرفت

----------


## فرید نجفلو

*ساخت فرم دریافت رشته اتصال(اولین فرم کاربردی در لایه PL):*

همان طور که متذکر شده بودیم اولین فرم کاریردی که کاربر خواهد دید همین فرم خواهد بود
وظیفه این فرم در یافت رشته اتصال به صورت کاربر پسند است (با استفاده از کنترل ها نه دریافت مستقیم یک رشته اتصال)

توجه نمایید که ما برنامه را از یک فرم Splash آغاز خواهیم کرد ولی چون این فرم تاثیر چندانی در کار ندارد و فقط وظیفه انتقال کنترل به متد Loader را دارد توجه زیادی به آن نشده و ما از یک Splash آماده در خود ویژوال استدیو استفاده می کنیم

هم چنین ما در زیبا سازی رابط کاربری(فرم ها) می شود گفت کاری انجام نداده ایم زیرا انجام چنین کاری نیاز به کد نویسی ، انجام تنظیمات و ارائه توضیحات بیشتر بوده و بحث را از هدف اصلی خود دور خواهد نمود و همچنین این واقعیت هم وجود دارد که ظاهر هر نرم افزار وابستگی کامل به سلیقه و دانش برنامه نویس  در طراحی دارد.

ایجاد فرم Splash:

پنجره Add New Item را باز ، گره Windoes Forms را انتخاب و یک Splash Screen را با نام frmAppSplash به پروژه اضافه نمایید

از نوار ابزار یک کنترل Timer به فرم اضافه نموده و نام آن را به tmrShowDelay تغییر داده خاصیت Enable را نیز True نمایید

نکته: از این به بعد درباره طراحی گرفیکی فرم توضیحات مفصل ارائه نشده و فقط موارد مهم و مورد نیاز به اختصار گفته شده و مستقیما وارد پنجره کد نویسی خواهیم شد زیرا ظاهر فرم ها در پروژه نمونه ارئه شده قابل مشاهده و نیازی به توضیح ندارد و ارائه توضیحات مفصل موجب اتلاف زمان گران بها و به درازا کشیدن صحبت ها خواهد شد

حالا با کلید F7 وارد پنجره کد نویسی فرم شوید

تمام کد های قرار گرفته در میان تعریف کلاس فرم را پاک و کد های زیر را اضافه نمایید:


#Region "متغیر ها"
    ''' <summary>
    ''' شفافیت پنجره
    ''' </summary>
    ''' <remarks></remarks>
    Private opcty As Double = 0
#End Region
#Region "روال رویداد ها"
    ''' <summary>
    ''' ایجاد وقفه نمایش فرم
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub tmrShowDelay_Tick(sender As System.Object, e As System.EventArgs) Handles tmrShowDelay.Tick
        Try
            opcty += 0.02
            Me.Opacity = Math.Min(1, opcty)
            If opcty >= 2 Then
                opcty = 0
                tmrShowDelay.Stop()
                Me.Visible = False
                Loader()
                Me.Close()
            End If
        Catch ex As Exception
            Me.Visible = False
            Loader()
            Me.Close()
        Finally
        End Try
    End Sub
    Private Sub lblbarnamenevis_LinkClicked_1(sender As System.Object, e As System.Windows.Forms.LinkLabelLinkClickedEventArgs  ) Handles lblbarnamenevis.LinkClicked
        Process.Start("https://barnamenevis.org/showthread.p...م-به-گام")
    End Sub
#End Region


نکته:تغییراتی در ظاهر و نام کنترل ها داده شده است که تاپیری در کد ها ندارند لذا از ذکر آنها خوداری می شود:

توضیح کد:

تنها کد مفید و همین طور مهم ، روال رویداد کنترل تایمر می باشد

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

اولین خط فرم را از دید کاربر پنهان می نماید

دومین خط کنترل اجرا را به تابع Loader انتقال داده و منتظر آن می ماند

خط سوم نیز بعد اجری کامل متد مذکور وارد عمل شده و فرم Spalsh را به طور کامل خواهد بست!(در صورتی که متد Loader به برنامه خاتمه نداده باشد)

چرا ما ابتدا Me.Close را فرخوانی و سپس اقدام به اجرای متد نمی کنیم؟

توجه داشته باشد در حال حاضر تنها فرمی که در حال اجرا می باشد همین فرم است پس چنانچه ما اقدام به بستن این فرم نماییم بدلیل اینکه دیگر فرم در حال اجرایی وجود ندارد کل برنامه به صورت خودکار بسته و اجرا به اتمام خواهد رسید!


تغییر متد Loader:

حال که کنترل را به این متد واگذار می کنیم پس باید کد های مورد نیاز را به آن اضافه نماییم

متد Loader را به صورت زیر تغییر دهید:

    ''' <summary>
    ''' این متد اجرای برنامه را از صفحه نخست دریافت و ادامه کارها را بر عهده می گیرد
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub Loader()
        Try
            'انجام کار های پیش نیاز
            InitAdapters()
            'فراخوانی فرم دریافت اطلاعات پایگاه داده
     GetConnStr:
            Dim frmDb As New frmDatabase
            Dim ConnStr As String = frmDb.ShowDialog
            frmDb.Dispose()          
            If ConnStr = "" Then
                MsgBox("عملیات لغو شد!")
            Else
                MsgBox("OK" & vbNewLine & ConnStr)
            End If
            End
            'فراخوانی فرم ورود کاربران
 
            'فراخوانی فرم اصلی
 
            'انجام کار های مورد نیاز بعدی
        Catch ex As Exception
            MsgBox("خطا در بارگذاری برنامه!" & vbNewLine & ex.Message, _
                    MsgBoxStyle.Critical + MsgBoxStyle.MsgBoxRtlReading + MsgBoxStyle.MsgBoxRight, _
                    "خطای جدی")
            AddError(ex.Message)
            End
        End Try
    End Sub


توضیح کد ها:

متد InitAdapters() باید برای شما آشنا باشد! درست است این همان متدی است که در مرحله قبل ایجاد کردیم و وظیفه مقدار دهی و تنظیم ابزار های موجود در Dataset را دارد و همانجا هم وعده اجرای آن را در این متد داده بودیم!
خط بعدی یک برچسب پرش است است که در زمان توضیح فرم ورود کاربر بررسی و مورد استفاده قرار خواهد گرفت
در خط بعدی یک نمونه از فرم frmDatabase را ایجاد می کنیم

و اما مهمترین خط! در این خط نمونه فرم frmDatabase فرخوانی و نتیجه بازگشتی را که یک رشته اتصال خواهد بود را در متغیر ConnStr قرار می دهیم (در ادامه به بررسی عمل کرد فرم از نقطه شروع یعنی تابع ShowDialog خواهیم پرداخت)

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

نکته: چند خط مربوط به شرط جهت آزمایش و اطمینان از عملکرد صحیح برنامه تا این مرحله بوده و در مرحله بعدی حذف خواهد شد

ایجاد فرم دریافت رشته اتصال:

بالاخره به مهمترین فرم این مرحله از کار رسیدیم
این فرم دارای چند کنترل است که وظیفه دریافت اطلاعات اساسی رشته اتصالی که ما را به پایگاه داده متصل خواهد نمود دارند(بررسی بخش UI به خودتان واگذار می شود!)

کد های زیر را به فرم اضافه نمایید تا در ادامه به توضیح آنها بپردازیم:



''' <summary>
''' فرم مدیریت رشته اتصال به پایگاه داده
''' </summary>
''' <remarks></remarks>
Public Class frmDatabase
#Region "متغیر ها"
    ''' <summary>
    ''' نام پایگاه داده پیش فرض
    ''' </summary>
    ''' <remarks></remarks>
    Private ReadOnly DefultDbName As String = "WorkWithDb_Sample1"
    ''' <summary>
    ''' نام نگارش (نرم افزار) پایگاه داده
    ''' </summary>
    ''' <remarks></remarks>
    Private ReadOnly DbVerName As String = "WorkWithDb"
    ''' <summary>
    '''شماره نگارش معتبر پایگاه داده
    ''' </summary>
    ''' <remarks></remarks>
    Private ReadOnly DbVerNum As String = "1.0.0.0"
    ''' <summary>
    ''' سارنده رشته اتصال نتیجه
    ''' </summary>
    ''' <remarks></remarks>
    Private ReadOnly MyConnStrBldr As New SqlConnectionStringBuilder With {.DataSource = "Local", .InitialCatalog = DefultDbName, _
                                                                           .ConnectTimeout = 30, .UserID = "sa"}
#End Region
#Region "توابع و متد ها"
 
    ''' <summary>
    ''' نمایش فرم و دریافت اطلاعات از کاربر
    ''' </summary>
    ''' <param name="ConnStr">رشته اتصال جهت بارگذاری در کنترل ها</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shadows Function ShowDialog(Optional ConnStr As String = "") As String
        Try
            If ConnStr <> "" Then MyConnStrBldr.ConnectionString = ConnStr
   SetValues()
            Me.DialogResult = Windows.Forms.DialogResult.Cancel
            MyBase.ShowDialog()
            If Me.DialogResult = Windows.Forms.DialogResult.OK Then
                Return MyConnStrBldr.ConnectionString
            Else
                Return ""
            End If
        Catch ex As Exception
            Return False
        End Try
    End Function
    ''' <summary>
    '''مقدار دهی اولیه به کنترل ها
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub SetValues()
        Try
            cmbServer.Text = MyConnStrBldr.DataSource
            txtTimeout.Text = MyConnStrBldr.ConnectTimeout
            txtUserName.Text = MyConnStrBldr.UserID
        Catch ex As Exception
        End Try
    End Sub
 
    ''' <summary>
    ''' بررسی صحت اطلاعات ورودی توسط کاربر
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function IsValidData() As Boolean
        Try
            Me.ErrorProvider1.Clear()
            If cmbServer.Text.Trim = "" Then
                ErrorProvider1.SetError(Me.cmbServer, "نام سرور وارد نشده است")
                Throw New Exception("نام سرور وارد نشده است!")
            End If
            If txtTimeout.Text.Trim = "" OrElse Not IsNumeric(txtTimeout.Text) Then
                ErrorProvider1.SetError(Me.txtTimeout, "مهلت اتصال وارد نشده است")
                Throw New Exception("مهلت اتصال وارد نشده است!")
 
            End If
            If Not chkIntegrated.Checked Then
                If txtUserName.Text.Trim = "" Then
                    ErrorProvider1.SetError(Me.txtUserName, "نام کاربری وارد نشده است!")
                    Throw New Exception("نام کاربری وارد نشده است")
                End If
            End If
            Return True
        Catch ex As Exception
            MsgBox("اطلاعات ورودی نا معتبر!" & vbNewLine & _
                  ex.Message, MsgBoxStyle.Exclamation + _
                MsgBoxStyle.MsgBoxRtlReading + _
                 MsgBoxStyle.MsgBoxRight, "خطا")
            Return False
        End Try
    End Function
    ''' <summary>
    ''' بررسی صحت رشته اتصال
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function IsValidConnStr() As Boolean
        Try
            ErrorCache = ""
            If Not IsValidData() Then Return False
            MyConnStrBldr.ConnectTimeout = CInt(txtTimeout.Text)
            MyConnStrBldr.DataSource = cmbServer.Text.Trim
            MyConnStrBldr.IntegratedSecurity = chkIntegrated.Checked
            If chkIntegrated.Checked Then
                MyConnStrBldr.UserID = ""
                MyConnStrBldr.Password = ""
            Else
                MyConnStrBldr.UserID = txtUserName.Text.Trim
                MyConnStrBldr.Password = txtPassword.Text
            End If
            Return CheckConnetionString(ConnStr:=MyConnStrBldr.Connec  tionString, DbVerName:=DbVerName, DbVerNum:=DbVerNum)
        Catch ex As Exception
            MsgBox("خطا در بررسی اتصال به پایگاه داده!" & vbNewLine & _
                      ex.Message & vbNewLine & ErrorCache, MsgBoxStyle.Exclamation + _
                      MsgBoxStyle.MsgBoxRtlReading + _
                      MsgBoxStyle.MsgBoxRight, "اتصال موفق")
            Return False
        End Try
    End Function
#End Region
#Region "روال رویداد ها"
    Private Sub chkIntegrated_CheckedChanged(sender As System.Object, e As System.EventArgs) Handles chkIntegrated.CheckedChanged
        grpIntegrated.Enabled = Not chkIntegrated.Checked
    End Sub
    Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
        If IsValidConnStr() Then
            Me.DialogResult = Windows.Forms.DialogResult.OK
            Me.Close()
        End If
    End Sub
    Private Sub btnTestConn_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTestConn.Click
        If IsValidConnStr() Then
            MsgBox("اتصال به پایگاه داده با موفقت انجام شد", _
                    MsgBoxStyle.MsgBoxRtlReading + _
                    MsgBoxStyle.MsgBoxRight, "اتصال موفق")
        End If
    End Sub
    Private Sub btnCancel_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        Me.DialogResult = Windows.Forms.DialogResult.Cancel
        Me.Close()
    End Sub
#End Region



توضیح کد:

متغیر ها:

DefultDbName : با این متغیر نام پایگاه داده ای را که ساخته ایم نگه داری می کنیم تا از پرسش آن از کاربر جلو گیری شود
چون فعلا ما تنها با یک پایگاه داده (در هر سرور) کار خواهیم کرد یک نام ثابت برای آن اختیار و از همان جهت اتصال استفاده خواهیم نمود

DbVerName : خواهید دید که در هنگام اعتبارسنجی پایگاه داده از این متغیر جهت شناسایی اینکه پایگاه داده مورد نظر متعلق به این نرم افزار می باشد یا نه استفاده خوایم نمود (چون در وقعیت ممکن است چند نرم افزار را با ساختار پایگاه داده مشابه ایجاد نماییم با این کار از تداخل برنامه های خود جلو گیری می کنیم!)

DbVerNum : در هنگام اعتبار سنجی پایگاه داده پس از اینکه مشخص شد پایگاه داده متعلق به این نرم افزار است توسط این متغیر بررسی می گردد که نگارش نرم افزاری که پایگاه داده را ایجاد کرده با نگارش این نرم افزار یکسان باشند برای مثال نگارش 1 نرم افزار اقدام به باز کردن پایگاه داده  نگارش 2 نرم افزار ننماید

MyConnStrBldr : توسط این شیئ رشته اتصال موقتی را که در این فرم ایجاد و حلاجی خواهیم نمود را مدیریت و رشته اتصال آن را در صورت صحیح بودن به عنوان پاسخ به کدی که فرم را فراخوانی نموده برگشت می دهیم و در همین خط تعریف آن را مقدار دهی اولیه می نماییم

توابع و متد ها:

تابع ShowDialog:

Public Shadows Function ShowDialog() As String 

 در این جا ما یک تابع نوشته ایم که تابع اصلی مربوط به فرم  را پنهان و خود به جای آن معرفی خواهد نمود(البته ما می توانستیم یک تابع جدید ایجاد کنیم ولی این تابع چون مشهور و کار آن مشخص شده است برای چنین کاری بسیار مناسب می باشد)

If ConnStr <> "" Then MyConnStrBldr.ConnectionString = ConnStr

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

Me.DialogResult = Windows.Forms.DialogResult.Cancel 

این یک خاصیت است  که در همه فرم ها وجود دارد حتی MsgBox هم یکی از این نوع  را برمی گشت می دهد ما  بوسیله  مقدار آن خواهیم فهمید که کاربر دکمه تایید را انتخاب کرده یا انصراف(یا فرم را بسته است) ما می تونستیم یه متغیر جداگانه برای این کار تعریف کنیم ولی از لقمه آماده استفاده کردیم

MyBase.ShowDialog() 

در اصل باید این تابع اجرا می شد که خوب ما تابع خود را بجای آن معرفی کریم حالا باید خودمان آن را  اجرا کنیم و زمانی که اجرا شد پنجره باز می شود تا کاربر با پنجره مشغول کار و وارد کردن اطلاعات شود و این تابع در همین نقطه معوق و منتظر بسته شدن فرم خواهد بود

حال کاربر دکمه تایید رو کلیک می کند(که Windows.Forms.DialogResult.Cancelبا OK تنظیم می شود) یا انصرف می دهد که همان Cancel در جای خود باقی می ماند
پس پنجره با ضربدر خودش یا Me.Close بسته می شود

کنترل به تابع این تابع بر می گردد و خط بعدی اجر میشود

If Me.DialogResult = Windows.Forms.DialogResult.OK Then
Return MyConnStr.ConnectionString
Else
Return ""
End If 

بررسی میشود کدام دکمه زده شده و نتیجه برگشت داده می شود

SetValuse : وظیفه مقدار دهی اولیه به کنترل ها را دارد


IsValidData: این تابع صحت اطلاعات وارد شده توسط کاربر را بررسی می نماید که با توجه به متن های پیغام خطا ها می شود عمل کرد آن را فهمید پس نیازی به توضیح اضافی ندارد

IsValidConnStr: این تابع ابتدا صحت اطلاعات را توسط IsValidData بررسی و سپس مقادیر را در MyConnStr در وارد می کند

سپس رشته اتصال را برای تابع CheckConnetionString جهت بررسی می فرستد و نتیجه همان تابع را برگشت می دهد( این تابع در ادمه اضافه و بررسی خواهد شد)

روال رویداد ها:
این چند خط کد ساده را هم خود تجزیه تحلیل نمایید (تمرین کنید زیرا رفته رفته و زمانی که چیز های بیشتری آموختید کد های بیشتری نیز به خود شما محول خواهد شد!)

CheckConnetionString :
این تابع جهت بررسی یک رشته اتصال است

ابتدا کد های مربوط به تابع را به کلاس SqlMgr اضافه نمایید 
نکته:ممکن است این سوال پیش آید که چرا ما این تابع را در همان فرم ایجاد و استفاده نمی نماییم؟
توجه داشته باشید یکی از اصول لایه بندی این است که لایه UI نباید شامل هیچ کدی باشد که اقدام به ارتباط *مستقیم* با پایگاه داده می نماید
در حالی که این تابع چیزی فراتر از آن نیز می باشد به طوری که برای خود یک Connection مجزا می سازد و مستقیما دستورات TSQL را نیز به کار می برد(از Dataset استفاده نمی کند!) درنتیجه ما به هیچ وجه مجوز استفاده از آن را در داخل یک فرم نخواهیم داشت!


    ''' <summary>
    ''' بررسی توانایی اتصال به پایگاه داده توسط یک رشته اتصال و اعتبار سنجی پایگاه داده
    ''' </summary>
    ''' <param name="ConnStr">رشته اتصال</param>
    ''' <param name="DbVerName">نام نگارش(نام نرم افزار یجاد کننده) پایگاه داده - جهت اعتبار سنجی</param>
    ''' <param name="DbVerNum">شماره نگارش(نگارش نرم افزار یجاد کننده) پایگاه داده - جهت اعتبار سنجی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CheckConnetionString(ConnStr As String, DbVerName As String, DbVerNum As String) As Boolean
        Using myConn As New SqlConnection(ConnStr)
            Dim TSQL As String = ""
            TSQL = "SELECT DbVer FROM tblDbVer"
            Dim SqlRes = SqlFillTable(TSQL, Conn:=myConn)
            myConn.Dispose()
            If SqlRes Is Nothing Then Throw New Exception("عدم توانایی در دریافت اطلاعات")
            If SqlRes.Rows.Count = 0 Then
                Throw New Exception("اتصال به سرور انجام شد. ولی اطلاعاتی در یافت نگردید")
            End If
            Dim Ver = CStr(SqlRes.Rows(0)(0)).Split("|")
            If Ver.Count < 2 Then
                Throw New Exception("اتصال به سرور انجام شد. ولی اطلاعاتی در یافتی معتبر نمی باشد")
            End If
            If Ver(0) <> DbVerName Then
                Throw New Exception("اتصال به سرور انجام شد. ولی پایگاه داده مورد نظر متعلق به این نرم افزار نمی باشد")
            End If
            If Ver(1) <> DbVerNum Then
                Throw New Exception("اتصال به سرور انجام شد. ولی پایگاه داده مورد نظر متعلق به نگارش دیگری از این نرم افزار نمی باشد")
            End If
        End Using
        Return True
    End Function



توضیح کد:

در ابتدا  یک Connection جهت اجرای دستورت ساخته می شود
نکته:چون هنوز این رشته اتصال بررسی نشده و صحت آن معلوم نیست و همچنین به تایید نهایی کاربر نرسیده است ما نمی توانیم آن را به اتصال عمومی(GlobalConnection) نسب دهیم زیرا این کار کل نرم افزار را تحت تاثیر قرار می دهد و نه ما قصد این کار داریم و نه کار صحیحی می باشد!

در خط بعد یک دستور TSQL که اطلاعات جدول DbVer را بدست خواهد آورد را ایجاد می کنیم

سپس با استفاده از تابع SqlFillTable دستور را اجرا و نتیجه را به صورت یک DataTable دریافت می نماییم(این تابع نیز مورد بررسی قرار خواهد گرفت)
توجه کنید ما علاوه بر دستور TSQL یک Connection نیز به این تابع ارسال می نماییم تا تابع در اجرای دستورات به جای اتصال عمومی از اتصال سفارشی ما استفاده نمایید(که امکانات این کار را نیز محیا کرده ایم)

نکته:در این کد نیز پیغام خطا خود راهنمای کد هستند

بعد از اجرای دستور چنانچه خطایی رخ نداده باشد به بررسی اطلاعات خواهیم پرداخت (برگشت مقدار Nothing نشان دهنده بروز خطا می باشد!)
پس اگر مقدار Nothing برگشت داده نشده نتیجه می گیریم که اتصال به سرور انجام شده و ساختار پایگاه داده نیز با ساختار مورد نظر ما مطابقت دارد(دارای جدولی به نام DbVer می باشد!)

حال بررسی می نماییم که آیا رکوردی از جدول گرفته شده است یا نه 

نکته:ما در زمان ایجاد پایگاه داده یک رکورد به این جدول اضافه می نماییم تا با همان رکورد اعتبار سنجی های بعدی انجام شود
این رکورد داری یک ستون است که مقدار آن توسط یک خط عمودی (*|*) به دوقسمت تقسیم می شود. قسمت اول نشانگر نام نگارش(نرم افزار ایجاد کننده) و قسمت دوم نگارش آن است(این مطالب در قسمت متغیر ها توضیح داده شد است)

البته ما می توانستیم دو ستون جهت این کار ایجاد نماییم ولی با اینکار ما یک اعتبار سنجی هم انجام می دهیم که آیا اطلاعات با فرمت مختص به ما نوشته شده است یا نه

اطلاعات دریافتی را جهت جدا سازی با *|* تقسیم می نماییم و اگر تقسیم انجام نشد نتیجه می گیریم که فرمت اطلاعات صحیح نبوده و پایگاه داده رد می شد!

حالا با مقاسه دو قسمت با متغیر های خود نگارش پایگاه داده را بررسی و در صورت صحیح بود ن پایگاه داده و در نتیجه رشته اتصال را مورد قبول خواهیم نمود


تابع SqlFillTable :

همان طور که در قسمت توضیحات آن نیز قابل مشاهده است این تابع یک دستور TSQL که معمولا با دستور SELECT ایجاد می شود را دریافت و توسط آن پرس و جو  را بر روی پایگاه داده اجرا و داخل یک شیئ DataTable قرار داده و آن را برگشت می دهد
نکته:
مشاهده می شود که بجز دستور TSQL باقی پارامتر ها به صورت اختیاری تعریف شده تا *در صورت نیاز* هر یک از اشیا دخیل در عملیات قابلیت سفارشی سازی داشته باشند

نمونه ای از این سفارشی سازی را در همین ابتدای کار و در تابع بالا مشاهده نموده و به توانایی بسیار بالای این توابع و فایده های آنها پی بردید

سفارشی سازی هم به این صورت اعمال می شود که اگر فراخوان اشیا را برای تابع ارسال نمایید از همان و در غیر این صورت یک نمونه جدید از آن جهت استفاده ایجاد می شود

جهت کامل شدن عملیات از صفحه Properties پروژه Startup Form را با frmAppSplashتنظیم و Form1 را که در ابتدای ایجاد پروژه ایجاد شده بود حذف کنید! 

(در ادامه کار به بررسی ورود کاربران خواهیم پرداخت)

----------


## فرید نجفلو

*ایجاد فرم اعتبار سنجی و  ورود کاربر:*

بعد از دریافت رشته اتصال نوبت به اعتبار سنجی و ورود کاربر به برنامه می رسد

توجه:

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

1-اعتبار سنجی در سطح سرور :
این همان مرحله دریافت رشته اتصال بود به این صورت که اگر شخصی نام کاربری ، کلمه عبور و همچنین مجوزی در سرور نداشته باشد نخواهد توانست به سرور متصل شود و در نتیجه رشته اتصالی هم ایجاد نخواهد شد و در همان مرحله متوقف می شود 
با این تفاسیر مرحله دریافت اتصال نیز خود به نوعی اعتبار سنجی کاربر به حساب می آید

2-اعتبار سنجی در سطح نرم افزار:
این اعتبار سنجی در واقع اعتبار سنجی در سطح پایگاه داده محسوب می شود. زیرا در این مرحله از اعتبار سنجی از اطلاعات موجود در یک پایگاه داده استفاده می شود که توسط نرم افزار ایجاد شده است

پس ما در این مرحله و در این فرم ، اعتبار سنجی نوع دوم(نرم افزار یا پایگاه داده) را انجام خواهیم داد

افزودن فرم:
یک فرم جدید به نام frmLogin به پروژه اضافه نمایید
بعد از افزودن کنترل ها(طبق پروژه نمونه) کد های زیر را به فرم اضافه نمایید:

Public Class frmLogin
#Region "متغیر ها"
    ''' <summary>
    '''رشته اتصال سفارشی جهت ارتباط با پایگاه داده
    ''' اگر توسط فراخوان ارسال نشود از رشته اتصال عمومی استفاده خواهد شد
    ''' </summary>
    ''' <remarks></remarks>
    Private CostumConnStr As String = ""
    ''' <summary>
    ''' تعداد دفعات ورود اطلاعات نادرست
    ''' </summary>
    ''' <remarks></remarks>
    Private TriedTimes As Integer = 0
    ''' <summary>
    ''' حداکثر مجاز دفعات ورود اطلاعات نادرست
    ''' </summary>
    ''' <remarks></remarks>
    Private Const MaxTryTime As Integer = 5
#End Region
#Region "توابع و متد ها"
    ''' <summary>
    ''' نمایش فرم و دریافت اطلاعات از کاربر
    ''' </summary>
    ''' <param name="ConnStr">رشته اتصال جهت بارگذاری کنترل </param>
    ''' <returns>برگشت نام کاربر</returns>
    ''' <remarks></remarks>
    Public Shadows Function ShowDialog(Optional ConnStr As String = "") As String
        Try
            CostumConnStr = ConnStr
            Me.DialogResult = Windows.Forms.DialogResult.Cancel
            MyBase.ShowDialog()
            If Me.DialogResult = Windows.Forms.DialogResult.OK Then
                Return txtUserName.Text
            Else
                Return ""
            End If
        Catch ex As Exception
            Return ""
        End Try
    End Function
    ''' <summary>
    ''' بررسی صحت اطلاعات ورودی توسط کاربر
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function IsValidData() As Boolean
        Try
            Me.ErrorProvider1.Clear()
            If txtUserName.Text.Trim = "" Then
                ErrorProvider1.SetError(txtUserName, "نام کاربری وارد نشده است!")
                Throw New Exception("نام کاربری وارد نشده است")
            End If
            If txtPassword.Text.Trim = "" Then
                ErrorProvider1.SetError(txtPassword, "کلمه عبور وارد نشده است")
                Throw New Exception("کلمه عبور وارد نشده است")
            End If
            Return True
        Catch ex As Exception
            MsgBox("اطلاعات ورودی نا معتبر!" & vbNewLine & _
                    ex.Message, MsgBoxStyle.Exclamation + _
                    MsgBoxStyle.MsgBoxRtlReading + _
                    MsgBoxStyle.MsgBoxRight, "خطا")
            Return False
        End Try
    End Function
    ''' <summary>
    ''' بررسی مشخصات و مجوز ورود کاربر
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function TestUser() As Boolean
        Try
            ErrorCache = ""
            If Not IsValidData() Then Return False
            If Not UserMgr.CheckUserForLogin(txtUserName.Text, txtPassword.Text, CostumeConnStr:=CostumConnStr) Then
                Throw New Exception("نام کاربری ، کلمه عبور یا مجوز معتبر نمی باشد")
            End If
            Return True
        Catch ex As Exception
            MsgBox("خطا در ورود" & vbNewLine & _
                    ex.Message & vbNewLine & ErrorCache, _
                    MsgBoxStyle.Exclamation + _
                    MsgBoxStyle.MsgBoxRtlReading + _
                    MsgBoxStyle.MsgBoxRight, "خطا")
            TriedTimes += 1
            Return False
        End Try
    End Function
#End Region
#Region "روال رویداد ها"
    Private Sub btnCancel_Click(sender As System.Object, e As System.EventArgs) Handles btnCancel.Click
        Me.DialogResult = Windows.Forms.DialogResult.Cancel
        Me.Close()
    End Sub
    Private Sub btnOK_Click(sender As System.Object, e As System.EventArgs) Handles btnOK.Click
        If TestUser() Then
            Me.DialogResult = Windows.Forms.DialogResult.OK
            Me.Close()
        ElseIf TriedTimes >= MaxTryTime Then
            Me.DialogResult = Windows.Forms.DialogResult.Cancel
            Me.Close()
        End If
    End Sub
#End Region

End Class

توضیح کد:
تنها قسمتی که ممکن ایت نیز به توضیح داشته باشد تابع TestUser است:

این تابع ابتدا مخزن خطا ها را خالی و سپس اقدام به بررسی مقادیر وردی می کند (که توضیحی هم نیاز ندارند همانند فرم انتخاب پایگاه داده می باشد)
در ادامه  اطلاعات را به تابع UserMgr.CheckUserForLogin فرستاده و تمام کار ها را به آن محول می کند (تمام متد ها و توابع استفاده شده به ترتیب کاربرد توضیح داده می شوند)
نکته:باز تاکید می نماییم که به دلیل لزوم جدا سازی عملیات هر یک از لایه ها و با توجه به اینکه تابع UserMgr.CheckUserForLogin به طور مستقیم اقدام به ارتباط با پایگاه داده خواهد نمود اجازه حضور در لایه رابط کاربری(PL) نخواهد داشت و کلاس UserMgr نیز به رشد خود ادامه داده و به لایه میانی (BLL) کار با کاربران تبدیل خواهد شد!

تابع UserMgr.CheckUserForLogin:

همان طور که از نام تابع نیز قابل استنباط است این تابع در داخل کلاس UserMgr قرار دارد پس ابتدا باید کلاس مذکور ایجاد شود
کلاس جدیدی به نام UserMgr به پوشه Calss\DB اضافه نمایید
حال کد های زیر را نیز به کلاس اضافه نمایید:

    ''' <summary>
    ''' لیست کلیه مجوز های موجود - جهت دسترسی سریع و جلوگیری
    ''' از اشتباهات در هنگام برنامه نویسی
    ''' </summary>
    ''' <remarks></remarks>
    Public Enum Premissions As Integer
        LoginToApp = 0

    End Enum


Premissions یک نوع شمارشی می باشد و وظیفه آن نگه داری نام و کد کلیه مجوز هایی است که در نرم ام افزار موجود و قابل اختصاص به کاربران می باشد و پیش نیازی نیز جهت تابع CheckUserForLogin است

دلایل وجودی Premissions : 

1-عدم اشتباه در تخصیص و بررسی مجوزات: به این صورت که زمانی که در کد کدنویسی قصد اختصاص یا کنترل مجوز به کاربر خواهیم داشت از اعضای این شمارش گر استفاده خواهیم نمود به عنوان مثال به جای استفاده از *0* برای کد مجوز ورود به نرم افزار از *Premissions*.*LoginToApp* استفاده و خود کاملا متوجه خواهیم بود که در حال اختصاص کدام مجوز به کاربر هستیم

2-سهولت کد نویسی

3-خوانایی بالا  و دیباگ راحت تر

پس از این به بعد ما به هر مجوزی نیاز پیدا کردیم و قصد افزودن آن را داشتیم ابتدا آن را به پایگاه داده و جدول tblPremissionList و سپس به این نقطه از کد اضافه خواهیم نمود

نکته:افزایش ، کاهش و ویرایش مجوزات موجود فقط در زمان طراحی خواهد بود و این کار بعد از اتمام و عرضه نهایی و هنگام اجرا و استفاده از نرم افزار صورت نخواهد گرفت(در واقع در DesignTime خواهد بود نه RunTime)

حال تابع CheckUserForLogin را به کلاس اضافه نمایید:


    ''' <summary>
    ''' بررسی صحت نام کاربری و کلمه عبور
    ''' </summary>
    ''' <param name="UserName">نام کاربری وارد شده</param>
    ''' <param name="PassWord">کلمه عبور وارد شده</param>
    ''' <param name="CostumeConnStr">رشته اتصال سفارشی - غیر از رشته اتصال عمومی</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CheckUserForLogin(ByVal UserName As String, _
                                                 ByVal PassWord As String, _
                                                 Optional ByVal CostumeConnStr As String = "") As Boolean
        UserName = UserName.Trim.ToLower
        Dim encrptPassword As String = Strings.Left(StringCoding(PassWord), 1000)
        Dim TSQL As String = "SELECT Password FROM tblUser WHERE LOWER(UserName) = " & _
                                PrepStrToSql(UserName)
        Dim SqlRes As DataTable
        Using MyConn As New SqlConnection(CostumeConnStr)
            SqlRes = SqlFillTable(TSQL, Conn:=IIf(CostumeConnStr <> "", MyConn, Nothing))
            If SqlRes Is Nothing OrElse SqlRes.Rows.Count <= 0 Then
                Throw New Exception("نام کاربری یا کلمه عبور معتبر نمی باشد")
            End If
            If CStr(SqlRes.Rows(0).Item(0).ToString) <> encrptPassword Then
                Throw New Exception("نام کاربری یا کلمه عبور معتبر نمی باشد")
            End If
            TSQL = ""
            TSQL = "SELECT * FROM tblUserPremission WHERE LOWER(UserName) = '{0}' AND Premission = {1} "
            TSQL = String.Format(TSQL, UserName, CInt(Premissions.LoginToApp))
            SqlRes = Nothing
            SqlRes = SqlFillTable(TSQL, Conn:=IIf(CostumeConnStr <> "", MyConn, Nothing))
            If SqlRes Is Nothing OrElse SqlRes.Rows.Count <= 0 Then
                Throw New Exception("شما مجوز ورود را ندارید")
            End If
            MyConn.Dispose()
        End Using
        Return True
    End Function


توضیح کد:

این تابع دو وظیفه مهم بر عهده دارد:
1-بررسی صحت نام کاربری و کلمه عبور وارد شده
2-بررسی وجود مجوز ورود به نرم افزار(پایگاه داده) کاربر

Dim encrptPassword As String = Strings.Left(StringCoding(PassWord), 1000)

بدلیل اینکه قرار دادن کلمه عبور به همان صورت در پایگاه داده کار صحیحی از جهت اصول امنیتی نمی باشد ما کلمه عبور تمام کاربران را به صورت کدگذاری شده(توسط توسط تابع StringDecoding ) در پایگاه داده ذخیره می شود

پس در این خط ما کلمه عبور دریافتی را به کدگذاری شده تبدیل و اقدام مقایسه آن با کلمه عبور پایگاه داده (که آن نیز کدگذاری شده) خواهیم نمود
دلیل استفاده از 1000 کاراکتر اول (در صورت عبور از این اندازه) این است که چون حداکثر تعداد کاراکترهای مجاز فیلد Password در جدول tblUser را 1000 کارکتر قرار داده ایم پس در هنگام قرار دادن پسورد کاربر در داخل آن کلمه عبور کد گذاری و فرستاده شده بیش از 1000 کاراکتر نبوده است

چون ما قصد بازگردانی کلمه عبور اصلی را نداریم و فقط  مقادیر کد گذاری شده با هم مقایسه می شوند این کوتاه کردن رشته موجوب بروز مشکل مقایسه نخواهد شد(اگر کلمه عبور اصلی و دریافت شده برابر باشند هر دو به یک اندازه یعنی 1000 کارکتر بریده شده اند)

در چند خط بعدی اقدام به مقایسه نام کاربری و کلمه عبور کرده و از کاربر اهراز هویت به عمل آمده و کلمه عبور نیز بررسی می شود
تنها خط قابل توضیح  قسمتی از خط زیر می باشد

            SqlRes = SqlFillTable(TSQL, Conn:=IIf(CostumeConnStr <> "", MyConn, Nothing))

و درواقع این قسمت:

IIf(CostumeConnStr <> "", MyConn, Nothing)

تابع IIf  یک شرط تک خطی(InLine) می باشد و به این صورت عمل می کند که اگر شرط در پارامتر اول صحیح(True) باشد مقدار پارامتر دوم و در غیر این صورت مقدار پارامتر سوم برگشت داده خواهد شد

دلیل استفاده:

همان طور که ملاحظه می شود این تابع یک پارامتر اختیاری دارد که رشته اتصال پایگاه داده می باشد اگر این پارامتر ارسال شود تابع باید یک اتصال سفارشی ساخته و به تابع SqlFillTable ارسال نماید و در غیر این صورت نیاز به ارسال پارامتر نخواهد بود(مقدار پارامتر Nothing پیش فرض خواهد بود)

حال ما یک اتصال سفارشی ساخته ایم (MyConn) ولی ممکن است از آن استفاده کنیم یا نکنیم و این بستگی به آن پارامتر اختیاری رشته اتصال دارد

پس ما در این خط  بررسی می نماییم که اگر پارامتر ارسال شده است(رشته خالی نخواهد بود) پارامتر اول یعنی اتصال سفارشی یا در غیر این صورت از Nothing (مقدار پیش فرض و نشانگر عدم ارسال پارامتر) را به عنوان مقدار Conn به تابع SqlFillTable فرستاده شود

نکته: اگر خود بررسی نمایید و روش های دیگر را نیز امتحان کنید درخواهید یافت که این نصف خط کد کار چنین خط کد را انجام داده است

نکته جالب: نکته جالبی که در این بین وجود دارد این است که تا این مرحله تمام ارتباطات ما با پایگاه داده با روش مستقیم TSQL و همچنین در حالات استثنا بوده است که امید واریم این امر موجب سردرگمی و سخت شدن مراحل ابتدایی کار نشده باشد

در ادامه کار و بعد از شناسایی کاربر و تایید کلمه عبور اقدام به بررسی مجوز ورود می شود

نکنه1: تا این نقطه ما تنها نیاز به یک مجوز یعنی مجوز ورود داریم که هم به پایگاه داده و هم به نوع شمارشی Premissions افزوده شده است

نکته 2:چون در اولین اجرای نرم افزار هنوز کاربری وارد برنامه نشده و در نتیجه هیچ کاربری هم ایجاد نشده جهت اینکه حداقل یک کاربر جهت ورود وجود داشته باشد اقدام به افزودن دستی یک کاربر به پایگاه داده به عنوان کاربر اولیه و پیش فرض می نماییم که این کاربر داری *تمام مجوزات موجود* است تا بتواند به عنوان مدیر اولیه به تمام قیمت های نرم افزار دسترسی و تنظیمات اولیه را انجام دهد(همانند کاربر *Sa* در  SQL Server  و *admin* در اکسس)

نکته 3: شما باید به استفاده کننده از نرم افزار هشدار بدهید که این کاربر در اولین فرصت باید حذف یا کلمه عبور آن تغییر یابد

نکته 4: نام کاربری پیش فرض و همچنین کلمه عبور آن باید به اطلاع کاربر نهایی برسد (به عنوان مثال در راهنمای نرم افزار ، جلد روی CD ، صفحه دانلود نرم افزار و ...) همان طور که ما در نکته بعدی به شما اطلاع می دیم

نکته 5: نام کاربری پیشفرض ما *admin*  و کلمه عبور آن *1* (عدد *یک*) می باشد


توابع کدگذاری:

جهت مسائل امنیتی ما نیاز خواهیم داشت تا بعضی اطلاعات خود را به صورت رمزنگاری شده ذخیره و در هنگام استفاده آنها را رمز گشایی کنیم 
مواردی از این قبیل همین کلمه عبور کاربر و رشته اتصالی خواهد بود که اقدام به ذخیره و بازیابی آن در اجرا های بعدی خواهیم نمود می باشند

جهت رسیدن به منظور دو تابع را در کلاس Publics به صورت زیر اصلاح کنید:

    ''' <summary>
    ''' رمز گذاری یک رشته متنی
    ''' </summary>
    ''' <param name="InputStr">رشته جهت کدگذاری</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function StringCoding(InputStr As String) As String
        Try
            Dim Key As String = "https://barnamenevis.org/member.php?243869-Farid.N"
            Dim DES As New System.Security.Cryptography.TripleDESCryptoServic  eProvider
            Dim MD5 As New System.Security.Cryptography.MD5CryptoServiceProvi  der
            DES.Key = MD5.ComputeHash(System.Text.UTF8Encoding.UTF8.GetB  ytes(Key))
            DES.Mode = System.Security.Cryptography.CipherMode.ECB
            Dim Buffer As Byte() = System.Text.UTF8Encoding.UTF8.GetBytes(InputStr)
            Return Convert.ToBase64String(DES.CreateEncryptor().Trans  formFinalBlock(Buffer, 0, Buffer.Length))
        Catch ex As Exception
            Return InputStr
        End Try
    End Function
    ''' <summary>
    ''' رمزگشایی یک رشته کد شده
    ''' </summary>
    ''' <param name="InputStr">رشته کد شده</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function StringDecoding(InputStr As String) As String
        Try
            Dim Key As String = "https://barnamenevis.org/member.php?243869-Farid.N"
            Dim DES As New System.Security.Cryptography.TripleDESCryptoServic  eProvider
            Dim MD5 As New System.Security.Cryptography.MD5CryptoServiceProvi  der
            DES.Key = MD5.ComputeHash(System.Text.UTF8Encoding.UTF8.GetB  ytes(Key))
            DES.Mode = System.Security.Cryptography.CipherMode.ECB
            Dim Buffer As Byte() = Convert.FromBase64String(InputStr)
            Return System.Text.UTF8Encoding.UTF8.GetString(DES.Create  Decryptor().TransformFinalBlock(Buffer, 0, Buffer.Length))
        Catch ex As Exception
            Return InputStr
        End Try
    End Function


نکته :

ما از توضیح این دو تابع  به دو دلیل صرف نظر نموده و به این اکتفا می کنیم که تابع اول یک رشته دریافت و آن را با یک کلید رمزنگاری و تابع دوم رشته ای را که توسط تابع اول رمزنگاری شده ، رمزگشایی می کند:

1-عملکرد و توضیح این توابع خارج از بحث است
2-این واقیت وجود داد که هر نرم افزار باید الگوریتم رمزنگاری خود را داشته باشد و این به عهده خود شماست که آن را ایجاد نمایید

اصلاح متد Loader:

حال که فرم ورود کاربران آماده شد نوبت به بکارگیری از آن می رسد و اولین نقطه ای که باید مورد استفاده قرار گیرد متد Loader است

پس متد را به صورت زیر تغییر دهید:

    ''' <summary>
    ''' این متد اجرای برنامه را از صفحه نخست دریافت و ادامه کارها را بر عهده می گیرد
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub Loader()
        Try
            'انجام کار های پیش نیاز
            InitAdapters()
            'فراخوانی فرم دریافت اطلاعات پایگاه داده
            Dim frmDb As New frmDatabase
GetConnStr:
            Dim ConnStr As String = frmDb.ShowDialog(ConnStr)
            If ConnStr = "" Then
                End
            End If
            'فراخوانی فرم ورود کاربران
            Dim frmlgn As New frmLogin
            Dim User As String = frmlgn.ShowDialog(ConnStr:=ConnStr)
            frmlgn.Dispose()
            If User = "" Then
                GoTo GetConnStr 'try agin
            End If
            CurrentUserName = User
            GlobalConnectionString.ConnectionString = ConnStr
            GlobalConnection.ConnectionString = ConnStr
            frmDb.Dispose()
            'فراخوانی فرم اصلی
            FrmMain.Show()
            'انجام کار های مورد نیاز بعدی
        Catch ex As Exception
            MsgBox("خطا در بارگذاری برنامه!" & vbNewLine & ex.Message, _
                    MsgBoxStyle.Critical + MsgBoxStyle.MsgBoxRtlReading + MsgBoxStyle.MsgBoxRight, _
                    "خطای جدی")
            AddError(ex.Message)
            End
        End Try
    End Sub


همنطور که مشاهده می شود ابتدا اقدام به دریافت کاربر تایید شده از فرم ورود کاربران نموده در صورتی که کاربر انصراف داده یا تعداد مجاز اشتباهات وی از حد مجاز فراتر رفته باشد وی را دوباره به صفحه انتخاب پایگاه داده راهنمایی می کنم تا در صورت لزوم پایگاه داده خود را مجددا معرفی و فرصتی جهت تلاش مجدد برای ورود داشته باشد
در صورتی که کاربر نام کاربری و کلمه عبور صحیح و مجوز ورود نیز اعطا شده باشد فرم ورود کاربر،  نام کاربری را برای ما ارسال خواهد کرد 
و متد Loader هم نام کاربر فعال شده را جهت استفاده های بعدی در یک متغیرر عمومی ذخیره می کند
سپس نوبت به مهترین نقطه یعنی تنظیم رشته اتصال عمومی رسیده و بعد از این عمل کاربر به فرم اصلی هدایت می شود

درنهایت این متد بعد از اتمام وظیفه خطیر خود عملیات را به پایان می رساند و فرم Splash نیز که تا این لحظه منتظر بود وارد عمل شده و فرم خود را که مخفی کرده بود به طور کامل بسته و از بین می برد(البته یک کار دیگر باقی مانده است که باید انجام شود و آن ذخیره رشته اتصال برای استفاده در اجرا های بهدی می باشد که در مطلب بعدی اضافه خواهد شد)

ایجاد فرم اصلی:

همانطور که ملاحظه نمودید در نهایت کاربر به یک فرم اصلی که معرف نرم افزار ما می باشد هدایت می گردد
پس ما باید این فرم را به برنامه اضافه نماییم
پنجره Add New Item را باز و یک فرم MDIParent با نام FrmMain را به پوشه Form\App  اضافه نمایید
حال تمام کنترل ها و کدهای این فرم را حذف نمایید(به مرور خود این فرم را تکمیل خواهیم نمود)

در نهایت پروژه خود را ذخیره نمایید

*نکته مهم:*

همانطور که قبلا نیز اشاره شد دو نوع اعتبار سنجی (سطح سرور و سطح پایگاه داده) با هم یک امنیت قدرتمند ایجاد می کنند
اما چنانچه سرور در حالت MixedMode نبوده و از اعتبار سنجی ویندوزی استفاده نماید یکی از مراحل اعتبار سنجی از بین می رود که به نوبه خود اعتبار سنجی سطح پایگاه داده را نیز به خطر می اندازد
به این صورت که در صورت نبود امنیت برای سرور هر کسی می تواند با یک ابزار ساده کار با پایگاه داده وارد سرور شده و جدول های مربوط به کاربران را به راحتی دست کاری نموده و به خود مجوزات در سطح مدیر را اضافه نمایید!!!

*پس همیشه سرور خود را با حالت MixedMode و نام کاربری و کلمه عبور مستحکم ، امن نگاه دارید*

----------


## فرید نجفلو

*ذخیره رشته اتصال:*

همانطور که می دانید این کار صحیحی نیست که ما در هر اجرا از برنامه اقدام به دریافت تمام مشخصات سرور پایگاه داده از کاربر نماییم در حالی که وی در اجرای قبلی آنها را ارائه نموده بود
پس ما می توانیم رشته اتصالی را که از کاربر دریافت نموده و صحت آن نیز بررسی شده است را در مکانی ذخیره و در اجرای بعدی آن را بازیابی و خود اقدام به تنظیم مقادیر در کنترل های فرم "انتخاب پایگاه داده" نماییم که ممکن است با این کار فقط نیاز به تایید آن توسط کاربر داشته باشیم(کلیک یک دکمه و تمام!)

رشته اتصال را کجا ذخیره کنیم؟
مکان های متعددی را جهت این کار پیش رو دارید مثلا ریجستری ، یک فایل TXT ، یک فایل XML ، یک فایل INI و...

ولی گزینه بهتری پیشرو دارید و آن استفاده از تنظیمات یا Settings برنامه است که قدرتی فوق العاده و کاربردی راحت دارد . ما نیز از همین امکانات بهره خواهیم برد

برای ادامه ازSolution Explorer بر روی گره (نام) پروژه خود کلیک راست و گزینه Properties را انتخاب نمایید
از پنجره باز شده برگ Settings را باز نمایید
حال از گرید سمت راست در ستون Name اولین سطر خالی مقدار LastConnectionString را وارد نمایید
در ستون Type گزینه String و در ستون Scope گزینه User را انتخاب نمایید . ستون آخر را نیز بدون تغییر رها کنید

پروژه خود را ذخیره کنید 
با این کار شما یک محل برای ذخیره یک رشته ایجاد نمودید

رشته اتصال را چگونه ذخیره کنیم؟

بهترین روش برای این کار داشتن دو تابع جداگانه برای ذخیره و بازیابی رشته اتصال می باشئد
پس کد های زیر را به کلاس Publics اضافه نمایید

 ''' <summary>
''' ذخیره رشته اتصال در تنظیمات نرم افزار
''' </summary>
''' <param name="ConnStr"></param>
''' <remarks></remarks>
Public Shared Sub SaveLastConnectionString(ConnStr As String)
Try
Dim tmpConnStrBldr As New SqlConnectionStringBuilder(ConnStr)
tmpConnStrBldr.Password = ""
My.Settings.LastConnectionString = StringCoding(tmpConnStrBldr.ConnectionString)
My.Settings.Save()
Catch ex As Exception
End Try
End Sub
''' <summary>
''' بازیابی رشته اتصال ذخیره شده در اجرای قبل
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetLastConnectionString() As String
Try
Dim ConnStr As String = My.Settings.LastConnectionString
If ConnStr = "" Then Return ""
Return StringDecoding(ConnStr)
Catch ex As Exception
Return ""
End Try
End Function

توضیح تابع SaveLastConnectionString یک رشته اتصال را دریافت و آن را رمزگذاری کرده و در Settings برنامه و محلی که ما قبلا ایجاد نمودیم ذخیره می کند

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

تابع GetLastConnectionString نیز مقدار ذخیره شده قبلی را بازیابی رمزگشایی و برگشت می دهد. چنانچه مقدار موجود در Settings خالی باشد نتیجه می گیریم که فعلا رشته اتصالی ذخیره نشده و در نتیجه نیاز به رمز گشایی هم نمی باشد و یک رشته خالی برگردانده می شود

رشته اتصال چه موقع ذخیره و بازیابی می شود؟

هدف ما از ذخیره سازی رشته اتصال راحتی کاربر نهایی می باشد و ما چیزی را ذخیره می کنیم که وی به ما داده است 
با توجه به اینکه ما رشته اتصال را در داخل متد Loader دریافت و در همان جا بررسی و مورد تایید قرار می دهیم بهترین مکان برای عملیات ذخیره می باشد
همچنین ما در داخل همین متد است که فرم "انتخاب پایگاه داده" را فراخوانی و رشته اتصالی به آن ارسال می نماییم با این تفاسیر بازیابی رشته اتصال هم در همین متد صورت می پذیرد
هرچند بعد ها ممکن است در مکان (های) دیگری نیز اقدام به این کار نماییم ولی اولین نقطه و مهمترین شان همین متد می باشد

پس متد Loder را به شکل زیر اصلاح نمایید:

 ''' <summary>
''' این متد اجرای برنامه را از صفحه نخست دریافت و ادامه کارها را بر عهده می گیرد
''' </summary>
''' <remarks></remarks>
Public Shared Sub Loader()
Try
'انجام کار های پیش نیاز
InitAdapters()
'فراخوانی فرم دریافت اطلاعات پایگاه داده
Dim ConnStr As String = GetLastConnectionString()
Dim frmDb As New frmDatabase
GetConnStr:
ConnStr = frmDb.ShowDialog(ConnStr)
If ConnStr = "" Then
End
End If
'فراخوانی فرم ورود کاربران
Dim frmlgn As New frmLogin
Dim User As String = frmlgn.ShowDialog(ConnStr:=ConnStr)
frmlgn.Dispose()
If User = "" Then
GoTo GetConnStr 'try agin
End If
CurrentUserName = User
GlobalConnectionString.ConnectionString = ConnStr
GlobalConnection.ConnectionString = ConnStr
SaveLastConnectionString(ConnStr)
frmDb.Dispose()
'فراخوانی فرم اصلی
FrmMain.Show()
'انجام کار های مورد نیاز بعدی
Catch ex As Exception
MsgBox("خطا در بارگذاری برنامه!" & vbNewLine & ex.Message, _
MsgBoxStyle.Critical + MsgBoxStyle.MsgBoxRtlReading + MsgBoxStyle.MsgBoxRight, _
"خطای جدی")
AddError(ex.Message)
End
End Try
End Sub


کلا دو خط کد اضافه شد که توضیحی نیاز ندارد!

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

----------


## فرید نجفلو

*مدیریت کاربران بخش اول:ایجاد،ویرایش،حذف کاربران لایه رابط کابری(PL):*

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

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

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

لایه رابط کاربری (PL)

ایجاد فرم لیست کاربران موجود:

کد های موجود در این فرم:

(به خاطر دارید که قرار شده است دیگر در مورد طراحی رابط کاربری توضیحی داده نشود)

Public Class frmUserList
#Region "توابع و متد ها"
''' <summary>
''' دریافت لیست کاربران و نمایش آن به کاربر
''' </summary>
''' <remarks></remarks>
Private Sub LoadList()
Try
Me.dgvUserList.DataSource = Nothing
Me.dgvUserList.DataSource = UserMgr.GetUsersDetailList
Me.dgvUserList.AutoResizeColumns(DataGridViewAutoS  izeColumnsMode.AllCells)
Me.dgvUserList.Columns(dgvUserList.ColumnCount - 1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
Catch ex As Exception
End Try
End Sub
''' <summary>
''' ایجاد کاربر جدید
''' </summary>
''' <remarks></remarks>
Private Sub AddNewUser()
Try
Dim MyfrmUsr As New frmUserAddEdit
Dim res = MyfrmUsr.ShowDialog()
If res Then LoadList()
Catch ex As Exception
MsgBox("عدم توانایی در انجام عملیات!" & vbNewLine & _
ex.Message, MsgBoxStyle.Exclamation + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا")
End Try
End Sub
''' <summary>
''' ویرایش کاربر انتخاب شده
''' </summary>
''' <remarks></remarks>
Private Sub EditUser()
Try
If dgvUserList.SelectedRows.Count = 0 Then Exit Try
Dim SelecteUserName As String = dgvUserList.SelectedRows(0).Cells("نام کاربری").Value
Dim MyfrmUsr As New frmUserAddEdit
Dim res = MyfrmUsr.ShowDialog(SelecteUserName)
If res Then LoadList()
Catch ex As Exception
MsgBox("عدم توانایی در انجام عملیات!" & vbNewLine & _
ex.Message, MsgBoxStyle.Exclamation + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا")
End Try
End Sub
''' <summary>
''' حذف کاربر انتخاب شده
''' </summary>
''' <remarks></remarks>
Private Sub DeleteUser()
Try
If dgvUserList.SelectedRows.Count = 0 Then Exit Try
Dim SelecteUserName As String = dgvUserList.SelectedRows(0).Cells("نام کاربری").Value
 
If MsgBox("آیا برای حذف این کاربر اطمینان دارید؟" & vbNewLine & _
SelecteUserName, MsgBoxStyle.YesNo + _
MsgBoxStyle.DefaultButton2 + MsgBoxStyle.Question + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا") <> MsgBoxResult.Yes Then
Exit Try
End If
 
If UserMgr.DeleteUser(SelecteUserName) Then
LoadList()
End If
Catch ex As Exception
MsgBox("عدم توانایی در انجام عملیات!" & vbNewLine & _
ex.Message, MsgBoxStyle.Exclamation + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا")
End Try
End Sub
#End Region
#Region "روال رویدادها"
Private Sub frmUserList_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
LoadList()
End Sub
Private Sub tsbExit_Click(sender As System.Object, e As System.EventArgs) Handles tsbExit.Click
Me.Close()
End Sub
Private Sub tsbNewUser_Click(sender As System.Object, e As System.EventArgs) Handles tsbNewUser.Click
AddNewUser()
End Sub
Private Sub tsbEditUser_Click(sender As System.Object, e As System.EventArgs) Handles tsbEditUser.Click
EditUser()
End Sub
Private Sub tsbDeleteUser_Click(sender As System.Object, e As System.EventArgs) Handles tsbDeleteUser.Click
DeleteUser()
End Sub
#End Region
End Class

نکته:

اگر یک نگاه سطحی هم به کد های این فرم (و فرم های بعدی) انداخته شود دریافته می شود با آنکه این فرم ها و وظایفشان کاملا درگیر با پایگاه داده است ولی هیچ اثری از کار با پایگاه داده دیدی نمی شود!(زیرا این لایه رابط کاربری (PL) می باشد)
تنها چیزی که دیده می شود این است که چند تابع با پارمتر صدا زده شده و نتیجه آن (که معمولایک نوع ساده یعنی بولین است) مورد برسی قرار می گیرد و فرم به هیچ وجه اطلاع ندارد در پشت پرده چه اتفاق های افتاده و اطلاعات از کجا آمده و کدام نوع پایگاه داده یا منبعی مورد استفاده قرار می گیرد. ما این نوع از کد نویسی را با استفاده از برنامه نویسی لایه ای انجام می دهیم 
جالب تر اینکه آن تابعی که ما آن را فرخوانی می کنیم در بیشتر اوقات خودش هم نخواهد فهمید اطلاعات از کدام منبع آمده است(این تابع خود در لایه میانی (BLL) قرار داشته و ارتباط مستقیم در لایه دسترسی به داده ها (DAL) انجام می شود)

توضیح کد:

تابع LoadList:

این تابع وظیفه دریافت لیست کاربران و بارگذاری آن در یک کنترل DataGridView موجود در فرم جهت نمایش را دارد
همانطور که می بیند ما نتیجه یک تابع را بارگذاری می کنیم یعنی عملیات کار با پایگاه داده که هیچ حتی به نوع برگشتی هم توجهی نداریم!
(طبق روال گذشته هر تابع مورد استفاده در ادامه و در مکان خود توضیح داده می شود)

تابع AddNewUser:

این تابع ابتدا فرم frmUserAddEdit را فراخوانی کرده و عملیات ایجاد کاربر را به آن محول می نماید سپس باتوجه به نتیجه برگشتی درصورتی که کاربر با موفقیت ایجاد شده باشد(True) اقدام به تازه سازی لیست کاربران جهت نمایش تغییرات ایجاد شده در رابط کاربری می نماید!

تابع EditUser:

این تابع ابتدا بررسی می کند که کاربری انتخاب شده است یا نه و در صورت انتخاب اقدام به استخراج نام کاربری از کنترل dgvUserList می نماید
همان طور که می بیند ما از نام فیلد استفاده ننموده ایم و فقط چیزی را که می بینیم (متن سرستون) دریافت می کنیم (نوشته ایم "نام کاربر" نه نام فیلد یعنی "UserName")
سپس نام کاربر را به فرم frmUserAddEdit ارسال می نماید تا عملیات ویرایش را انجام و نتیجه را اعلام کند

تابع DeleteUser:

این عملیات داری قوانین بیشتری خواهد بود (حتی بعدها خواهیم دید که این قوانین فعلی افزایش خواهند یافت و محاسبات پیچیده ای انجام خواهد شد)
البته بیشتر این قوانین در لایه میانی اعمال خواهد شد.

قوانین:

1-کاربر باید یک مورد را در لیست انتخاب کرده باشد
2-کاربر باید عملیات حذف را تایید نماید

بعد از گذراندن این قوانین اقدام به حذف کاربر با فراخوانی تابع آن از لایه BLL خواهیم نمود و بقیه قوانیین در آنجا ارزیابی خواهند شد

خوب دید فرمی که باید بسیار پیچیده می بود خیلی ساده نوشته شده است و ممکن است عملیاتی که این فرم قرار است انجام دهد نسبت به کد های موجود عجیب جلوه کند(مخصوصا اگر از آن دسته افرادی باشید که همه کار ها را در داخل فرم انجام می دهند!)

ما این را مدیون دو چیز هستیم:

1-برنامه نویسی لایه ای
2-تقسیم وظایف(تجزیه کار های بزرگ به قسمت های کوچک تر)

فرم ایجاد و یرایش کابران:

این یک فرم کوچک (از نظر ابعاد و بزرگ از نظر عملکرد) است که دو وظیفه بر عهده دارد:

1-ایجاد یک کاربر جدید
2-ویرایش و اصلاح یک کاربر موجود

کد های فرم:

Public Class frmUserAddEdit
#Region "متغیر ها"
''' <summary>
''' نشانگر انصراف کاربر از ادامه یا انجام موفقیت آمیز
''' </summary>
''' <remarks></remarks>
Private Canceled As Boolean = True
''' <summary>
''' نشانگر این که فرم جهت ویرایش یک کاربر موجود باز شده است یا ایجاد کاربر جدید
''' </summary>
''' <remarks></remarks>
Private EditMode As Boolean = False
#End Region
#Region "توابع و متد ها"
''' <summary>
''' نمایش فرم و دریافت اطلاعات از کاربر
''' </summary>
''' <param name="UserNameToEdit">نام کاربری جهت ویرایش</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shadows Function ShowDialog(Optional UserNameToEdit As String = "")
Try
EditMode = UserNameToEdit <> ""
If EditMode Then
txtUserName.ReadOnly = True
LoadDataForEdit(UserNameToEdit)
End If
MyBase.ShowDialog()
If Canceled Then
Return False
Else
Return True
End If
Catch ex As Exception
MsgBox(ex.Message)
Return False
End Try
End Function
''' <summary>
''' دریافت اطلاعات کاربر موجود و وارد کردن آنها در داخل کنترل هل جهت ویرایش
''' </summary>
''' <param name="UserName">نام کاربری جهت ویرایش</param>
''' <remarks></remarks>
Private Sub LoadDataForEdit(UserName As String)
Dim UserDitail = UserMgr.GetUserDetail(UserName)
If UserDitail Is Nothing Then
Throw New Exception("کاربر وجود ندارد!")
End If
txtUserName.Text = UserDitail.UserName
txtPassword.Text = "" 'Password Not Loaded For Security Reasons
txtConfirmPassword.Text = "" 'Password Not Loaded For Security Reasons
txtFirstName.Text = UserDitail.FirstName
txtLastName.Text = UserDitail.LastName
End Sub
''' <summary>
''' بررسی صحت اطلاعات ورودی توسط کاربر
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function IsValidData() As Boolean
Try
Me.ErrorProvider1.Clear()
If txtUserName.Text.Trim = "" Then
ErrorProvider1.SetError(txtUserName, "نام کاربری وارد نشده است!")
Throw New Exception("نام کاربری وارد نشده است")
End If
If Not EditMode AndAlso txtPassword.Text.Trim = "" Then
ErrorProvider1.SetError(txtPassword, "کلمه عبور وارد نشده است")
Throw New Exception("کلمه عبور وارد نشده است")
End If
If txtPassword.Text <> txtConfirmPassword.Text Then
ErrorProvider1.SetError(txtConfirmPassword, "تایید کلمه عبور با کلمه غبور مطابقت ندارد")
Throw New Exception("تایید کلمه عبور با کلمه عبور مطابقت ندارد")
End If
If txtFirstName.Text.Trim = "" Then
ErrorProvider1.SetError(txtFirstName, "نام وارد نشده است!")
Throw New Exception("نام وارد نشده است")
End If
 
If txtLastName.Text.Trim = "" Then
ErrorProvider1.SetError(txtLastName, "نام خانوادگی وارد نشده است!")
Throw New Exception("نام خانوادگی وارد نشده است!")
End If
Return True
Catch ex As Exception
MsgBox("اطلاعات ورودی نا معتبر!" & vbNewLine & _
ex.Message, MsgBoxStyle.Exclamation + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا")
Return False
End Try
End Function
''' <summary>
''' انجام عملیات ایجاد کاربر جدید یا ویرایش کاربر موجود
''' </summary>
''' <returns></returns>
''' <remarks>نوع عملیات بستگی به نحوه فراخوانی فرم دارد</remarks>
Private Function Add_Edit() As Boolean
Try
If Not IsValidData() Then Return False
If EditMode Then
Dim Res = UserMgr.EditUser(txtUserName.Text, txtPassword.Text, txtFirstName.Text, txtLastName.Text)
Return Res
Else
Dim Res = UserMgr.AddNewUser(txtUserName.Text, txtPassword.Text, txtFirstName.Text, txtLastName.Text)
Return Res
End If
Return False
Catch ex As Exception
MsgBox("عدم توانایی در انجام عملیات!" & vbNewLine & _
ex.Message, MsgBoxStyle.Exclamation + _
MsgBoxStyle.MsgBoxRtlReading + _
MsgBoxStyle.MsgBoxRight, "خطا")
Return False
End Try
End Function
#End Region
#Region "روال رویداد ها"
Private Sub btnOk_Click(sender As System.Object, e As System.EventArgs) Handles btnOk.Click
If Add_Edit() Then
Canceled = False
Me.Close()
End If
End Sub
Private Sub btnCancel_Click(sender As System.Object, e As System.EventArgs) Handles btnCancel.Click
Me.Close()
End Sub
#End Region
End Class

طبق روال گذشته باید فهمیده باشید کدام قسمت از کد فرم قبل از همه توضیح داده خواهد شد!

متغیر ها:

Canceled:

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

EditMode:
گفتیم که این فرم دو وظیفه ایجاد کاربر و ویریش کاربر را بر عهده دارد که هر کدام نیازمند یک سری عملیات و بررسی شریط خواص خود هستند پس با تنظیم این متغیر سایر کد های فرم را از نوع عملیاتی که قرار است انجام شود(کاربر جدید یا ویرایش کاربر موجود ) با خبر ساخته و آنها نیز بر طبق آن عملیات آن نوع را انجام خواهند داد

تابع ShowDialog:

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

اولین خط:

 EditMode = UserNameToEdit <> ""

این خط اقدام به تنظیم نوع فراخوانی ( یعنی قصد فراخوان از باز کردن این فرم) را تنظیم می کند
همان طور که می دانید چنانچه دو شیئ با عملوندهای مقایسه ای ( = ،> ،< ،<> ،>=، <=) مقایسه شوند یکی از دو نتیجه True (برابر بودن) و False (نابرابر بودن) را برگشت خواهند داد
پس در این کد چنانچه پارامتر UserNameToEdit برای فرم ارسال شده باشد می توان نتیجه گرفت که قصد از فراخوانی ویرایش یک کاربر موجود است و در غیر این صورت عملیات ایجاد کاربر جدید مورد درخواست می باشد
در نتیجه در خط بالا اگر UserNameToEdit خالی نباشد حالت ویرایش فعال خواهد شد

در خطوط بعدی چنانچه در حالت ویرایش باشیم نام کاربری را فقط خواندنی می کنیم زیرا نام کاربری غیر قابل ویریش است(به یاد دارید که کلید جدول بود!)
سپس با استفاده از تابع LoadDataForEdit اطلاعات کاربر در فرم بارگذلری می شود
در ادامه طبق روال فرم به نمایش در آمده و تابع منتظر کاربر و بسته شدن فرم می ماند

متد LoadDataForEdit:

این متد وظیفه دارد اطلاعات کاربر را دریافت و در کنترل ها قرار دهد تا در صورت لزوم ویرایش شوند

توجه کنید که به دلایل امنیتی به هیچ وجه نباید کلمه عبور را بار گذری کرد و اصلا نیازی به این کار هم نیست چون در صورت بارگذاری هم قابل نمایش نمی باشد(به صورت ستاره دیده می شود!)

تابع IsValidData: 

این تابع هم وظیف بررسی صحت اطلاعات وارد شده رد دارد که خود کد نیز گویای عملکرد می باشد
فقط یک نکته وجودارد که در حالت ویرایش می توان کلمه عبور را خالی گذاشت زیرا ممکن است کاربر قصد تعویض آن را نداشته باشد.یکی از دلایل آن این می تواند باشد که کاربر (مدیر سیستم)در حال ویرایش مشخصات کاربر دیگری می باشد و تعویض کلمه عبور می تواند از ورود کاربر جلوگیری به عمل آورد در حالی که هدف فقط ویرایش مشخصات بود!

تابع Add_Edit:

این تابع با چند خط کد خود انجام کل عملیات اصلی بر عهده دارد!(باز هم لایه بندی)

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

سپس به برسی نوع عملیات پرداخته تابع مناسب از لایه میانی(BLL) را پارامتر دهی کرده و فرخوانی می کند.(همین و تمام !)

اصلاح فرم اصلی:

تنها کاری که در این لایه باقی مانده است ایجاد راهی برای دسترسی به (باز کردن) پنجره لیست کابران است 
چون کار در این قسمت به جز یک کنترل منو و چند خط کد چیز دیگری وجود ندارد توضیح بیشتری نیز احساس نمی شود

----------


## فرید نجفلو

*مدیریت کاربران بخش دوم:ایجاد،ویرایش،حذف کاربران لایه میانی و لایه دسترسی به داده(DAL,BLL):*



خوب کم کم به نقاط جالب نزدیک می شویم 


تا به حال( در قسمت رابط کاربری) برای هر کاری یک تابع مجهول الهویه ای را صدا زده و کار را به آن واگذار می کردیم که در این قسمت قصد معرفی آنها را داریم
پس توابع مورد نظر این بخش را به ترتیب نیاز نه به ترتیب معرفی شده در قسمت قبلی مورد بررسی قرار می دهیم

تمام توابع زیر در کلاس UserMgr قرار خواهند گرفت:

تابع GetUsersDetailList:

 ''' <summary>
''' لیست کاربرن موجود 
''' آماده شده جهت نمایش به کاربر
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetUsersDetailList() As tblUserDetailDataTable
Dim Result As tblUserDetailDataTable
Result = AdpMgr.tblUserDetailTableAdapter.GetData
Result.UserNameColumn.ColumnName = "نام کاربری"
Result.FirstNameColumn.ColumnName = "نام"
Result.LastNameColumn.ColumnName = "نام خانوادگی"
Result.CreateDateColumn.ColumnName = "تاریخ ایجاد"
Result.LastLoginColumn.ColumnName = "تاریخ آخرین ورود به سیستم"
Return Result
End Function


همانطور که می بینید تابع از دو شیئ (یا کلاس) tblUserDetailDataTable و AdpMgr.tblUserDetailTableAdapter استفاده کرده است

این دو کلاس چیستند و از کجا آمده اند؟

بله هچ کدام از این دو را ما به صورت صریح ایجاد نکرده ایم و کدی برای آنها ننوشته ایم اما آنها نتیجه یکی از کارهای خود ما می باشند
به یاد می آورید که ما یک Dataset به پروژه خود اضافه کردیم و بعد جداول پایگاه داده را درون آن اضافه نمودیم؟
درست در همان زمانی که ما جداول را به داخل Dataset انداختیم در پشت پرده و به صورت خودکار کد های فراوانی ایجاد شد (تا به حال برای پروژه خودمان بیش از 10.000 خط کد!) در بین این کدها برای هر جدول سه کلاس بسیار مهم و پر کاربرد اضافه می شود که عبارتند از:

DataRow
DataTable
TableAdapter

که نام هر یک عبارت است از نام جدول باضافه نام نوع کلاس (برای مثال tblUserDetail_DataRow_)
حال ببینیم هر کدام چه وظیفه ای بر عهده دارند:


DataRow: 

هر نمونه از این شیئ وظیفه نگه داری یک رکورد از اطلاعات جدول مربوط به خود را دارد و برای هر فیلد از جدول نیز یک خاصیت برای آن تعیین شده است که به شما امکان می دهد به صورت شیئ گرا(بدون نوشتن نام فیلد) به صورت مستقیم مقدار آن فیلد را بدست آورد برای مثال وقتی یک tblUserDetailDataTableDataRow دارید جهت دریافت نام کاربری آن (که همان فیلد UserName می باشد) به این صورت خواهید نوشت :
MytblUserDetailDataTableDataRow.UserName

DataTable:

هر نمونه از این کلاس حاوی DataRow ها خواهد بود برای مثال وقتی شما لیست 3 کاربر موجود را در خواست کنید یک tblUserDetailDataTable حاوی 3 مورد tblUserDetailDataTableDataRow خواهید داشت

TableAdapter:

بله مجموع این اشیاء در واقع همان لایه دسترسی به داده ها ی(DAL) ما را ایجاد می کنند
پس این کلاس بسیار مهم خواهد بود در واقع تمام اطلاعات خوانده شده از و نوشته شده در پایگاه داده توسط این این کلاس ها انجام می شود
وقتی این کلاس به صورت خودکار ایجاد شد باز به صورت خودکار چهار عملیات اصلی کار با گایگاه داده (DELETE , UPDATE,INSERT,SELECT) نیز ایجاد شده و شما می توانید بدون درگیری با دستورات TSQL این عملیات را انجام دهید!

اما خواهیم دید که در بعضی مکان ها این توابع و متد های خودکار جوابگوی تمام نیاز های ما نخواهند بود.
پس راه حل چیست آیا باید باز توابع خودمان با پایگاه داده کار کنیم؟!

خوشبختانه نیازی به این کار نیست زیرا این کلاس قابلیت سفارشی سازه فوق العاده بالای دارد در توابع بعدی می بینید که چگونه دستورات TSQL خود را به آنها اضافه و بصورت متد ها و توابع ساده از آنها استفاده خواهیم نمود

آیا متد InitAdapters را به یاد دارید؟
این همان متدی بود که تمام TableAdapter هایی را که ایجاد شده و مورد نیاز بودند را نمونه سازی و تنظیم نمودیم و همانطور که دید یک متغیر به نام AdpMgr نیز تعریف کردیم که حاوی یک نمونه از هر کدام از TableAdapter های موجود است پس زمانی که ما این متد را اجرا کردیم TableAdapter ها جهت استفاده آماده شدند

به توضیح کد بالا (تابع GetUsersDetailList) می پردازیم:

همانطور که می دانید وقتی می خواهیم لیست کاربران را بدست آوریم باید رکورد های جدول پایگاه داده را دریافت کنیم پس ما مجموعه ای از DataRow ها خواهیم داشت و همانطور که گفتیم این مجموعه به صورت DataTable نگه داری می شوند بنابراین مقدار برگشتی تابع از نوع DataTable خواهد بود و چون جدول مورد نظر ما tblUserDetail است کلاس مرتبط با آن tblUserDetailDataTable خواهد بود

پس ابتدا یک متغیر از آن نوع که برگشت خواهیم داد ایجاد می کنیم(جهت مقدار دهی ، ویرایش و برگشت به عنوان نتیجه)

در خط بعدی ما یکی از آن متد هایی را که به صورت خودکار ایجاد شده اند را استفاده می کنیم.متد GetData از هر TableAdapter کل رکود های موجود در جدول متناظر خود در پایگاه داده را باز می گرداند که در این نمونه برابر است با کد TSQL زیر:

ُSELECT * FROM tblUserDetail

البته با امکانات بیشتر نسبت به حالت اجرای عادی آن

پس وقتی ما این متد را فراخوانی کریدم مقدار برگشتی یک tblUserDetailDataTable خواهد بود که مقدار آن را در داخل متغیر تعریف شده در خط قبل قرار می دهیم
خطوط بعدی جهت آماده سازی اطلاعات به کاربر می باشد

همانطور که می دانید اگر خاصیت ایجاد خودکار ستون(AutoGenerateColumns) در یک کنترل Datagridveiw را با True تنظیم نموده و یک DataTable را به عنوان DataSurce به آن بدهیم به صورت خودکار اقدام به ایجاد یک ستون به ازای هر فیلد نموده و نام آنها را نیز با نام آن فیلد تنظیم می کند و همان را به عنوان سر ستون به کاربر نشان می دهد (برای مثال UserName) در حالی که برنامه با به زبان فارسی نوشته شده است پس ما نام هر ستون را به متناظر فارسی آن تبدیل می کنیم

یک دلیل مهم دیگر هم به جداسازی لایه ها برمی گردد به این صورت که در این روش لایه رابط کاربری نیازی به دانستن نام فیلد ندارد ( که در توضیحات قسمت لایه کاربری هم مشاهد شد) و شما بعد ها می توانید نام فیلد یا حتی نوع منبع خود را تغییر دهید بدون آنکه نیازی به اعمال تغییرات در لایه PL داشته باشید

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

تابع GetUser:

کد:

 ''' <summary>
''' دریافت رکورد یک کاربر
''' </summary>
''' <param name="UserName"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetUser(UserName As String) As tblUserRow
Dim Res = AdpMgr.tblUserTableAdapter.GetDataByUserName(UserN  ame)
If Res.Rows.Count = 0 Then
Return Nothing
Else
Return Res.First
End If
End Function


همانطور که می بینید باز از همان نوع کلاس ها و این بار برای یک جدول دیگر استفاده شده است

ولی یک جای کار میلنگد!
ما از یک متد tblUserTableAdapter به نام GetDataByUserName استفاده کرده ایم که به صورت خودکار ایجاد نشده وجود هم ندارد!

این متد از کجا آمده است؟!

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

ممکن بگویید اینکه متد جدید لازم ندارد ما کل رکورد ها را دریافت و توسط یک حلقه رکورد مورد نظر را جدا و مورد استفاده قرار می دهیم

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

آیا بهتر نبود به صورت مستقیم سراغ آن رکورد می رفتیم؟

پس اگر قانع شدید آماده شوید تا کار سفارشی سازی را انجام دهیم

ایجاد یک متد(دستور) جدید در Dataset:

ابتدا Dataset را در حالت طراحی باز کنید(از Solution Explorer رو نام آن دوبار کلیک کنید)
قصد ما ایجاد دستوری جدید برای عملیاتی بروی جدول tblUser می باشد پس جدول مورد نظر را یافته و انتخاب کنید(روی عنوان آن کلیک کنید)

همانطور که می بینید جدول به دوتکه کلی تقسیم شده است

1-قسمت بالا که نام فیلد های ما مشاهده می شود

این قسمت همان ساختار جدول است و یک DataRow از این جدول نیز چنین ساختاری دارد

2-قسمت پایین که در حال حاظر فقط دارای یک مورد به نام GetData است (این نام برایتان آشنا نبود؟)

این همان قسمت مورد نظر ما می باشد در واقع این همان TableAdapter این جدول است و ما همین قسمت را سفارشی سازی خواهیم کرد
همانطور که می بیند متد GetData هم در آنجا حضور دارد

خوب جهت شروع جدول را انتخاب و روی همان GetData کلیک راست کنید

گزینه Add Query را انتخاب کنید

در این قسمت چون ما قصد داریم با یک دستور TSQL که خود ایجاد می کنیم رکورد(ها) را انتخاب نماییم از پنجره باز شده گزینه Use SQL statement را انتخاب و دکمه Next را بزنید

حال نوبت به انتخاب نوع عملیات رسیده است .اگر به تابع بالا دقت کرده باشید متوجه می شوید که ما نیازمند دریافت اطلاعات از پایگاه داده خواهیم بود و نیاز به کل یک رکورد داریم پس گزینه SELECT which returns rows را انتخاب و به مرحله بعدی بروید

در مرحله بعدی نوبت به معرفی دستور TSQL که قرار است انتخاب را انجام دهد می رسد
همانطور که می بینید ویزارد برای شما یک دستور را پیشنهاد کرده است.این دستور تاحدودی درست است ولی با این دستور کل رکورد ها خوانده می شود و ما می خواهیم رکورد خاصی را از جدول بخوانیم
پس کافی است یک ساختار WHERE به دستور اضافه نماییم 

صبر کنید مشکلی در کار است!!!

حالا ما باید چگونه به متد بگوییم کدام کاربر مد نظر ما می باشد ، ما که اینجا فقط می توانیم مقادیر ثابت را در ستور بنویسیم؟

نگران نباشید راه حلی وجود دارد:

اگر در مواردی همچون این مورد نیاز به ارسال پارامتر به متد TableAdapter جهت جاگذاری در ستور TSQL بود می توان آن پارامتر را با علامت @ تعریف نمود
پس ما دستور TSQL رو به صورت زیر تغییر می دهیم

SELECT UserName, PassWord FROM dbo.tblUser WHERE LOWER(UserName) = LOWER(@UserName)

طبق دستور بالا متد ما یک پارامتر به نام UserName دریافت خواهد نمود و پس از دریافت مقدار پارامتر را با UserName@ جایگزین و دستور را اجرا خواهد نمود.(چون عرف آن است که نام کاربری حساس به کوچکی و بزرگی حروف نباشند به همین دلیل هر دو طرف به حالت حروف کوچک تبدیل و مورد مقایسه قرار می گیرند)

نکته:

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

یکی از مهمترین دلایل این کار جلوگیری از حملات معرف به SQL Injection است که حد خطر آن غیر قابل تصور است و سمی مهلک است و نه تنها اطلاعات و پایگاه داده شما بلکه کل سرور و حتی رایانه ای که سرور روی آن در حال اجرا است می تواند مورد حمله هکران و افراد داری نیات سوء قرار گیرد.
چون توضیح این مطلب در این مکان ممکن نیست از وارد شدن به آن خودداری می کنیم ولی در آموزش های پیشرفته این موضوع نیز مورد بررسی قرار می گیرد.

نکته:

جهت آموزش و قبل از ورود به بحث SQL Injection در برنامه ما نقطه (های) نفوذی وجود خواهد داشت که باهم آن را پیدا و رفع خواهیم نبود.البته تا آن مقطع ، مکان و زمانی که آن نقطه بوجود آمده برای شما نامعلوم خواهد بود مگر آنکه خود آن را بیابید یا منتظر باشید که ما به شما بگوییم!

به ادامه مطلب اصلی می پردازیم:
تا اینجا ما دستور را ایجاد نمودیم پس دکمه Next را زده وارد مرحله بعدی شوید

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

چرا دو نام وارد شد؟
در واقع متدی که با Fill مشخص است یک DataTable را دریافت و اطلاعات دریافتی را در داخل می ریزد و تابعی که با Get مشخص است اطلاعات دریافتی از پایگاه داده را به صورت یک DataTable به فرخوان باز می گرداند

حال Finish را جهت اتمام عملیات کلیک کنید

کار ما در لایه (DAL) تمام شد و دستور آماده استفاده است بدون آنکه لایه های بالاتر از چند و چون قضیه باخبر باشند آنها فقط یک پارامتر ارسال و نتیجه را دریافت خواهند کرد(جالب نیست!)

تابع بعد از دریافت اطلاعات بررسی می کند که اگر رکوردی وجود داشت اولین رکود را به فرخوان بازگرداند در غیر این صورت یک مقدار پوچ (Nothing) برگشت داده می شود که نشان دهنده عدم وجود کاربر(رکورد) مورد نظر می باشد

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

تابع GetUserDetail:

کد:

 ''' <summary>
''' دریافت مشخصات یک کاربر
''' </summary>
''' <param name="UserName">نام کاربری</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetUserDetail(UserName As String) As tblUserDetailRow
Dim Res = AdpMgr.tblUserDetailTableAdapter.GetDataByUserName  (UserName)
If Res.Rows.Count = 0 Then
Return Nothing
Else
Return Res.First
End If
End Function


این تابع هم همانند تابع قبل است و این یکی مشخصات یک کاربر را بر می گرداند
متد استفاده شده tblUserDetailTableAdapter.GetDataByUserName را با دستور TSQL زیر در TableAdapter مربوط به جدول tblUserDetail ایجاد کنید :

SELECT UserName, FirstName, LastName, CreateDate, LastLogin FROM dbo.tblUserDetail
WHERE LOWER(UserName) = LOWER(@UserName)


تابع AddNewUser:

کد:

 ''' <summary>
''' ایجاد یک کاربر جدید
''' </summary>
''' <param name="UserName">نام کاربری جدید</param>
''' <param name="Password">کلمه عبور</param>
''' <param name="FirstName">نام</param>
''' <param name="LastName">نام خانوادگی</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function AddNewUser(UserName As String, Password As String, _
FirstName As String, LastName As String) As Boolean
Try
UserName = UserName.Trim
FirstName = FirstName.Trim
LastName = LastName.Trim
If UserName.Length = 0 Then
Throw New Exception("نام کاربری نامعتبر!")
End If
If Password.Length = 0 Then
Throw New Exception("کلمه عبور نامعتبر!")
End If
If FirstName.Length = 0 Then
Throw New Exception("نام نامعتبر!")
End If
If LastName.Length = 0 Then
Throw New Exception("نام خانوادگی نامعتبر!")
End If
If GetUser(UserName) IsNot Nothing Then
Throw New Exception("این نام کاربری موجود می باشد!")
End If
OpenConn(GlobalConnection)
Dim trans = GlobalConnection.BeginTransaction
AdpMgr.tblUserTableAdapter.Transaction = trans
AdpMgr.tblUserDetailTableAdapter.Transaction = trans
Dim encrpPassword As String = Strings.Left(StringCoding(Password), 1000)
AdpMgr.tblUserTableAdapter.Insert(UserName, encrpPassword)
AdpMgr.tblUserDetailTableAdapter.Insert(UserName, FirstName, LastName, PersianDate(Now), "")
trans.Commit()
 
Return True
Catch ex As Exception
Throw ex
Finally
CloseConn(GlobalConnection)
End Try
End Function


کار این تابع از نام آن پیداست و معلوم می شود که این تابع باید یک کاربر جدید ایجاد کند
قسمت اعتبار سنجی نیازی به توضیحات ندارد
پس از کد زیر شرح داده می شود:


OpenConn(GlobalConnection)
Dim trans = GlobalConnection.BeginTransaction
AdpMgr.tblUserTableAdapter.Transaction = trans
AdpMgr.tblUserDetailTableAdapter.Transaction = trans
Dim encrpPassword As String = Strings.Left(StringCoding(Password), 1000)
AdpMgr.tblUserTableAdapter.Insert(UserName, encrpPassword)
AdpMgr.tblUserDetailTableAdapter.Insert(UserName, FirstName, LastName, PersianDate(Now), "")
trans.Commit()
 
Return True

جهت اینکه از نکات ساده تر شروع شود باید گفت که کد بالا را می توانستیم به شکل ساده زیر بنویسیم:


Dim encrpPassword As String = Strings.Left(StringCoding(Password), 1000)
AdpMgr.tblUserTableAdapter.Insert(UserName, encrpPassword)
AdpMgr.tblUserDetailTableAdapter.Insert(UserName, FirstName, LastName, PersianDate(Now), "")
Return True

پس ابتدا به شرح کد دوم می پردازیم:

خط اول که مشخص است و قبلا نیز توضیح داده شده که کلمه عبور را جهت امنیت به صورت رمزنگاری شده تبدیل می کند

خط دوم با توسط یکی از همان متد ای خودکار یک رکورد به جدول کاربران اضافه می کند

خط بعدی هم یک رکورد به جدول مشخصات کاربر اضافه می کند

و چنانچه عملیات با موفقیت انجام شد این اتفاق با True به اطلاع فراخوان خواهد رسید

تعجب کردید که چرا در حالی که ما می توانیم این کد را بنویسیم و هیچ مشکلی هم وجود ندارد چرا خود را درگیر باز و بسته کردن اتصال و تراکنش ها کرده ایم!

درست الان هیچ مشکل بالفعلی وجود ندارد آیا مشکلات بالقوه را نیز در نظر گرفته اید؟

فرض کنید خط مربوط به ایجاد کاربر جدید اجرا و در پایگاه داده اعمال شد ولی در خط بعدی یعنی افزودن مشخصات کاربر به هر دلیلی( برای مثال طول بیش از حد نام خانوادگی که نباید بیش از 255 کاراکتر باشد) خطایی رخ داد و تابع از ادامه کار باز ایستاد

حال ما چه داریم؟
کاربری که در جدول اصلی ثبت شده ولی مشخصات آن ثبت نشده است!

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

با این تفاسیر تکلیف چیست؟ آیا باید دستور TSQL جدید بنویسیم؟!

راه بهتری وجود دارد:

آیا تعریف تراکنش (Transaction) را که در بخش اصطلاحات بیان نمودیم به خاطر دارید

"تازمانی که کل عملیات درون تراکنش با موفقیت انجان نشده و تراکنش نیز اعمال (Commit) نشود تغییرات به صورت قطعی در پایگاه داده وارد نخواهد شد."

پس ما می توانیم هر دو عمل بالا را در داخل یک تراکنش انجام دهیم و در صورت موفق بودن هر دو عملیات تغییرات را اعمال نماییم

برای انجام اینکار یک تراکنش را برای اتصال شروع می کنیم اما تا اتصال باز نباشد این عمل ممکن نیست پس ابتدا اتصال را باز و با فراخوانی Dim trans = GlobalConnection.BeginTransaction یک تراکنش را شروع و یک شیئ SqlTransaction دریافت می کنیم

ولی عملیات کامل نشده است ! درست است که ما تراکنش را ایجاد کرده ایم ولی TableAdapter ها از وجود آن خبری ندارند

بنابراین ما تراکنشی را که آغاز کرده ایم و اطلاعات آن درون متغیر trans قرار دارد را به TableAdapter ها معرفی می کنیم تا عملیات خود را درون آن انجام دهند

 AdpMgr.tblUserTableAdapter.Transaction = trans
AdpMgr.tblUserDetailTableAdapter.Transaction = trans


خوب حال عملیات را شروع می کنیم

اگر عملیات موفقیت آمیز بود تغییرات در پایگاه داده اعمال می شود:
 
trans.Commit()

و نتیجه برگشت داده می شود

اگر خطایی رخ داد چه؟

هیچ! چون ما هنوز تغییرات را اعمال نکرده ایم بعد از بروز خطا خطوط بعدی اجرا نمی شوند (Commit اجرا نمی شود) و اجرا وارد بدنه Catch می شود و آن نیز خطا را ارسال می کند. ولی قبل خروج کلی از تابع طبق قانون باید بدنه Finally هم اجرا شود 

در بدنه Finally اتصال بسته می شود و در همین لحظه (طبق توضیحات بخش اصطلاحات) عمل بازگشت(RollBack) انجام شده تغییرات انجام شده در پایگاه داده از بین می رود

و به همین دلیل است که ما عملیات بسته شدن اتصال را به بدنه Finally آورده ام به هر حال چه عملیات موفق باشد یا خطایی رخ دهد در نهایت اتصال باز شده باید بسته شود و طبق قانون Try ... Catch در هر شرایطی بدنه Finally باید اجرا شود پس چه مکانی بهتر از اینجا!

نکته:
توجه کنید که عملیات های چند تایی در دستورات حذف (DELETE) حساس تر و خطرناک تر هستند! و بی چون و چرا باید از تراکنش استفاده شود.

تابع EditUser:

کد:

 ''' <summary>
''' ویرایش کاربر
''' </summary>
''' <param name="UserName">نام کاربری</param>
''' <param name="Password">کلمه عبور جدید</param>
''' <param name="FirstName">نام جدید</param>
''' <param name="LastName">نام خانوادگی جدید</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function EditUser(UserName As String, Optional Password As String = "", _
Optional FirstName As String = "", Optional LastName As String = "") As Boolean
Try
UserName = UserName.Trim
FirstName = FirstName.Trim
LastName = LastName.Trim
Dim ExistUser = GetUser(UserName)
Dim ExistUserDeail = GetUserDetail(UserName)
If ExistUser Is Nothing OrElse ExistUserDeail Is Nothing Then
Throw New Exception("این کاربر موجود نمی باشد!")
End If
If Password.Length > 0 Then
Dim encrpPassword As String = Strings.Left(StringCoding(Password), 1000)
ExistUser.PassWord = encrpPassword
End If
If FirstName.Length > 0 Then
ExistUserDeail.FirstName = FirstName
End If
If LastName.Length > 0 Then
ExistUserDeail.LastName = LastName
End If
OpenConn(GlobalConnection)
Dim trans = GlobalConnection.BeginTransaction
AdpMgr.tblUserTableAdapter.Transaction = trans
AdpMgr.tblUserDetailTableAdapter.Transaction = trans
AdpMgr.tblUserTableAdapter.Update(ExistUser)
AdpMgr.tblUserDetailTableAdapter.Update(ExistUserD  eail)
trans.Commit()
CloseConn(GlobalConnection)
Return True
Catch ex As Exception
Throw ex
Finally
CloseConn(GlobalConnection)
End Try
End Function


توضیح کد:

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

با توجه به اینکه ممکن است قصد تغییر تمام مشخصات را نداشته باشیم پس بررسی می نماییم که چنانچه پارامتر آن ارسال شده باشد اقدام به تغییر مشخصات نماییم

در ادامه اتصال باز ، تراکنش ایجاد و معرفی می شود

بعد از متد توکار TableAdapter یعنی Update که یک DataRow دریافت و طبق آن پایگاه داده را بروز رسانی می کند استفاده می نماییم
و در نهایت تغییرات اعمال می شود


تابع DeleteUser:

کد:

 ''' <summary>
''' حذف یک کاربر موجود
''' </summary>
''' <param name="UserName">نام کابری</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function DeleteUser(UserName As String) As Boolean
Try
UserName = UserName.Trim
If GetUser(UserName) Is Nothing Then
Throw New Exception("این کاربر موجود نمی باشد!")
End If
If CurrentUserName.ToLower = UserName.ToLower Then
Throw New Exception("شما نمی توانید کاربر جاری را حذف کنید" & vbNewLine &
"جهت انجام این کار باید با یک کاربر دیگر داری مجوز کافی وارد شوید")
End If
Dim AllUsersList = AdpMgr.tblUserTableAdapter.GetData
If AllUsersList.Rows.Count <= 1 Then
Throw New Exception("نرم افزار حداقل باید داری یک کاربر باشد")
End If
 
OpenConn(GlobalConnection)
Dim trans = GlobalConnection.BeginTransaction
AdpMgr.tblUserTableAdapter.Transaction = trans
AdpMgr.tblUserDetailTableAdapter.Transaction = trans
AdpMgr.tblUserPremissionTableAdapter.Transaction = trans
AdpMgr.tblUserPremissionTableAdapter.DeleteUser(Us  erName)
AdpMgr.tblUserDetailTableAdapter.DeleteUser(UserNa  me)
AdpMgr.tblUserTableAdapter.DeleteUser(UserName)
trans.Commit()
Return True
Catch ex As Exception
Throw ex
Finally
CloseConn(GlobalConnection)
End Try
End Function


این تابع وظیفه حذف یک کاربر را بر عهده دارد

ولی عملیات حذف دارای قوانین بیشتر و حساس تری است:

قوانین:

1-کاربر باید موجود باشد

2-همه کاربران را نمی شود حذف کرد :
حداقل یک دلیل برای این کار وجود دارد.اگر همه کاربران حذف شوند دیگر کاربری جهت ورود به سیستم باقی نخواهد ماند

3-کاربر جاری نمی تواند حذف شود: اگر این کار انجام شود ما در حال استفاده از کاربری خواهیم بود که وجود ندارد!

(این قوانین بیشتر خواهند شد)

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

DELETE FROM [dbo].[tblUser] WHERE LOWER(UserName)=LOWER(@UserName)
DELETE FROM [dbo].[tblUserDetail] WHERE LOWER(UserName)=LOWER(@UserName)
DELETE FROM [dbo].[tblUserPremission] WHERE LOWER(UserName) = LOWER(@UserName)

ولی اگر دقت کنید نکته مهمی در این تابع نهفته است!
نکته در ترتیب قرار گیری دستور حذف ها می باشد

آیا مطالب مربوط به روابط بین جداول و پدر و فرزندی را به یاد می آورید؟ اصل جامعیت داده ها را چطور؟
بله درست است ما گفتیم نباید در پایگاه داده هیچ فرزندی وجود داشته باشد که پدر نداشته باشد (یتیم بودن) چه به صورت دائمی و چه به صورت موقت و در داخل یک تراکنش
حتی گفتیم که اگر قرار باشد پدر و فرزندانش حذف شوند ابتدا باید تمام فرزندان سپس پدر حذف شود

نتیجه این می شود که چون جدول tblUser پدر دو جدول دیگر می باشد ابتدا باید کاربر از آن دو و در نهایت از این جدول حذف شود در غیر اینصورت DBMS (سیستم مدیریت پایگاه داده) یک خطا را راه اندازی و عملیات را لغو خواهد نمود(می توانید امتحان کنید)



نکات بخش:
همانطور که دید ما به واسطه برنامه نویسی لایه ای در رابط کاربری و حتی لایه میانی حتی یک دستور TSQL هم نداریم و کارها چنان تقسیم بندی شده اند که لایه ها را می شود مستقل از هم دید برای مثال چنانچه روزی پایگاه داده خود را از SQL Server به Oracel یا اکسس و ... تغییر دهید نیازی به تغییر در فرم ها و کد های آنها نخواهید داشت 
و تصور کنید اگر تمام کد ها را در فرم ها نوشته بودیم علاوه بر اینکه مجبور بودیم کد های فراوانی را در هر فرم تکرار کنیم مدیریت آنها نیز کار آسانی نبود و یافتن یک خطا شما را ممکن بود از کل برنامه دلسرد کند

علاوه بر آن وقتی شما از Dataset استفاده می کنید تا حد خیلی بالایی خود را از خطر های امنیتی همچون SQL Injection دور می کنید

دید که کد نویسی به این سبک بسیار راحت تر و شیرین تر از روشی است که همیشه دستورات TSQL رو در پیش رو دارید و به نوعی به شما دهن کجی کرده و با هربار دیدن آنها داغ خطا های قبلی برنامه شما تازه می شود

*نکته بسیار مهم:*

باز هم تاکید می کنیم قبل انجام کوچکترین تغییری در دیتا ست حتی اگر قصد باز کردن آن در حالت طراحی را نمودید *حتما* از پروژه خود یک *نسخه پشتیبان* تهیه کنید زیرا گاهی به دلایل نامعلومی Dataset رفتار های عجیبی از خود نشان می دهد( به قول یکی از دوستانم دیوانه می شود)




(انشا ا... در مطلب بعدی به بررسی ، ایجاد ، اعطا و اعمال سطوح دسترسی کاربران خواهیم پرداخت که یکی از مسائل مهم و پیچیده بشمار می آید)

----------


## فرید نجفلو

فعلا تاپیک در حال ایجاد است لطفا تا تکمیل مراحل اولیه ایجاد تاپیک از ارسال هرگونه پستی خودداری نمایید

----------


## HM2020

سلام و با تشکر از آقای نجفلو عزیز

اولا که خیلی وقته از تاپیک متوقف شده اما سوالم رو همین جا از آقای نجفلو میپرسم.

*- من طبق گفته های شما جلو رفتم حالا رسیدم به گزارشگیری 

زمانی که *View* رو به داخل دیتاست درگ میکنم نمیتونم ازش استفاده کنم

اصلا روش گزارشگیری رو توضیح میدید؟؟؟



    ''' <summary>
    '''مقدار دهی به ابزار ها
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub InitAdapters()
        Try
            AdpMgr.Connection = GlobalConnection
            AdpMgr.tblAnbarKalaGroupTableAdapter = New tblAnbarKalaGroupTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblAnbarTableAdapter = New tblAnbarTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblDbVerTableAdapter = New tblDbVerTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaGroupTableAdapter = New tblKalaGroupTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaInputTableAdapter = New tblKalaInputTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaOutputTableAdapter = New tblKalaOutputTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblKalaTableAdapter = New tblKalaTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblMoshtariTableAdapter = New tblMoshtariTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblPremissionListTableAdapter = New tblPremissionListTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserDetailTableAdapter = New tblUserDetailTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserPremissionTableAdapter = New tblUserPremissionTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.tblUserTableAdapter = New tblUserTableAdapter With {.Connection = GlobalConnection}
            AdpMgr.Table1TableAdapter = New Table1TableAdapter With {.Connection = GlobalConnection}
        Catch ex As Exception

        End Try
    End Sub


حالا وقتی یک View به دیتاست اضافه کنم *AdpMgr* مقدار view رو در خودش نداره !!!؟؟؟

لطفا توضیح میدید.

----------


## HM2020

******************************************


*آقای نجفلو ما همچنان  چشم به راه شماییم.*

لطفا نحوه گزارش از view  رو بفرمایید.

با تشکر

----------


## HM2020

*
آقا کسی نیست جواب ما رو بده؟؟؟؟؟؟؟

ای بابا عجب گیری کردیماااااااااااااا
*

----------

