I am working on a project using 3 tier architecture.
I want to knw about how to pass datareader value from DAL to Presentation layer
My code is like this.
In DAL layer
public class HomeDAL
{
public SqlDataReader DefaultSearchFriends(long userid)
{
SqlConnection SocialConn = new SqlConnection(connstr);
using (SqlCommand comm = new SqlCommand("proc_FriendsSearch", SocialConn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#userid", userid);
SocialConn.Open();
SqlDataReader dr = comm.ExecuteReader(CommandBehavior.CloseConnection);
return dr;
}
}
}
In BAL layer
public class HomeBAL
{
public SqlDataReader DefaultSearchFriends(long userid)
{
HomeDAL HDAL = new HomeDAL();
SqlDataReader dr = HDAL.DefaultSearchFriends(userid);
return dr;
}
}
On Presentaion Layer I wrote this on Page load
HomeBAL HBAL = new HomeBAL();
SqlDataReader dr = HBAL.DefaultSearchFriends(user_id);
while (dr.Read())
{
//some code
}
Now i want to know two things
1- Is this right to call datareader in this way or there is some better logic.
2- how to close datareader object in BAL layer and in DAL layer.
Well, the basic idea of a layered architecture is to decouple the different components for several reasons.
Some reasons are testability, maintainability, extensibility but there are many more.
To pass the data between these layers - well it depends a bit on the kind of data - but usually you would use some simple classes as data transfer objects (DTO), which would be filled with data in the DAL. E.g.
public class Person
{
public string Name {get; set;}
public string FirstName {get; set;}
...
}
With your approach you are breaking this idea, because you're passing the DataReader to presentation layer which implies, that you cannot switch the DAL technology without touching the other layers. E.g. if you want to use Entity Framework you would have to modify every part in the code, where you're currently using the SqlDataReader.
You can also see, if you'd stick to the idea behind the layered approach, you don't have to think about your second question.
I hope this helps a little bit.
EDIT
Ok I'm a bit curious, that you don't find a proper solution. But anyways, the first and simplest approach could be, don't publish the SqlDataReader. Handle its life cycle in the DAL. Means, assuming that you're using my DTO above
public class HomeDAL
{
public List<Person> DefaultSearchFriends(long userid)
{
SqlConnection SocialConn = new SqlConnection(connstr);
using (SqlCommand comm = new SqlCommand("proc_FriendsSearch", SocialConn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#userid", userid);
SocialConn.Open();
SqlDataReader dr = comm.ExecuteReader(CommandBehavior.CloseConnection);
var persons = new List<Person>();
while (dr.Read())
persons.Add(new Person { Name = dr["Name"], FirstName = dr["FirstName"] });
dr.Close();
return persons;
}
}
}
would be the much better approach.
I think your architecture has problems;
you are using concrete classes to query database; instead you need an abstraction in case you change the backend SQL server or the querying mechanism.
you are passing concrete SqlDataReader into all of your layers. From DAL, you need to return data objects not the db operation context instance.
you need to change only domain objects between layers, not the object doing the actual work.
I suggest you to refer to n layer reference application of microsoft spain
IN DAL File
public class HomeDAL
{
public void DefaultSearchFriends(ref HomeBAL hBAL)
{
SqlConnection SocialConn = new SqlConnection(connstr);
using (SqlCommand comm = new SqlCommand("proc_FriendsSearch", SocialConn))
{
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#userid", hBAL.userid);
SocialConn.Open();
hBAL.Search_Reader = comm.ExecuteReader(CommandBehavior.CloseConnection);
}
}
}
In BAL File
public class HomeBAL
{
public SqlDataReader Search_Reader = null;
}
and in Presentaion layer
HomeBAL HBAL = new HomeBAL();
HomeDAL HDAL = new HomeDAL();
HDAL.DefaultSearchFriends(ref HBAL);
SqlDataReader dr = HBAL.Search_Reader;
while (dr.Read())
{
}
Related
I am attempting to get the information of user whenever user logged in to the website, it success when I used a DataSet, but if I want to use the SqlDataReader, the error says: Invalid attempt to read when reader is closed. I have search why is it like that and I have found an article says that
SqlDataReader requires connection remains open in order to get the
data from the server, while DataSet does not need requires
connection remains open.
My question is: I want to know how can I use SqlDataReader as well? So that I don't have to depends on DataSet all the times when I want to get the data from the database.
My problem is occurs when I am trying to change the structure of reading the data function using SqlDataReader, so that it can be re-usable anytime.
Here is the code:
DatabaseManager class:
public SqlDataReader GetInformationDataReader(string procName, SqlParameter[] parameters)
{
SqlDataReader reader = null;
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(procName, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null)
{
foreach(SqlParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
}
reader = cmd.ExecuteReader();
}
}
return reader;
}
Web Manager class:
public ModelContexts.InformationContext GetInformation(string username)
{
SqlDataReader reader = null;
ModelContexts.InformationContext context = new ModelContexts.InformationContext();
SqlParameter[] parameters =
{
new SqlParameter("#Username", SqlDbType.NVarChar, 50)
};
parameters[0].Value = username;
try
{
reader = DatabaseManager.Instance.GetInformationDataReader("GetInformation", parameters);
while(reader.Read())
{
context.FirstName = reader["FirstName"].ToString();
context.LastName = reader["LastName"].ToString();
context.Email = reader["Email"].ToString();
}
}
catch(Exception ex)
{
throw new ArgumentException(ex.Message);
}
return context;
}
Controller:
public ActionResult MainMenu(ModelContexts.InformationContext context, string firstName, string lastName, string username, string email)
{
context = WebManager.Instance.GetInformation(User.Identity.Name);
firstName = context.FirstName;
lastName = context.LastName;
username = User.Identity.Name;
email = context.Email;
return View(context);
}
Model contains string return value with getter and setter (FirstName, LastName and Email).
View contains the html label and encode for FirstName, LastName and Email from the Model.
Appreciate your answer.
Thanks.
Here is an approach you can use to keep the code pretty clean that allows you to read from the SqlDataReader while the connection is still open. It takes advantage of passing delegates. Hopefully the code is understandable. You can adjust it to fit your specific needs, but hopefully it illustrates another option at your disposal.
public void GetInformationDataReader(string procName, SqlParameter[] parameters, Action<SqlDataReader> processRow)
{
SqlDataReader reader = null;
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(procName, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
if (parameters != null)
{
foreach(SqlParameter parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
}
using (SqlDataReader dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
// call delegate here.
processRow(dataReader);
}
}
}
}
return reader;
}
public ModelContexts.InformationContext GetInformation(string username)
{
SqlDataReader reader = null;
ModelContexts.InformationContext context = new ModelContexts.InformationContext();
SqlParameter[] parameters =
{
new SqlParameter("#Username", SqlDbType.NVarChar, 50)
};
parameters[0].Value = username;
try
{
// Instead of returning a reader, pass in a delegate that will perform the work
// on the data reader at the right time, and while the connection is still open.
DatabaseManager.Instance.GetInformationDataReader(
"GetInformation",
parameters,
reader => {
context.FirstName = reader["FirstName"].ToString();
context.LastName = reader["LastName"].ToString();
context.Email = reader["Email"].ToString();
});
}
catch(Exception ex)
{
throw new ArgumentException(ex.Message);
}
return context;
}
Brief explanation:
You'll notice that the overall structure of the code is very similar to what you already have. The only changes are:
Instead of returning a SqlDataReader, the GetInformationDataReader() method accepts an Action<SqlDataReader> delegate.
Within the GetInformationDataReader() method, the delegate is invoked at the correct time, while the connection is still open.
The call to GetInformationDataReader() is modified to pass in a block of code as a delegate.
This sort of pattern can be useful for exactly these cases. It makes the code reusable, it keeps it pretty clean and separate, and it doesn't prevent you from benefiting from the using construct to avoid resource/connection leaks.
You have wrapped your SqlConnection object in a using clause, therefore at the end of it SqlConnect.Dispose is called, closing the connection. Whatever caller is consuming your SqlDataReader no longer has the open connection, therefore you're getting your error.
while DataSet does not need requires connection remains open.
That is not entirely correct. DataSet is just an object that is typically filled when called by SqlDataAdapter (the Fill() method of that class). The SqlDataAdapter handles the opening and closing of the SqlConnection, which is most likely why that comment states that. But it's a different class that handles that, not the DataSet itself. Think of the DataSet as just the object that holds the result set of the SqlCommand.
To answer your comment...
So, shouldn't I use using keyword for this matter? In all of the Sql keyword?
I wouldn't take that approach either. You could have a connection leak bug quite easily with that model, and running out of pooled connections could be a not-so-fun thing to troubleshoot.
Typically it's best to consume your data and then close/dispose your connection. There's a saying, "open late, close early". That's typically how you'd want to approach this. I wouldn't try to pass a SqlDataReader object between class methods for this very issue that you're dealing with. The workaround (leaving the connection open) is very error prone.
Another though process, going back to something we mentioned, don't use the SqlDataReader. You have no benefit to cyclically loop through reading each row. Depending on your result set, just fill a DataSet (or usually more appropriate, a DataTable) and return either that Data[Set | Table] or, even better, an object that is more representative of the data it pertains to.
I have a project in c# about a hospital system which contains 30 child forms.
I have created database which contain more than 30 tables.
I created data access like this:
namespace emamTree
{
public class DBAccess
{
public static string connectionString = ConfigurationManager.ConnectionStrings["TreeFinal"].ConnectionString ;
public SqlCommand Intialize(string query, params SqlParameter[] prmArray)
{
SqlConnection cn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand(query, cn);
if (!query.Contains(" "))
cmd.CommandType = System.Data.CommandType.StoredProcedure;
if (prmArray.Length > 0)
cmd.Parameters.AddRange(prmArray);
cn.Open();
return cmd;
}
public int ExcuteNonQuery(string query, params SqlParameter[] prmArray)
{
try
{
SqlCommand cmd = Intialize(query, prmArray);
int affectedRows = cmd.ExecuteNonQuery();
cmd.Connection.Close();
return affectedRows;
}
catch (SqlException ex)
{
return ex.Number;
}
}
public object ExcuteScalar(string query, params SqlParameter[] prmArray)
{
try
{
SqlCommand cmd = Intialize(query, prmArray);
object value = cmd.ExecuteScalar();
cmd.Connection.Close();
return value;
}
catch (SqlException ex)
{
return ex.Number;
}
}
public SqlDataReader ExcuteReader(string query, params SqlParameter[] prmArray)
{
SqlCommand cmd = Intialize(query, prmArray);
SqlDataReader sqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return sqlDataReader;
}
public DataTable ExcuteDataTable(string query, params SqlParameter[] prmArray)
{
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(query, connectionString);
if (!query.Contains(" "))
sqlDataAdapter.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;
if (prmArray.Length > 0)
sqlDataAdapter.SelectCommand.Parameters.AddRange(prmArray);
DataTable dt = new DataTable();
sqlDataAdapter.Fill(dt);
return dt;
}
public DataSet ExcuteDataSet(string query, params SqlParameter[] prmArray)
{
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(query, connectionString);
if (!query.Contains(" "))
sqlDataAdapter.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;
if (prmArray.Length > 0)
sqlDataAdapter.SelectCommand.Parameters.AddRange(prmArray);
DataSet ds = new DataSet();
sqlDataAdapter.Fill(ds);
return ds;
}
}
}
In form patient(Table Patient) I create save method and works fine:
public void Save()
{
DBAccess db = new DBAccess();
db.ExcuteNonQuery("insert into Patients (FileNum,PatientTypeID,EngName,NationalityID,RelegionID) values (#FileNum,#PatientTypeID,#EngName,#NationalityID,#RelegionID)",
new SqlParameter("#FileNum", txtFileNum.Text),
new SqlParameter("#PatientTypeID", txtPatientTypeID.Text),
new SqlParameter("#EngName", txtEngName.Text),
new SqlParameter("#NationalityID", txtNationalityID.Text),
new SqlParameter("#RelegionID", txtRelegionID.Text)
);
}
My question is how to make classes for each table in the database?
Use existing solutions like EntityFramework. It provided all the functionality you need and it will make your life much easier than writing it all yourself (and I know, I have done it).
Creating a data access layer is a very critical part of any application. It has to be a separate library so that you can use it in any project today and in the future. Say, tomorrow you want your windows app to be converted to a web application! You can add the DAL libray and start using it.
Having said that, building your own DAL is time cosuming and its like reinventing the wheel. So you need to explore available options which might suite your requirements. Out of the box you have an ORM called EntityFramework. Its pretty straight forward to use but performace wise, its slow compared to handwritten sql. There is also another popular ORM called NHibernate. Its original counterpart Hibernate is huge in java community but it has a very steep learning curve.
But i like to use PetaPoco. It gives best of both worlds. ORM + sql. There are also other such micro ORMs like Dapper, Massive, etc. You need try each one of them and pick the one that suits your application at hand.
Even after choosing a framework that fits your bill, you need to create abstractions to make sure you can change frameworks later if required. Creating a proper DAL is a huge undertaking and has taken big chunk of my time as a developer to get things right. You can explore and find it out by your self. Good luck.
All I want to do is a very simple select/picklist with values from a MySQL database.
I'm trying to find a simple solution online (I'm new to C#) and everything I'm finding is very complicated.
All I want to do is generate the <select><option.. etc parts, with all the attributes and values that I want to set.
This seems like it should be very, very easy. Can anyone give me some basic instructions, or point me to a tutorial that shows how to accomplish this?
Currently, I am using MySqlCommand and MySqlDataReader for classes to talk to the database (for another function).
Create a class for the entity you want to display. Ex : If you want to show all states in the dropdown, create State class
public class State
{
public int ID { set;get;}
public string Name { set;get;}
}
Now write a method in which you query the database and get the result to the DataReader, Iterate over the items and set the values a new object of our State class. Add each object to a list (of State class). So your method's return type will be a List of State class object.
public List<State> GetStates()
{
List<State> stateList=new List<State>();
// execute query, read from reader and add to the stateList
// the below code is SqlServer DB specific.
// you need to change the Connection,Command class for it to use with MySql.
using (var con= new SqlConnection("replace your connection string"))
{
string qry="SELECT ID,NAME FROM STATES";
var cmd= new SqlCommand(qry, objConnection);
cmd.CommandType = CommandType.Text;
con.Open();
using (var objReader = cmd.ExecuteReader())
{
if (objReader.HasRows)
{
while (objReader.Read())
{
var item=new State();
item.ID=reader.GetInt32(reader.GetOrdinal("ID"));
item.Name=reader.GetString(reader.GetOrdinal("Name"));
stateList.Add(item);
}
}
}
}
return stateList;
}
Now, have a DropDownList control in your page,
<asp:DropDownList id="states" runat="server" />
Now in the codebehind of this page, you can set the data for the dropdown( possibly in the Page_Load event)
if(!isPostBack)
{
states.DataSource=yourRepositary.GetStates();
states.DataTextField="Name";
states.DataValueField="ID";
states.DataBind();
}
I think what you searching for is something like a DropDownList, it accepts a DataSource, so you can use your already populated MySqlDataReader as it.
Something like this
MySqlDataReader dr = //the code to fill the MySqlDataReader
DropDownList1.DataSource = dr;
You can create the DropDownList in the design of your page.
To show the data you need to set then the values
DropDownList1.DataValueField = "DbField1";
DropDownList1.DataTextField = "DbField2";
Generally, in a list/drop down you got (a) the value that will be selected and (b) its presentation to the user. The first one might be some primary key, the second might be some label which is self-explaining to the user.
Assuming you got a table FOOD like
FoodValue | FoodLabel
---------------
00010 | Sausage
00020 | Eggs
00030 | Cheese
Put a listbox in your ASP.NET view, e.g. listBox1, then
you can read it in the code behind using
MySqlConnection con = new MySqlConnection("YOUR CONNECTION STRING");
MySqlCommand cmd = new MySqlCommand("SELECT FoodValue, FoodLabel FROM FOOD", con);
con.Open();
MySqlDataReader r = cmd.ExecuteReader();
while(r.Read()) {
listBox1.Items.Add(new ListItem(r["FoodLabel"], r["FoodValue"]);
}
con.Close();
But keep in mind this is a quick and dirty approach. In production code, you will need some separation of the presentation and data layer. Use the data source controls to bind your data in a better way.
i work with N tiers tec in C# for ado, trying to make it easy to use and capable to change any database kind with out write all the cod all over again ,
my code here doesn't get any error but it doesn't get any values to my textbox
(i am trying to get data from table to many textboxs to update it later)
and here how code works:{
at first i make some functions to take any set any kind of parameters or set any command and then i make other function to to execute what ever i set or get from database all that Function i build it in folder name (Data Access Layer)
then i made other folder (Data Build layer)to take use all those function for what ever i want to do in any page (insert , update , delete , Select),
the last think i do it to call the function i made at at (Data Build layer) to my page or control ,
i do all that because if i Change the database Type ,i change only one class and other classes still the same
i hope i explain enough (sorry for my English not good enough)}
Code :
Class DataAccessLayer
public static void Setcommand (SqlCommand cmd,CommandType type,string commandtext)
{
cmd.CommandType=type;
cmd.CommandText=commandtext;
}
public static void AddSQLparameter(SqlCommand cmd, int size,SqlDbType type,object value,string paramName,ParameterDirection direction)
{
if (cmd == null)
{
throw (new ArgumentException("cmd"));
}
if (paramName == null)
{
throw (new ArgumentException("paramName"));
}
SqlParameter param=new SqlParameter();
param.ParameterName= paramName;
param.SqlDbType=type;
param.Size=size;
param.Value=value;
param.Direction=direction;
cmd.Parameters.Add(param);
}
public static SqlDataReader ExecuteSelectCommand(SqlCommand cmd)
{
if (cmd == null)
{
throw (new ArgumentNullException("cmd"));
}
SqlConnection con = new SqlConnection();
cmd.Connection = con;
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
con.Close();
return dr ;
}
Class DatabuildLayer
SqlCommand com;
public DatabuildLayer()
{
com = new SqlCommand();
//
// TODO: Add constructor logic here
//
}
public SqlDataReader SelectCatalog(int catid)
{
DataAccessLayer.Setcommand(com, CommandType.Text, "select catname,catdescription,photo from category where catid=#catid" );
DataAccessLayer.addSQLparameter(com,16,SqlDbType.Int,catid,"#catid",ParameterDirection.Input);
return DataAccessLayer.ExecuteSelectCommand(com);;
}
and here my last code that retrieve my data to some textbox
in my Pageload :
protected void Page_Load(object sender, EventArgs e)
{
DatabuildLayer= new DatabuildLayer();
SqlDataReader dr ;
dr = obj.SelectCatalog(catselectddl.SelectedIndex);
if (dr.Read())
{
catnametxt.Text = dr["catname"].ToString();
catdestxt.Text = dr["catdescription"].ToString();
}
}
Is it possible that the query is returning nothing, and dr.Read() is returning false? Assuming the code actually executes (it is hard to tell from here) that is probably the only thing that would stop it working - either that or empty columns.
For what it is worth I think that your code needs to be tidied up a bit from a structural and conventions point of view. You should probably look through your code and consider the naming guidelines for the .NET framework. When others read your code they will want it formatted and consistent with this documentation. http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx
Further, most people doing ASP.NET these days try to look for some way to inject external dependencies (such as databases) into their code using a framework like WebFormsMVP available at
http://webformsmvp.com/ in conjunction with an IoC container like autofac available at http://code.google.com/p/autofac/.
Using this approach you can push all external dependencies out of your application behind interfaces which would make it fairly trivial to plug in a different database engine.
Your current wrapper code is not doing anything particularly useful (just subsituting the existing methods or your own tht do the same thing), and it is not closing the connections correctly. It is... a bit of a mess.
If you aren't already massively familiar with the raw ADO.NET interfaces, then maybe consider something like "dapper" which will do all this for you, with a sane API:
short catid = 16;
using(var conn = GetOpenConnection()) {
var row = conn.Query(
"select catname,catdescription,photo from category where catid=#catid",
new { catid }).FirstOrDefault();
if(row != null) {
string name = row.catname, desc = row.catdescription;
// ...
}
}
Or if you have a class with CatName / CatDescription properties:
var obj = conn.Query<Catalogue>(
"select catname,catdescription,photo from category where catid=#catid",
new { catid }).FirstOrDefault();
from my experience, when you close a connection associated with a DataReader, nothing can be retrieved from the reader anymore.
//You closed the connection before returning the dr in the your method below:
public static SqlDataReader ExecuteSelectCommand(SqlCommand cmd)
{
if (cmd == null)
{
throw (new ArgumentNullException("cmd"));
}
SqlConnection con = new SqlConnection();
cmd.Connection = con;
con.Open();
SqlDataReader dr = cmd.ExecuteReader();
con.Close(); //here your connection was already closed
return dr ; //this dr is disconnected
}
I did this code in my presentation layer but i don't know how to do it on 3 tier . .
Please help, i'm stack with this problem.
form1_Load()
{
cboStatus();
GetlistView();
}
region "FILL combo"
public void cboStatus()
{
try
{
SqlConnection conn = new SqlConnection(connStr);
SqlCommand sqlcom = new SqlCommand("sp_loadStatus",conn);
sqlcom.CommandType = CommandType.StoredProcedure;
SqlDataReader dr = null;
conn.Open();
dr = sqlcom.ExecuteReader();
cmbStatus.Items.Clear();
while (dr.Read())
{
cmbStatus.Items.Add((dr["StatusDescription"]));
}
if (conn.State == ConnectionState.Open) conn.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error Occurred:" + ex);
}
finally
{
}
}
#endregion
#region "fill listview"
public void GetlistView()
{
int i = 0;
SqlConnection sqlcon = new SqlConnection(connStr);
lstBF.Items.Clear();
SqlCommand sqlcom = new SqlCommand("sp_LoadNew", sqlcon);
sqlcom.CommandType = CommandType.StoredProcedure;
SqlDataReader dr;
lstBF.Items.Clear();
sqlcon.Open();
dr = sqlcom.ExecuteReader();
while (dr.Read())
{
lstBF.Items.Add(dr["SerialNumber"].ToString());
lstBF.Items[i].SubItems.Add(dr["PartNumber"].ToString());
lstBF.Items[i].SubItems.Add(dr["StatusDescription"].ToString());
lstBF.Items[i].SubItems.Add(dr["CustomerName"].ToString());
lstBF.Items[i].SubItems.Add(dr["DateCreated"].ToString());
lstBF.Items[i].SubItems.Add(dr["CreatedBy"].ToString());
lstBF.Items[i].SubItems.Add(dr["ModifiedBy"].ToString());
i = i + 1;
}
if (sqlcon.State == ConnectionState.Open) sqlcon.Close();
}
#endregion
There are probably a handful of things you can do, but I think the most basic would be to make Business objects (C# Class) that represent Status and whatever you are displaying in your ListView (probably a Product of some sort).
Your Data Tier should have the Data Access code (SqlConnection and such) that builds collections of Statuses and Products.
Your Business Tier will take care of any business logic (which you don't have much here, which is fine).
Your Presentation Tier will Bind the UI Controls to the collections returned from the Data Tier (via the Business Tier).
Really, your Business objects are Business Tier entities.
From there, you can expand to make Data Access classes that correspond to your Business objects and such.
Here's a good tech article to get you started:
http://msdn.microsoft.com/en-us/library/ms973279.aspx
To start out, you would put this code in a separate file or project and call that the DataLayer. Instead of binding to your listview, you would return a data reader. Then, you would create a business layer that has some business classes. In this case you could start with "Part" or "Order". That would contain the properties from your data table as properties on the class. Then, create a controller that would output a list of your Parts or Orders and pass them back to the presentation layer.
This way is one example and allows you to maintain separation of your presentation and data.