مدیریت کاربران بخش دوم:ایجاد،ویرایش،حذف کاربران لایه میانی و لایه دسترسی به داده(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
که نام هر یک عبارت است از نام جدول باضافه نام نوع کلاس (برای مثال tblUserDetailDataRow)
حال ببینیم هر کدام چه وظیفه ای بر عهده دارند:
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 رفتار های عجیبی از خود نشان می دهد( به قول یکی از دوستانم دیوانه می شود)
(انشا ا... در مطلب بعدی به بررسی ، ایجاد ، اعطا و اعمال سطوح دسترسی کاربران خواهیم پرداخت که یکی از مسائل مهم و پیچیده بشمار می آید)