Parent Child Relationship in SQL: Database हमें SQL Queries के माध्यम से दो Tables की Joining करके Result Generate करने की सुविधा Provide करता है। ठीक इसी प्रकार से DataSet Object हमें दो Tables के बीच DataRelations Define करने की सुविधा Provide करता है, लेकिन हम इस पर SQL Queries जैसे Syntax का प्रयोग नहीं कर सकते।
बल्कि ADO.NET का DataRelations Object हमें कुछ अलग Properties व Methods Provide करता है, जिनका प्रयोग करके हम किसी Parent Row से Associated Child Rows को Access करने की क्षमता प्राप्त करते हैं।
यदि हम इस Concept को इस Chapter में Use किए जा रहे अपने उदाहरण के आधार पर समझें, तो यदि हमें इस बात का पता लगाना हो कि किस Customer ने कौनसा Order Place किया है, जबकि हमारा Resultset एक Strongly Typed DataSet के रूप में Defined है, जिस पर हम SQL JOIN Query Use नहीं कर सकते।
क्योंकि हमने हमारे DataSet Object को Strongly Typed DataSet Designer के माध्यम से Design किया है, इसलिए Visual Studio ने स्वयं ही विभिन्न DataTable Objects के बीच Appropriate Relationships को Underlying Database की विभिन्न Tables पर Specified Relationship के आधार पर Setup कर दिया है, जिसे हमें Manually Setup करने की जरूरत नहीं है।
Relationship के आधार पर एक से ज्यादा DataTable के Records को एक साथ Join करते हुए Access करने की व्यवस्था को बेहतर तरीके से समझने के लिए हम अपना Windows Form निम्नानुसार Redesign कर सकते हैं:
इस Form पर हमने चार ListBox Controls तथा तीन Button Controls को Place किया है। साथ ही क्योंकि इस Form पर चार अलग Tables के Data Access हो रहे हैं, इसलिए सबसे पहले हमने निम्नानुसार Code के माध्यम से चारों ही DataTables को Access करने के लिए चार Strongly Typed DataTable व DataAdapter Objects को Class Level Properties के रूप में Declare कर दि, हैं, जिसकी वजह से ये DataTables व DataAdapters Current Form के सभी Methods व Event Handlers के लिए Accessible रहते हैं:
private dsSSFTableAdapters.CustomersTableAdapter daCustomers = new dsSSFTableAdapters.CustomersTableAdapter(); private dsSSFTableAdapters.OrdersTableAdapter daOrders = new dsSSFTableAdapters.OrdersTableAdapter(); private dsSSFTableAdapters.ProductsTableAdapter daProducts = new dsSSFTableAdapters.ProductsTableAdapter(); private dsSSFTableAdapters.Order_DetailsTableAdapter daOrderDetails = new dsSSFTableAdapters.Order_DetailsTableAdapter(); private dsSSF ds = new dsSSF();
इन सभी Strongly Typed DataAdapters को इसलिए Define किया गया है, ताकि हम हमारे DataSet Object की विभिन्न Tables को Underlying Database की Actual Tables के Data से Fill कर सकें।
इस Code की अन्तिम Line के रूप में हमने के नाम का एक dsSSF Type का Object Create किया है, जो कि हमारे Strongly Typed DataSet Type का Object है। अभी तक हमने कोई भी ऐसा Example Program Create नहीं किया है, जिसमें हमने अपने Strongly Typed DataSet Type का Object Create किया हो, बल्कि लगभग सभी Examples में हमने सीधे ही Strongly Typed DataTable Type का Object Create करते हुए ही अपनी जरूरतों को पूरा किया है।
लेकिन जब हम Professional Development करते हैं, तब केवल DataTables को उसी स्थिति में उपयोग में लिया जाता है, जब हमें केवल एक Single DataTable को Access व Manipulate करना होता है।
जबकि यदि हम एक से ज्यादा DataTables को उनकी Relationships Maintain करते हुए साथ Access व Manipulate करना चाहते हैं, जैसाकि Current Example में हम एक से ज्यादा Tables के Data को Joining के साथ Access व Manipulate कर रहे हैं, तो इस प्रकार की जरूरत को बेहतर तरीके से पूरा करने के लिए ये जरूरी होता है कि हम अपने सभी DataTables को DataSet Object के माध्यम से ही Access व Manipulate करें।
जैसाकि पिछले Chapter में हमने देखा था कि जब हम Visual Studio के Strongly Typed DataSet Designer का प्रयोग करते हुए अपना DataSet Object Create करते हैं, जिसमें विभिन्न DataTables Contained होते हैं, तो हमारे DataSet Object व उसमें Contained सभी DataTables, DataViews, Stored Procedures, Stored Functions आदि की एक अलग Class Create होती है, जिनके माध्यम से हम Underlying Database के इन Objects को Classes यानी Types के Objects के माध्यम से Access व Manipulate करने में सक्षम हो जाते हैं।
इसलिए जब हम उपरोक्तानुसार के नाम का dsSSF Type का Strongly Typed DataSet Object Create करते हैं, तो Visual Studio इस DataSet Object में Contained विभिन्न DataTables व DataColumns को उनके नाम से Access करने की सुविधा Provide कर देता है, जिससे Data Manipulation करना ज्यादा आसान हो जाता है।
इन Properties को Specify करने के बाद जब हम इस Application को Run करते हैं, तो Form के Memory में Load होते ही Form का Load Event Fire होता है, जिसके Response में निम्न Event Handler Execute होता है:
private void frmAccessingMultipleTable_Load(object sender, EventArgs e) { daCustomers.Fill(ds.Customers); foreach (dsSSF.CustomersRow drCustomer in ds.Customers.Rows) { lbCustomers.Items.Add(drCustomer.CustomerID + ": " + drCustomer.ContactName); } }
इस Code के Execute होते ही सबसे पहले ds.Customers नाम का DataTable Underlying Database के Customers Table के Rows से Fill हो जाता है। फिर एक foreach Statement द्वारा ds.Customers DataTable में Stored सभी Rows के CustomerID व ContactName Columns को आपस में एक String की तरह Concatenate करके Form पर दिखाई देने वाले lbCustomers नाम के ListBox Control में Add कर दिया है।
हम देख सकते हैं कि Customers DataTable को Access करने के लिए हमने सीधे ही “Customers” नाम का प्रयोग किया है, जबकि Customers DataTable के सभी Rows को Represent करने के लिए हमने सीधे ही “ds.Customers.Rows” Statement का प्रयोग किया है।
जब foreach Loop Execute होता है, तो “ds.Customers.Rows” Collection द्वारा के नाम के DataSet Object में Contained Customers नाम के DataTable के Rows Collection के सभी Rows को One-by-One Access करता है और हर Row को drCustomer नाम के dsSSF.CustomersRow Type के Object में Store कर देता है, जिसके विभिन्न Columns में से निम्नानुसार Statement द्वारा Retrieve होने वाले Row के CustomerID व ContactName Columns को Concatenate किया जाता है:
drCustomer.CustomerID + “: ” + drCustomer.ContactName
इस Concatenation द्वारा जो String Return होता है, उसे Add() Method का प्रयोग करते हुए lbCustomers नाम के Items Collection में Add कर दिया जाता है। परिणामस्वरूप Form पर स्थित lbCustomers नाम के ListBox Control में Customers Table के सभी Rows के CustomerID + ContactName का Combination निम्न चित्रानुसार दिखाई देने लगता है:
चूंकि हमने Visual Studio के Strongly Typed DataSet Designer के माध्यम से DataSet Object Create किया है, इसलिए इस DataSet में Contained विभिन्न DataTables के बीच की Relationship, Automatically Underlying Database के अनुसार Predefined है, जिसे हमें अलग से Set करने की जरूरत नहीं है न ही हमें Relationship को Represent करने वाले Objects या Properties को अपने Code में कहीं पर भी Specify करने की जरूरत है, क्योंकि Visual Studio स्वयं ही इन सभी प्रकार की Relationships को अपने स्तर पर Maintain करता है और हमें Related Tables के Data को Access करने के लिए Appropriate Methods Provide करता है:
किस Customer ने कौन-कौन से Orders Place किए हैं, इस बात की जानकारी प्राप्त करने के लिए जैसे ही हम Form पर दिखाई देने वाले “Child Rows >>” नाम के पहले Button को Click करते हैं, निम्नानुसार Click Event Handler Execute हो जाता है और lbCustomers ListBox में जिस Customer को Select किया गया होता है, उस Customer द्वारा Place किए गए सभी Orders की Information lbOrders नाम के दूसरे ListBox Control में Add हो जाती है:
private void btnShowOrders_Click(object sender, EventArgs e) { if (lbCustomers.SelectedIndex < 0) { return; } daOrders.Fill(ds.Orders); dsSSF.CustomersRow rowCustomer = (dsSSF.CustomersRow)ds.Customers.Rows[lbCustomers.SelectedIndex]; lbOrders.Items.Clear(); foreach (dsSSF.OrdersRow rowOrder in rowCustomer.GetOrdersRows()) { lbOrders.Items.Add(rowOrder.OrderID + ": " + rowOrder.OrderDate); } }
जब ये Event Handler Execute होता है, तो सबसे पहले इस बात को Check किया जाता है कि lbCustomers नाम के ListBox Control में किसी Option को Select किया गया है या नहीं। यदि इस ListBox में किसी Option को Select न किया गया हो, तो lbCustomers Control की SelectedIndex Property का मान 0 से कम होता है। परिणामस्वरूप if Statement Successful हो जाता है और कोई Action Perform नहीं होता क्योंकि Program Control को return Statement प्राप्त हो जाता है।
लेकिन यदि इस ListBox में किसी Option को Select किया गया हो, तो lbCustomers Control की SelectedIndex Property का मान 0 या 0 से ज्यादा होता है। परिणामस्वरूप if Statement Fail हो जाता है और निम्न Statement का Execution होता है:
daOrders.Fill(ds.Orders);
ये Statement के नाम के हमारे DataSet Object की Orders नाम की DataTable में Underlying Database की Orders Table के Data को Fill कर देता है। फिर निम्न Statement Execute होता है:
dsSSF.CustomersRow rowCustomer = (dsSSF.CustomersRow)ds.Customers.Rows[lbCustomers.SelectedIndex];
ये Statement Execute होते ही ds.Customers.Rows Collection के उस Index Number के Row को DataRow Object के रूप में Return किया जाता है, जिसे Form पर स्थित lbCustomers नाम के ListBox में Select किया गया होता है।
चूंकि हम Strongly Typed Objects के साथ Interact कर रहे हैं, इसलिए इस DataRow Type के Object की Type Casting करते हुए Return होने वाले Customer यानी Parent Row को rowCustomer नाम के Object में Store कर दिया जाता है। फिर निम्न Statement Run होता है:
lbOrders.Items.Clear();
ये Statement हमारे Form पर स्थित lbOrders नाम के ListBox Control के Items Collection में Exist सभी Items को Remove करके ListBox को Clear कर देता है, ताकि जब उसमें नया Data Add हो, तो कोई भी पिछला Data List में Exist न हो। फिर निम्नानुसार foreach Statement Execute होता है:
foreach (dsSSF.OrdersRow rowOrder in rowCustomer.GetOrdersRows()) { lbOrders.Items.Add(rowOrder.OrderID + ": " + rowOrder.OrderDate); }
जब हम Strongly Typed Objects को ही Use कर रहे होते हैं, तब किसी Parent Row से Related सभी Child Rows को प्राप्त करने के लिए हम Get{TableName}Rows() Method को Use कर सकते हैं। इस Statement की सबसे बडी विशेषता ये है कि हमें किसी भी तरह के Relationship के बारे में सोचने की जरूरत नहीं होती, क्योंकि इस विषय में जो भी काम करना होता है, Visual Studio स्वयं अपने स्तर पर करता रहता है।
इसलिए इस foreach Statement में जब rowCustomer.GetOrdersRows() Statement Execute होता है, तो पिछले Statement के Execution द्वारा rowCustomer Object में जिस Customer का Record Stored होता है, उस Customer से Associated सभी Orders Table के Rows Return हो जाते हैं, जिन्हें One-by-One rowOrder नाम के dsSSF.OrdersRow Type के Object में Store कर दिया जाता है और फिर निम्न Statement के माध्यम से Orders DataTable से Return होने वाले हर Child Row के OrderID व OrderDate Columns ds Concatenation को lbOrders नाम के ListBox Control में Add कर दिया जाता है:
lbOrders.Items.Add(rowOrder.OrderID + “: ” + rowOrder.OrderDate);
परिणामस्वरूप इस Statement के Execution के साथ ही हमें हमारा Resultant Form कुछ निम्नानुसार दिखाई देने लगता है:
जिस तरह से हमने पहले “Child Rows >>” Button को Code करके lbCustomers नाम के ListBox Control में Selected Customer द्वारा Place किए गए सभी Orders की Information को lbOrders नाम के ListBox Control में Add किया है, उसी तरह से हम Particular दूसरे “Child Rows >>” Button को Click करके lbOrders नाम के ListBox Control में Selected Order में Place किए गए सभी Products की Information को lbOrderDetails नाम के ListBox Control में Add कर सकते हैं। यानी हम इस दूसरे Button के लिए निम्नानुसार Event Handler Specify कर सकते हैं:
private void btnShowOrderDetails_Click(object sender, EventArgs e) { if (lbOrders.SelectedIndex < 0) { return; } daOrderDetails.Fill(ds.Order_Details); dsSSF.OrdersRow rowOrder = (dsSSF.OrdersRow)ds.Orders.Rows[lbOrders.SelectedIndex]; lbOrderDetails.Items.Clear(); foreach (dsSSF.Order_DetailsRow rowOrderDetail in rowOrder.GetOrder_DetailsRows()) { lbOrderDetails.Items.Add("ProductID: " + rowOrderDetail.ProductID); } }
जब हम इस Code को Specify करके फिर से अपने Application को Run करते हैं और इस बार Customer द्वारा Place किए गए Orders में से किसी Order को Select करते हैं, तो उस Particular Order में Included सभी Products का ID lbOrderDetails नाम के ListBox Control में Add हो जाता है, जिसे हम निम्न चित्र में देख सकते हैं:
इस प्रकार से दोनों “Child Rows >>” Buttons One-to-Many Relationship को Represent करते हुए अपनी Child या Detail Table के Data को अगले ListBox में Add करते हैं। यानी जब हम lbCustomers ListBox में किसी Customer को Select करते हैं, तो उसके द्वारा Place किए गए सभी Orders की Information lbOrders ListBox में Show हो जाती है। इसी तरह से जब हम lbOrders ListBox में किसी Particular Order को Select करते हैं, तो उसकी Order Details की Information lbOrderDetails ListBox में Show हो जाती है।
हम देख सकते हैं कि lbOrderDetails ListBox में केवल Product का ID ही Show हो रहा है। क्योंकि वास्तव में lbOrderDetails Table, Orders व Products Table की Line Table है, जो Orders व Products के बीच Many-to-Many की Relationship को Represent कर रहा है।
इसलिए यदि हम इस lbOrderDetails ListBox में दिखाई देने वाले विभिन्न Products के ID से Associated ProductName की जानकारी प्राप्त करना चाहते हैं, तो इस जानकारी को प्राप्त करने के लिए हमें हर ProductID की Parent Table को Access करना होगा क्योंकि Product की Information Products नाम की Table में Stored है, जो कि Order Details Table की Parent Table है।
किसी Child Record के ID के आधार पर Parent Table के Record को Access करने के लिए ADO.NET हमें Strongly Typed DataSet Use करने पर {ParentTable}Row नाम का Object Provide करता है। इस Object को Use करते हुए यदि हम हमारे Form के lbProducts ListBox में Customer द्वारा Order किए गए Products की Order Details को Add करने के लिए जब हम Form पर दिखाई देने वाले “Parent Rows >>” Button को Click करते हैं, तो इस Click Event को Handle करने के लिए निम्नानुसार Event Handler Execute होता है:
private void btnShowOrderedProducts_Click(object sender, EventArgs e) { dsSSF.OrdersRow rowOrder=(dsSSF.OrdersRow)ds.Orders.Rows[lbOrders.SelectedIndex]; daProducts.Fill(ds.Products); lbProducts.Items.Clear(); foreach (dsSSF.Order_DetailsRow rowOrderDetail in rowOrder.GetOrder_DetailsRows()) { dsSSF.ProductsRow rowProduct = rowOrderDetail.ProductsRow; lbProducts.Items.Add(rowProduct["ProductName"]); } }
जब ये Code Execute होता है, तो सबसे पहले dsSSF.OrdersRow Strongly Type का rowOrder नाम का एक Object Create होता है और इस Object में हमारे के DataSet के Orders DataTable के उस Row का Reference Store हो जाता है, जिसका Index Number lbOrders नाम के ListBox की SelectedIndex Property में होता है। यानी Form पर स्थित lbOrders ListBox Control में हम जिस Order को Select करते हैं, उस Order से सम्बंधित Row, rowOrder Object में Store हो जाता है।
फिर अगले Statement द्वारा ds.Products DataTable को Underlying Database की Products Table के Data से Fill कर दिया जाता है, ताकि Form में Products की Information को Access किया जा सके।
Products की Information को ds.Products DataTable में Retrieve करने के बाद lbProducts नाम के ListBox Control के सभी Items को Clear कर दिया जाता है, ताकि अलग-अलग Orders को Select करने पिछली बार Selected Order के Products की Information ListBox Control से Clear हो जाए।
ListBox के Clear हो जाने के बाद एक foreach Statement Execute होता है। इस Statement में सबसे पहले हमने rowOrder.GetOrder_DetailsRows() Statement के माध्यम से उन Child Rows को One-by-One Select किया है, जो कि rowOrder नाम के dsSSF.OrdersRow Type के Object में Stored Row के Child Rows हैं और हर Enumerate होने वाले Row में Stored ProductID Foreign Key से Associated Parent Record को rowOrderDetail.ProductsRow Statement द्वारा Products Table से Retrieve करके rowProduct नाम के dsSSF.ProductsRow Type के Object में Store किया है।
अन्त में इस rowProduct Object में Stored Rows के ProductName Column के Data को lbProducts.Items.Add() Statement द्वारा lbProducts नाम के ListBox Control में Add कर दिया है। परिणामस्वरूप जब हम “Parent Rows >>” Button पर Click करते हैं, तो हमें हमारा Output कुछ निम्नानुसार दिखाई देता है:
इस प्रकार से यदि हम हमारे इस पूरे Form के Code को एक साथ Specify करें, तो हमारा पूरा Program Code कुछ निम्नानुसार होता है:
using System; using System.Collections.Generic; using System.Data; using System.Windows.Forms; namespace SearchingSortingFiltering { public partial class frmAccessingMultipleTable : Form { public frmAccessingMultipleTable() { InitializeComponent(); } private void btnExit_Click(object sender, EventArgs e) { this.Close(); } private dsSSFTableAdapters.CustomersTableAdapter daCustomers = new dsSSFTableAdapters.CustomersTableAdapter(); private dsSSFTableAdapters.OrdersTableAdapter daOrders = new dsSSFTableAdapters.OrdersTableAdapter(); private dsSSFTableAdapters.ProductsTableAdapter daProducts = new dsSSFTableAdapters.ProductsTableAdapter(); private dsSSFTableAdapters.Order_DetailsTableAdapter daOrderDetails = new dsSSFTableAdapters.Order_DetailsTableAdapter(); private dsSSF ds = new dsSSF(); private void btnShowOrders_Click(object sender, EventArgs e) { if (lbCustomers.SelectedIndex < 0) { return; } daOrders.Fill(ds.Orders); dsSSF.CustomersRow rowCustomer = (dsSSF.CustomersRow)ds.Customers.Rows[lbCustomers.SelectedIndex]; lbOrders.Items.Clear(); foreach (dsSSF.OrdersRow rowOrder in rowCustomer.GetOrdersRows()) { lbOrders.Items.Add(rowOrder.OrderID + ": " + rowOrder.OrderDate); } } private void frmAccessingMultipleTable_Load(object sender, EventArgs e) { daCustomers.Fill(ds.Customers); foreach (dsSSF.CustomersRow drCustomer in ds.Customers.Rows) { lbCustomers.Items.Add(drCustomer.CustomerID + ": " + drCustomer.ContactName); } } private void btnShowOrderDetails_Click(object sender, EventArgs e) { if (lbOrders.SelectedIndex < 0) { return; } daOrderDetails.Fill(ds.Order_Details); dsSSF.OrdersRow rowOrder = (dsSSF.OrdersRow)ds.Orders.Rows[lbOrders.SelectedIndex]; lbOrderDetails.Items.Clear(); foreach (dsSSF.Order_DetailsRow rowOrderDetail in rowOrder.GetOrder_DetailsRows()) { lbOrderDetails.Items.Add("ProductID: " + rowOrderDetail.ProductID); } } private void btnShowOrderedProducts_Click(object sender, EventArgs e) { if (lbOrders.SelectedIndex < 0) { return; } dsSSF.OrdersRow rowOrder = (dsSSF.OrdersRow)ds.Orders.Rows[lbOrders.SelectedIndex]; daProducts.Fill(ds.Products); lbProducts.Items.Clear(); foreach (dsSSF.Order_DetailsRow rowOrderDetail in rowOrder.GetOrder_DetailsRows()) { dsSSF.ProductsRow rowProduct = rowOrderDetail.ProductsRow; lbProducts.Items.Add(rowProduct["ProductName"]); } } } }
जब हम कोई Windows Forms Application Develop कर रहे होते हैं, तो समय-समय पर हमें किसी Particular Object में Stored Current Data के बारे में जानने की जरूरत होती है, ताकि हम इस बात को निश्चित कर सकें, कि हम जिस Data को Access करना चाहते हैं, Object में वही Data Contained है।
लेकिन इस प्रकार के Data को हमेंशा Visual Control जैसे कि DataGridView या ListBox जैसे Control में Place करते हुए Check करना थोडा Typical होता है, जबकि कुछ Specific Situations में हम सरल तरीके से किसी Object के Data को इस प्रकार के Visual Controls में Normal तरीके से Store नहीं कर सकते। इस स्थिति में Console हमारी काफी मदद करता है, जिसे हम Debug Window की तरह Use कर सकते हैं।
Visual Studio हमें ये सुविधा Provide करता है कि हम बडी ही आसानी से अपने किसी भी प्रकार के Windows Application, Windows Service, WPF, ASP.NET आदि विभिन्न प्रकार के Programs में Directly Console को Use कर सकते हैं। लेकिन अपने Program में Console Use करने पर भी Console उसी स्थिति में दिखाई देता है, जबकि हमारा Application Console Application की तरह Run हो रहा हो।
इसलिए जब हम Windows Application में Console को Use करते हैं, तो Console को Display करने के लिए हमें Visual Studio के “PROJECT => {Project Name} Properties” Option को Select करना होता है और निम्न चित्रानुसार अपने Windows Application के “Output type:” Combobox से “Windows Application” के स्थान पर “Console Application” Option को Select करना होता है:
जब हम ये Setting करके Save कर देते हैं तो जब भी हम हमारे Windows Application को Run करते हैं, Windows Application शुरू होने से पहले Console Open होता है और हमारे GUI Program में हमने जहां पर भी Console को Use किया होता है, वह Data इस Console Window में दिखाई देता है।
उदाहरण के लिए यदि हम हमारे पिछले Example Program में ही btnShowOrders_Click Event Hander को निम्नानुसार Modify कर दें, जिसमें विभिन्न Customers द्वारा Place किए गए सभी Orders की Information को Console में Display किया गया हो-
private void btnShowOrders_Click(object sender, EventArgs e) { . . . foreach (dsSSF.CustomersRow rowCust in ds.Customers) { Console.WriteLine("Row: >>>>>>>> "); foreach (dsSSF.OrdersRow rowOrder in rowCust.GetOrdersRows()) { Console.WriteLine(rowOrder[0]+" "+rowOrder[1]+" "+rowOrder[2]+" "+rowOrder[3]); } } }
तो जैसे ही हम Form पर दिखाई देने वाले पहले “Child Rows >>” Button पर Click करते हैं, lbOrders में Records Add होने के साथ ही Backend में चल रहे Console में भी निम्न चित्रानुसार Output प्राप्त होता है:
यानी Visual Studio की Project Properties में उपरोक्तानुसार Setting करके हम किसी भी GUI Application में Console को अपनी जरूरत के अनुसार उपयोग में ले सकते हैं और अपने GUI Application की ज्यादा बेहतर तरीके से Debugging कर सकते हैं, क्योंकि Visual Studio द्वारा किसी Exception या Error को Describe करने के लिए जो Error Message Show किया जाता है, वह उतना Informative नहीं होता कि हम आसानी से समझ सकें कि हमारे Program में किस प्रकार की Error Trigger हो रही है।
ये Article इस वेबसाईट पर Selling हेतु उपलब्ध EBook ADO.NET with C# in Hindi से लिया गया है। इसलिए यदि ये Article आपके लिए उपयोगी रहा, तो निश्चित रूप से ये पुस्तक भी आपके लिए काफी उपयोगी साबित होगी।
ADO.NET with C# in Hindi | Page:501 | Format: PDF