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

سلام دوستان و اساتید محترم وقتتون به خیر
دوستان پیشاپیش ازتون پوزش میطلبم به خاطر اینکه این تاپیک ممکنه یه خورده طولانی بشه به این دلیل که سعی کردم کل ساختار برنامه رو با یک مثال ساده توضیح بدم و چند تا سوال بهم پیوسته رو مطرح کنم.همچنین بابت وقتی که صرف مطالعش میکنید ازتون ممنونم.
در ادامه ساختار این برنامه سه لایه رو به کمک یک مثال ساده خدمتتون توضیح میدم. فرض می کنیم که دیتابیس این برنامه از یک جدول به نام User که شامل چهار ستون به نام های ID, UserName, Date, Admin هست، تشکیل شده :




attachment.php




لایه UI :
PHP:
private void button1_Click(object sender, EventArgs e)
{
    if (txtID.Text != "" && txtUserName.Text != "")
    {
        BusinessLogicLib.User_Value userValueObj = new User_Value();
        userValueObj.ID = (int)txtID.Text;
        userValueObj.UserName = txtUserName.Text;
        userValueObj.Date = DateTime.Now;
        userValueObj.Admin = chbxAdmin.Checked;

        BusinessLogicLib.User_BLL userBllObj = new User_BLL();
        userBllObj.CreateUser(userValueObj);
    }
}
در این لایه اطلاعات کاربر از فرم خونده میشه، در شی User_Value ثبت و سپس این شی یه User_BLL ازسال میشه.

لایه BLL :
PHP:
class User_Value
 {
    private int? id;
    private string userName;
    private DateTime? date;
    private bool? admin;
    .
    .   //Properties
    .   
}
PHP:
class User_BLL
{
    DataAccessLib.User_DAL userDalObj = new DAL.User_DAL();

    public void CreateUser(User_Value userValueObj)
    {
        DataTable dtUser = MyObjectConverter.ConvertToDataTable(userValueObj);
        userDalObj.Insert(dtUser);
    }
    .
    .   //Other Methods
    .
}
PHP:
class MyObjectConverter
{
    public static DataTable ConvertToDataTable(Object Obj)
    {
        PropertyInfo[] fields = Obj.GetType().GetProperties();
        DataTable dt = new DataTable();
        DataColumn column;
        foreach (PropertyInfo Property in Properties)
        {
            column = new DataColumn();
            column.ColumnName = Property.Name;
            dt.Columns.Add(column);
        }
        DataRow row = dt.NewRow();
        foreach (PropertyInfo Property in Properties)
        {
            row[Property.Name] = Property.GetValue(Obj);
        }
        dt.Rows.Add(row);
        return dt;
    }
}
در این لایه شی User_Value حاوی اطلاعات کاربره و وظیفه اعتبارسنجی این اطلاعات رو بر عهده داره.
شی User_BLL عملیات های ثبت کاربر، ویرایش کاربر و ... رو انجام میده. به این صورت که اطلاعات رو در قالب یک شی User_Value دریافت می کنه، با استفاده از کلاس MyObjectConverter اون رو تبدیل به یک دیتاتیبل می کنه و در انتها این دیتاتیبل رو برای ثبت به لایه DAL میفرسته. (البته همونطور که میدونید این شی در واقعیت شامل پیاده سازی منطق اصلی برنامه هست و این یک مثال سادست).
شی MyObjectConverter هم که وظیفه کپی اطلاعات شی در یک دیتاتیبل رو بر عهده داره.

لایه DAL :
PHP:
public class User_DAL
{
    public void Insert(DataTable dtUser)
    {
        SqlParameter idParam = new SqlParameter("@ID", SqlDbType.Int);
        idParam.Value = dtUser.Rows[0]["ID"];

        SqlParameter usernameParam = new SqlParameter("@UserName", SqlDbType.NVarChar);
        usernameParam.Value = dtUser.Rows[0]["UserName"];

        SqlParameter dateParam = new SqlParameter("@StartDate", SqlDbType.Date);
        dateParam.Value = dtUser.Rows[0]["Date"];

        SqlParameter adminParam = new SqlParameter("@Admin", SqlDbType.Bit);
        adminParam.Value = dtUser.Rows[0]["Admin"];
        .
        .       //Inserting Parameters Into DataBase By StoredProcedure
        .
    }
    .
    .   //Other Methods
    .
}
همونطور که میبینید اطلاعات به کمک یک دیتاتیبل از لایه BLL به لایه DAL میاد و در این لایه پارامترها ساخته شده و به Stored Procedure که در بانک اطلاعاتی نوشته شده فرستاده و در بانک ثبت میشن.

نکته : لایه های DAL و BLL توسط کتابخانه کلاس پیاده سازی و به پروژه اضافه شدند. در لایه UI فقط رفرنس به لایه BLL و در لایه BLL فقط رفرنس به لایه DAL داده شده.

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

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

سوال سوم :
همونطور که مشاهده می کنید من در لایه BLL با استفاده از شی MyObjectConverter اطلاعات شی User رو در یک دیتاتیبل کپی کردم. اما یک نکته در اینجا وجود داره و اونم اینه که من در زمان کپی اطلاعات، نوع داده های شی User رو در دیتاتیبل مشخص نکردم :
PHP:
class MyObjectConverter
{
    public static DataTable ConvertToDataTable(Object Obj)
    {
        .
        .
        .
        foreach (PropertyInfo Property in Properties)
        {
                **********************
            column = new DataColumn();
            column.ColumnName = Property.Name;
            dt.Columns.Add(column);
                **********************
        }
        .
        .
        .
    }
}
در صورتی که باید از کدی مشابه این استفاده می کردم :
PHP:
class MyObjectConverter
{
    public static DataTable ConvertToDataTable(Object Obj)
    {
        .
        .
        .
        foreach (PropertyInfo Property in Properties)
        {
                **********************
            column = new DataColumn();
            column.ColumnName = Property.Name;
            column.DataType = Property.PropertyType;
            dt.Columns.Add(column);
                **********************
        }
        .
        .
        .
    }
}
اما از اونجاییکه بعضی از فیلدهام در آبجکت User به صورت Nullable تعریف شده اند (منطق برنامم در قسمت جستجو نیاز داره که اینطور باشن) و دیتاتیبل این رو ساپورت نمیکنه نتونستم این کار رو انجام بدم.
حالا با این شرایط که این کار رو نکردم، از چند تا متد ثبت و جستجوی ساده که تست گرفتم برنامه درست عمل کرده ! که حدس میزنم این درست عمل کردن برنامه به خاطر این باشه که در لایه DAL زمانی که فیلدهای دیتاتیبل رو به پارامترها نسبت میدادم نوع پارامتر رو مشخص کرده بودم :
PHP:
public class User_DAL
{
    public void Insert(DataTable dtUser)
    {
        SqlParameter idParam = new SqlParameter("@ID", ****SqlDbType.Int****);
        idParam.Value = dtUser.Rows[0]["ID"];

        SqlParameter usernameParam = new SqlParameter("@UserName", ****SqlDbType.NVarChar****);
        usernameParam.Value = dtUser.Rows[0]["UserName"];
        .
        .
        .
}
که باعث شده نوع داده ها همینجا تنظیم بشه و برنامه درست عمل کنه !
میخوام بدونم آیا این حدس درسته یا کاملا اتفاقی بوده ؟
آیا این روش کاملا درسته و بعدا ممکن نیست ایجاد مشکل کنه ؟

باز هم بابت حوصله ای که به خرج دادین ازتون ممنونم
 

the_king

مدیرکل انجمن

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

چیزی به نام استاندارد وجود نداره، در سایت مایکروسافت ببینید لایه BLL رو چطوری پیاده سازی کرده :

Tutorial 2: Creating a Business Logic Layer
موقعی که لایه UI از GetProducts ئه لایه BLL استفاده می کنه بهش DataTable تحویل داده میشه، یعنی DataTable رو هم در ارتباط بین UI و BLL و هم ارتباط بین BLL و DAL بکار می بره و چیزی مثل User_Value شما رو نداره.
این مثال رو برای این مطرح کردم که اگه بخواهید می توانید از اون کلاس هایی که ویژوال استدیو بصورت خودکار از روی Data Source ای که تو منوی Data اضافه می کنید می سازه استفاده کنید و نیازی نیست بصورت پویا DataTable جدید ایجاد بشه.
اونها هم شمای شون دقیقا منطبق بر پایگاه داده تون ئه و هم فیلد هاشون با اسم مشخص شده. می توانید ویرایش شون هم بکنید. برای هر جدول تون هم کلاس Row دارند و هم DataTable و هم Adapter

کد تون ناقصه ولی ظاهرا شما از اون dtUser مقدار فیلد ها رو برمیدارید و یک Query مستقل می سازید. در واقع با DataTable که ساختار پیچیده ای داره در حد یک User_Value رفتار می کنید. برای همینه که ناقص بودنش باعث خطا نمیشه.
پیشنهاد می کنم تا جایی که براتون مشکل ایجاد نمی کنه از امکانات موجود ویژوال استدیو کمک بگیرید. مثلا یک SqlDataAdapter می تونه با یک متد Update فقط در یک سطر کد DataTable تون رو Insert کنه.
 
چیزی به نام استاندارد وجود نداره، در سایت مایکروسافت ببینید لایه BLL رو چطوری پیاده سازی کرده :

Tutorial 2: Creating a Business Logic Layer
موقعی که لایه UI از GetProducts ئه لایه BLL استفاده می کنه بهش DataTable تحویل داده میشه، یعنی DataTable رو هم در ارتباط بین UI و BLL و هم ارتباط بین BLL و DAL بکار می بره و چیزی مثل User_Value شما رو نداره.
این مثال رو برای این مطرح کردم که اگه بخواهید می توانید از اون کلاس هایی که ویژوال استدیو بصورت خودکار از روی Data Source ای که تو منوی Data اضافه می کنید می سازه استفاده کنید و نیازی نیست بصورت پویا DataTable جدید ایجاد بشه.
اونها هم شمای شون دقیقا منطبق بر پایگاه داده تون ئه و هم فیلد هاشون با اسم مشخص شده. می توانید ویرایش شون هم بکنید. برای هر جدول تون هم کلاس Row دارند و هم DataTable و هم Adapter

کد تون ناقصه ولی ظاهرا شما از اون dtUser مقدار فیلد ها رو برمیدارید و یک Query مستقل می سازید. در واقع با DataTable که ساختار پیچیده ای داره در حد یک User_Value رفتار می کنید. برای همینه که ناقص بودنش باعث خطا نمیشه.
پیشنهاد می کنم تا جایی که براتون مشکل ایجاد نمی کنه از امکانات موجود ویژوال استدیو کمک بگیرید. مثلا یک SqlDataAdapter می تونه با یک متد Update فقط در یک سطر کد DataTable تون رو Insert کنه.

سلام ممنونم بابت وقتی که گذاشتین
راستش من تا حالا با این روشی که شما گفتین کار نکردم و هیچ اطلاعاتی راجع بهش ندارم ! من تمام کوئری هام (حتی Insert و Update) رو خودم به صورت دستی در یک Stored Procedure پیاده سازی می کنم.
میخوام این دو روش رو با هم ترکیب کنم و اطلاعاتی که از لایه UI به لایه BLL میفرستم رو با کمک همون DataTable های آماده ای که گفتید بفرستم.
چطور می تونم به اون DataTabel های آماده دسترسی پیدا کنم تا دیگه نیاز نباشه ساختار DataTable رو هم خودم دستی مشخص کنم !
ممنون میشم راهنماییم کنید و اگر منبعی دارید معرفی کنید. همونطور که گفتم هیچ اطلاعاتی راجع به این روش ندارم.
 
آخرین ویرایش:

the_king

مدیرکل انجمن
سلام ممنونم بابت وقتی که گذاشتین
راستش من تا حالا با این روشی که شما گفتین کار نکردم و هیچ اطلاعاتی راجع بهش ندارم ! من تمام کوئری هام (حتی Insert و Update) رو خودم به صورت دستی در یک Stored Procedure پیاده سازی می کنم.
میخوام این دو روش رو با هم ترکیب کنم و اطلاعاتی که از لایه UI به لایه BLL میفرستم رو با کمک همون DataTable های آماده ای که گفتید بفرستم.
چطور می تونم به اون DataTabel های آماده دسترسی پیدا کنم تا دیگه نیاز نباشه ساختار DataTable رو هم خودم دستی مشخص کنم !
ممنون میشم راهنماییم کنید و اگر منبعی دارید معرفی کنید. همونطور که گفتم هیچ اطلاعاتی راجع به این روش ندارم.

درسته که بصورت خودکار براتون کلاس و CommandSet طراحی می کنه ولی معمولا لازم میشه که اون کلاس ها رو ویرایش بکنید و در مواردی مثل حذف کردن یکسری رکورد های مرتبط
(مثلا حذف کردن تمامی اطلاعات یک مشتری) نهایتا خودتان هم دستی Query می سازید و یا از Stored Procedure استفاده می کنید، جایگزین همه نیاز هاتون نمیشه.
بیشترین کمکی که بهتون می کنه که در اضافه کردن سطر جدید به جدول یا Query های ساده است.

در منوی Data، وقتی با گزینه Add New Data Source پایگاه داده تون رو در Server فلان به ویژوال استدیو معرفی می کنید و مشخص می کنید که فلان موارد داخل اش رو لازم دارید (تیک می زنید)
ویژوال استدیو یکسری کد هایی رو بصورت کلاس می سازه که در همون Solution Explorer هم قابل دسترسی و ویرایش ئه.
به روش های مختلفی می توانید ازشون Query بگیرید اما حداقل از این DataTable هاشون که دقیقا به اسم هر Table تون و با همون فیلد های داخلش طراحی شده کمک بگیرید.

رجوع شود به :
ارتباط دادن با ديتا بيس
حذف و اديت در ديتابيس
 
درسته که بصورت خودکار براتون کلاس و CommandSet طراحی می کنه ولی معمولا لازم میشه که اون کلاس ها رو ویرایش بکنید و در مواردی مثل حذف کردن یکسری رکورد های مرتبط
(مثلا حذف کردن تمامی اطلاعات یک مشتری) نهایتا خودتان هم دستی Query می سازید و یا از Stored Procedure استفاده می کنید، جایگزین همه نیاز هاتون نمیشه.
بیشترین کمکی که بهتون می کنه که در اضافه کردن سطر جدید به جدول یا Query های ساده است.

در منوی Data، وقتی با گزینه Add New Data Source پایگاه داده تون رو در Server فلان به ویژوال استدیو معرفی می کنید و مشخص می کنید که فلان موارد داخل اش رو لازم دارید (تیک می زنید)
ویژوال استدیو یکسری کد هایی رو بصورت کلاس می سازه که در همون Solution Explorer هم قابل دسترسی و ویرایش ئه.
به روش های مختلفی می توانید ازشون Query بگیرید اما حداقل از این DataTable هاشون که دقیقا به اسم هر Table تون و با همون فیلد های داخلش طراحی شده کمک بگیرید.

رجوع شود به :
ارتباط دادن با ديتا بيس
حذف و اديت در ديتابيس

سلام جناب the-king عزیز
خیلی ممنونم ازتون، این کار رو کردم و جواب داد
فقط چند تا سوال داشتم از خدمتتون، اینکه من DataSet رو به برنامم اضافه کنم واسم هزینه ای نداره ؟ (فقط صرف اینکه بخوام از جداولش استفاده کنم)
آیا بعد از ساخت فایل ستاپ DataSet به مشکل برنمیخوره و برنامه از کار نمی افته ؟
آیا در هر بار اجرای برنامه DataSet میخواد به دیتابیس وصل بشه و کل اطلاعاتش رو بخونه ؟ اطلاعات اولیه ای که در هر بار اجرای برنامه از بانک میخونه فقط ساختار دیتابیس و جداولش هست یا اطلاعات داخل جداول رو هم میخواد ؟
من همیشه کانکشن استرینگ برنامم رو در App.config به صورت رمز نگاری شده قرار میدم، این دیتاست برای وصل شدن به بانک، از چه کانکشن استرینگی استفاده می کنه ؟
خیلی ممنونم
 

the_king

مدیرکل انجمن
سلام جناب the-king عزیز
خیلی ممنونم ازتون، این کار رو کردم و جواب داد
فقط چند تا سوال داشتم از خدمتتون، اینکه من DataSet رو به برنامم اضافه کنم واسم هزینه ای نداره ؟ (فقط صرف اینکه بخوام از جداولش استفاده کنم)
آیا بعد از ساخت فایل ستاپ DataSet به مشکل برنمیخوره و برنامه از کار نمی افته ؟
آیا در هر بار اجرای برنامه DataSet میخواد به دیتابیس وصل بشه و کل اطلاعاتش رو بخونه ؟ اطلاعات اولیه ای که در هر بار اجرای برنامه از بانک میخونه فقط ساختار دیتابیس و جداولش هست یا اطلاعات داخل جداول رو هم میخواد ؟
من همیشه کانکشن استرینگ برنامم رو در App.config به صورت رمز نگاری شده قرار میدم، این دیتاست برای وصل شدن به بانک، از چه کانکشن استرینگی استفاده می کنه ؟
خیلی ممنونم
هزینه اش اینه که اگه شمای DataSet تون خیلی بزرگ باشه (تعداد جداول و ...، نه تعداد رکورد ها) زمان لازم برای کامپایل پروژه بالا میره، چون در واقع حجم کد برنامه تون رو زیاد می کنه.

اون قسمت حساس صرفا ConnectionString ئه، اون ConnectionString توی Properties پروژه تون قسمت Settings ذخیره میشه. البته اختیاری ئه، اون موقعی که Database رو به پروژه Add می کنید ازتون سوال می کنه
که این ConnectionString رو ذخیره بکنم یا نه.
کد:
string s = Properties.Settings.Default.[COLOR="#0000FF"]YourDatabase[/COLOR]ConnectionString;

DataSet به خودی خودش کاری انجام نمیده، بدون درخواست شما هم به پایگاه داده وصل نمیشه.
فقط موقعی که پایگاه داده رو به پروژه اضافه کردین تو منوی Data با گزینه Show Data Sources می توانید جداول رو ببینید و بکشید و روی فرم بندازید. که لابد شما تمایلی ندارید که چنین کاری بکنید،
چون اون کنترل هایی که روی فرم می اندازه کد نویسی خودکار دارند و یکی از اون کد ها Fill کردن DataSet با اطلاعات جدول ئه که موقع فراخوانی فرم اجرا میشه و البته می توانید کدش رو پاک کنید.
 

جدیدترین ارسال ها

بالا