C# Database Mapper - c#

I was looking to map my database query results to strongly type objects in my c# code. So i wrote a quick and dirty helper method on the SqlConnection class which runs the query on the database and uses reflection to map the record columns to the object properties. The code is below:
public static T Query<T>(this SqlConnection conn, string query) where T : new()
{
T obj = default(T);
using (SqlCommand command = new SqlCommand(query, conn))
{
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
obj = new T();
PropertyInfo[] propertyInfos;
propertyInfos = typeof(T).GetProperties();
for (int i = 0; i < reader.FieldCount; i++)
{
var name = reader.GetName(i);
foreach (var item in propertyInfos)
{
if (item.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) && item.CanWrite)
{
item.SetValue(obj, reader[i], null);
}
}
}
}
}
}
return obj;
}
public class User
{
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public DateTime signupDate { get; set; }
public int age { get; set; }
public string gender { get; set; }
}
var user = conn.Query<User>("select id,firstname,lastname from users");
I just wanted a second opinion on my approach above of using reflection to tie the values together, if there's anything i can do better in the code above. Or if there's some other totally different approach i can take to get the same result?
I think i can probably improve the code in the helper method by removing the loop for propertyInfos and using a dictionary instead. Is there anything else that needs to be tweaked?
P.S: i'm aware of Dapper, i just wanted to implement something similar on my own to help me learn better.

What you've done is basically what linq-to-sql or other OR-mappers do under the hood. To learn the details of how it works it's always a good idea to write something from scratch.
If you want more inspiration or want to have something that's ready for production use out-of-the-box I'd recommend reading up on linq-to-sql. It is lightweight, yet competent.

There are a few of things I can think of:
I think that in order to skip the loop you can use:
reader[item.Name]
I've done something similar myself, but I never ran into dapper. I'm not sure if it uses reflection, but it's always a good idea to read someone else's code to sharpen your skill (Scott Hanselman frequently recommends doing so).
You can also look at:
http://www.codeproject.com/KB/database/metaquery_part1.aspx
You can implement an attribute that maps a field to a database column, but that's just for fun.
Edit:
5: You can also skip the while loop over the reader and just take the first row, and document the fact that your query only returns one object, so it doesn't pull a thousand rows if the query returns a thousand rows.

Related

SQL Server database information to properties class c#

I need a method that takes the information of a selected row (with another method, already written (SQL Count)) and puts it in an object called Gebruiker (user).
I already have a method that puts the information from an object in to the database with parameters, but this doesn't work for the other way.
This is my object class:
namespace BurnThatFat
{
class Gebruikerklasse
{
public string Naam;
public string Achternaam;
public int Leeftijd;
public string Geslacht;
public int Huidiggewicht;
public int Streefgewicht;
public string Gebruikersnaam;
public string Wachtwoord;
public override string ToString()
{
return Naam;
}
}
}
and this is the method that puts the information from the object to the database:
public void SignUp(string commandText, Gebruikerklasse gebruiker)
{
// nieuwe connectie maken
// ontvangt de query vanuit 'buttonclick' en voert hem hier uit
// als ExecuteNonQuery niet kan worden uitgevoerd is er iets fout gegaan. D.m.v een bool moet hij dan een bericht tonen
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(commandText, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#Naam", gebruiker.Naam);
cmd.Parameters.AddWithValue("#Achternaam", gebruiker.Achternaam);
cmd.Parameters.AddWithValue("#Leeftijd", gebruiker.Leeftijd);
cmd.Parameters.AddWithValue("#Geslacht", gebruiker.Geslacht);
cmd.Parameters.AddWithValue("#Huidiggewicht", gebruiker.Huidiggewicht);
cmd.Parameters.AddWithValue("#Streefgewicht", gebruiker.Streefgewicht);
cmd.Parameters.AddWithValue("#Gebruikersnaam", gebruiker.Gebruikersnaam);
cmd.Parameters.AddWithValue("#Wachtwoord", gebruiker.Wachtwoord);
int a = cmd.ExecuteNonQuery();
if (a == 1)
{
Success = true;
}
else if (a == -1)
{
Success = false;
}
conn.Close();
}
}
So how do I have to do this? I don't know how to google this really. I think I'm using the wrong words while googling, because im getting non related things...
Edit: added screenshots
So I need the information in this table: http://prnt.sc/dsg95v
To be stored in this object: http://prnt.sc/dsghl1
I already have code (above) that returns the information from the object to the table. I do that with parameters.
I really don't know where to start with database to object...
Edit again: something like this:
public void DatabaseTransferObject(string commandText, Gebruikerklasse gebruiker)
{
using (SqlConnection conn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(commandText, conn))
{
conn.Open();
gebruiker.Naam = //code to get Naam (name) from table Gebruiker (user);
gebruiker.Leeftijd = //code to get Leeftijd(age) from table Gebruiker (user);
conn.Close();
}
}
This the SQL code to get all the information from the row with the given #username
"Select * from Gebruiker where Gebruikersnaam = #Gebruikersnaam;
It seems pretty clear this is homework, and it is far from clear what you are trying to do, so there will be lacunae for you to fill in. There is a fair amount that should be done differently.
DB Design
For some kind of fitness/weight tracker, it really seems like that should be at least 2 tables. One for user data and one for the current weights. The current weight alone isnt very interesting unless you also know previous weights to see if the trend is Up or Down, no? Such a table would also reveal the rate of loss/gain ("getting in shape" can result in less loss than expected - or even a gain - if fat is being converted to muscle).
Something like {Id, UserId, Date, Weight} which associates a number of weight values on specific dates with a user would work.
Never store something which can be easily calculated (ie Age). Sooner or later such values will be wrong and/or you build in an excessive amount of maintenance to the system.
Never, ever store Passwords as plain text. Hash them. Always.
Gebruikerklasse (User)
As per your title, your class has no Properties, just public fields/members. The difference matters a great deal when it comes to data binding.
The table seems to store both the login and fitness related data. But the login data plays no role in the weight tracking and vice versa, so you probably should have 2 classes: one using the log in columns and the other using the tables related to weight tracking
The db PK Id is not represented which will be critical to updating.
So here is how my User class would look for the fitness related elements:
public class User
{ // critical
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public int Age
{
get { return DateTime.Now.Date.Year - DateOfBirth.Date.Year; }
}
public string Gender { get; set; }
public int CurrentWeight { get; set; }
public int Targetweight { get; set; }
private static string dbConnStr = "server=127.0.0.1;database=...";
public User()
{ }
...
The log in related data is omitted; another class would deal with that (if I even needed a class for it). Also, the DateOfBirth is used to calculate an age. You can add code to also check the month to use an ANB (Age Nearest) age rather than ALB (Age at Last Birthday) method.
As noted, CurrentWeight should probably be some sort of collection of Date and Weight values.
Next, in OOP you generally want the class to do all the work (or as much as possible) related to managing the data related to it. So rather than a DatabaseTransferObject method to "push" data into the class object, use class methods for it to load the data itself.
There are many ways to do it. This this one uses a static method to create a new User object from row data. This uses MySQL, but the way DBProviders works varies very little from one another:
// create a user object from an ID
public static User Load(int id)
{
string sql = "SELECT * FROM gebruiker WHERE Id = #id";
using (var dbCon = new MySqlConnection(dbConnStr))
using (var cmd = new MySqlCommand(sql, dbCon))
{
dbCon.Open();
cmd.Parameters.Add("#id", MySqlDbType.Int32).Value = id;
using (var rdr = cmd.ExecuteReader())
{
if (rdr.HasRows)
{
User U = new User();
rdr.Read();
U.Id = id;
U.Name = rdr.GetString(rdr.GetOrdinal("FirstName"));
U.LastName = rdr.GetString(rdr.GetOrdinal("LastName"));
U.DateOfBirth = rdr.GetDateTime(rdr.GetOrdinal("BirthDate"));
//...
return U;
}
else { return null; }
}
}
}
SQL Server lacks the Getxxxx(string) overrides to get data by column by name. For others that have that override like MySQL, it is slightly simpler:
U.Name = rdr.GetString("FirstName");
U.LastName = rdr.GetString("LastName");
U.DateOfBirth = rdr.GetDateTime("BirthDate");
Usage:
User U = new User(1);
An instance method could be used instead to set the properties (this.Name = rdr.GetString("FirstName");); as I said there are many ways to do it. A complementary Save() method would almost certainly be an instance method to INSERT/UPDATE the db from the local data.
Assuming you want to learn, not just get a grade, a simpler way to do that is with an ORM. These map db data to class objects for you. Dapper is a micro-ORM which maps DB data to a class using connection extensions:
User u = dbCon.Query<User>(SQL, new { id = 1 });
Dapper will create a new User object from the record with the Id 1. It won't quite work that simply with your design because the column and property names do not match.

C# Generic binding

I am working on an assignment for school and trying to implement as much features just for learning sake. Hence I've made a generic mapper that maps databse tables to objects to see what's possible. The Db in this case is local. I know I'm making loads and loads of calls and should go around this very differently but....
Everything works as intended except for when a class has a Collection of another class.
Example:
class Student {
public int Id { get; set; }
public string Name { get; set; }
}
My method for filling a list of all the students in the database.
public List<TModel> MapEntitiesFromDb<TModel>(string tablename, string customquery = "") where TModel : class, new()
{
try
{
sql = ValidateSelectSql(tablename, customquery);
}
catch (AccessViolationException ex) { Console.WriteLine(ex.Message); }
command.CommandText = sql;
command.Connection = conn;
List<TModel> list = new List<TModel>();
try
{
using (conn)
{
Type t = new TModel().GetType();
conn.Open();
using (reader = command.ExecuteReader())
{
if (t.GetProperties().Length != reader.FieldCount)
throw new Exception("There is a mismatch between the amount of properties and the database columns. Please check the input code and try again.");
//Possible check is to store each column and property name in arrays and match them to a new boolean array, if there's 1 false throw an exception.
string columnname;
string propertyname;
//Pairing properties with columns
while (reader.Read())
{
TModel obj = new TModel();
for (int i = 0; i < reader.FieldCount; i++)
{
columnname = reader.GetName(i).ToString().ToLower();
PropertyInfo[] properties = t.GetProperties();
foreach (PropertyInfo propertyinfo in properties)
{
propertyname = propertyinfo.Name.ToLower();
if (propertyname == columnname)
{
propertyinfo.SetValue(obj, reader.GetValue(i));
break;
}
}
}
list.Add(obj);
}
}
}
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
return list;
}
My ValidateSelectSql just returns the sql string that needs to be used in the query.
After calling:
List<Student> = MapEntitiesFromDb<Student>("students");
It will return a list with all the students like intended.
Things go wrong when I add a collection for example:
class Student {
public Student()
{
this.Courses = new List<Course>();
string customsqlquery = ::: this works and is tested! :::
Courses = MapEntitiesFromDb<Course>("", customsqlquery);
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Course> Courses;
}
The courses list returned empty and with some help of the debugger tool I found out at the time of creating the object the Id property is 0 of course. In my query I am filtering on student Id but at the time of executing the method to fill the Courses list in the constructor the Id of student will always be 0 becuase it's set at a later stage and the result will be no courses in the list.
I'm wondering if I should put a check for an ICollection property after the other properties are set and if so execute a method on the object that in return executes the method that's now inside the constructor?
I can't call any methods on TModel, else it would be as simple as finding if TModel has a collection property and call obj.FillCollection(); after the Id property has been assigned in the GetEntitiesFromDb method.
I was also thinking about recursion. Again I'd have to find if obj has a collection property and then call GetEntitiesFromDB but it seems undoable because I also need to find out the type in between <> and I Can't send any customquery from the outside...
Maybe tackle it from a whole other perspective?
I can really use some advice on how to tackle this problem.
The most straightforward way to approach this would be to have the collection property lazy load what it needs. I would additionally recommend that you use IEnumerable<T> instead of ICollection<T> because this represents a read-only view of what's currently in the database, nobody should be modifying it in any way.
public class Student
{
private readonly Lazy<IEnumerable<Course>> courses;
public int Id { get; set; }
public IEnumerable<Course> Courses => this.courses.Value;
public Student()
{
this.courses = new Lazy<IEnumerable<Course>>(LoadCourses);
}
private IEnumerable<Course> LoadCourses()
{
var sql = "custom SQL query that uses this.Id after it's loaded";
return MapEntitiesFromDb(sql);
}
}
I'm only recommending this approach because you mentioned that this is just an academic exercise to help you learn about the tools available to you. In an actual production environment this approach would very quickly become unwieldy and I would recommend using Entity Framework instead (which may be something else that you might want to learn about).

Update List of classes with data from a list of classes

I have a class:
public class DataMember {
public string ID{ get; set; }
public List<string> Versions { get; set; }
}
And another class:
public class MasterDataMember {
public string ID { get; set; }
public List<string> FoundVersions { get; set; }
}
I store both sets of data in a Cache as:
List<DataMember> datamembers
List<MasterDataMember> masterdatamembers
When originally built, the MasterDataMember is a list of partial "versions". These versions need to be confirmed and found in the list of DataMember's.
How can I update masterdatamembers with the confirmed versions found in datamembers?
(this code block is untested but it illustrates what I'm trying to do)
foreach (MasterDataMember item in masterdatamembers) {
List<string> confirmedvers = new List<string>();
foreach(string rawver in item.FoundVersions ){
foreach(DataMember checkitem in datamembers){
foreach (string confirmedver in checkitem.Versions) {
if (rawver.Contains(confirmedver)) {
confirmedvers.Add(confirmedver);
}
}
}
}
item.FoundVersions = vers;
}
Is there a LINQ that can accomplish this a lot easier, faster (I've already tried lots of ideas, iterations)?
Speed is the key here since both lists can be hundreds to thousands long.
Thank you in advance!
foreach (MasterDataMember item in masterdatamembers) {
IEnumerable<string> confirmedvers = item.FoundVersions.Where(rawver => rawver.Any(confirmedver => datamembers.Any(checkitem => checkitem.Versions.Contains(rawver)));
}
HOLY crap bro that was confusing as hell for me!
Awesome mind experiment though!
If speed really is your primary concern because of large lists, then you'll want to use hash table constructs. Using LINQ is slick, but won't necessarily make things faster (or clearer) for you. What you really need is to use the proper collection type.
Assumptions made for the code that follows:
datamembers cache cannot have duplicate DataMember entries (where more than one entry has the same ID).
masterdatamembers cache cannot have duplicate MasterDataMember entries (where more than one entry has the same ID).
In both DataMember and MasterDataMember, the Versions and FoundVersions lists cannot have duplicate version entries.
Algorithm Description
I still feel that your code block doesn't quite reflect your intent. And unfortunately, as a result, I think you got wrong answers.
This is the algorithm I followed, based on trying to interpret your intended result:
For each master data member, update its FoundVersions set (or list) by only keeping the versions in the list that can also be found in the matching data member's Versions set (or list). If no matching data member is found, then I assume you want the master data members FoundVersions set (or list) to be emptied, as none of the versions can be confirmed.
Implementation
Notice that I replaced a few uses of List<T> with Dictionary<K, V> or HashSet<T> where it would benefit performance. Of course, I am assuming that your lists can become large as you said. Otherwise, the performance will be similar as simple lists.
Your 2 classes, (notice the change in types):
public class DataMember
{
public string ID { get; set; }
public HashSet<string> Versions { get; set; } // using hashset is faster here.
}
public class MasterDataMember
{
public string ID { get; set; }
public HashSet<string> FoundVersions { get; set; } // used HashSet for consistency, but for the purposes of the algorithm, a List can still be used here if you want.
}
Your cached data, (notice the change to a Dictionary):
Dictionary<string, DataMember> datamembers; // using a Dictionary here, where your key is the DataMember's ID, is your fastest option.
List<MasterDataMember> masterdatamembers; // this can stay as a list if you want.
And finally, the work is done here:
foreach (var masterDataMember in masterdatamembers)
{
DataMember dataMember;
if (datamembers.TryGetValue(masterDataMember.ID, out dataMember))
{
HashSet<string> newSet = new HashSet<string>();
foreach (var version in masterDataMember.FoundVersions)
{
if (dataMember.Versions.Contains(version))
{
newSet.Add(version);
}
}
masterDataMember.FoundVersions = newSet;
}
else
{
masterDataMember.FoundVersions.Clear();
}
}
Your code will look like something like this in Linq
masterDataMembers.ForEach(q=>q.FoundVersions = (from rawver in q.FoundVersions from checkitem in dataMembers from confirmedver in checkitem.Versions where rawver.Contains(confirmedver) select confirmedver).ToList());

Transforming DataTable to List<T> in C#

I've already searched through StackOverflow (and other websites) about transforming a DataTable to List with reflection in C#.
My results until now are pretty good: I can reflect 200k lines in 3.5 seconds (0.5 seconds in hardcoded mode).
But my entities (the classes that represent my data, but I think you already know that) follow this pattern:
My database have columns like this (I don't actually do this, but you'll get the idea):
Table: Clients
Columns:
ClientID, ClientName, ClientPhone, CityID[FK]
I'm using SqlConnection (MySqlConnection), so I have to hardcode my entities and transform the database result in a list of this entity. Like:
Select *, cit.* from Clients cli
Inner join Cities cit on (cit.CityID == cli.CityID)
Inner join Countries cou on (cou.CountryID == cit.CountID)
I don't know if this SQL is correct, but I think you got the idea. This should return some fields like this:
ClientID, ClientName, ClientPhone, CityID, CityName, CountryID, CountryName
Shoud result a List<Client>.
Here's the problem: I have 2 inner joins and I represent this data in my entities like this (I like the expression "like this"):
public class Client
{
public int ClientID { get; set; }
public string ClientName { get; set; }
public string ClientPhone { get; set; }
public City ClientCity { get; set; }
}
public class City
{
public int CityID { get; set; }
public string CityName { get; set; }
public Country CityCountry { get; set; }
}
public class Country
{
public int ContryID { get; set; }
public string CountryName { get; set; }
}
So, if I have a Client object, I would get its country name by the expression client.ClientCity.CityCountry.CountryName. I call it a 3-level property acessor.
And I want to reflect it properly. Here is the main method to transform the DataTable into a List. My native language is Portuguese, but I tried to translate my comments to match my description above.
The idea of this code is: I try to find in the main class the column I have to set. If I don't find it, I search the property in the properties that are objects. Like CityName inside ClientCity inside Client. This code is a mess.
public List<T> ToList<T>(DataTable dt) where T : new()
{
Type type= typeof(T);
ReflectionHelper h = new ReflectionHelper(type);
insertPropInfo(tipo); //a pre-reflection work, I cache some delegates, etc..
List<T> list = new List<T>();
DataTableReader dtr = dt.CreateDataReader();
while (dtr.Read())
{
T obj = new T();
for (int i = 0; i < dtr.FieldCount; i++)
{
GetObject(ref obj, tipo, dtr.GetName(i), dtr.GetValue(i));
}
list.Add(obj);
}
return lista;
}
//ref T obj: the object I create before calling this method
//Type classType: the type of the object (say, Client)
//string colName: this is the Database Column i'm trying to fill. Like ClientID or CityName or CountryName.
//colLineData: the data I want to put in the colName.
public void GetObject<T>(ref T obj, Type classType, string colName, object colLineData) where T : new()
{
//I do some caching to reflect just once, and after the first iteration, I think all the reflection I need is already done.
foreach (PropertyInfo info in _classPropInfos[classType])
{
//If the current PropertyInfo is a valuetype (like int, int64) or string, and so on
if (info.PropertyType.IsValueType || info.PropertyType == typeof(string))
{
//I think string.Equals is a little faster, but i had not much difference using "string" == "string"
if (info.Name.Equals(colName)) //did I found the property?
if (info.PropertyType != typeof(char)) //I have to convert the type if this is a Char. MySql returns char as string.
{
_delegateSetters[info](obj, colLineData); //if it isn't a char, just set it.
}
else
{
_delegateSetters[info](obj, Convert.ChangeType(colLineData, typeof(char)));
}
break;
}
else //BUT, if the property is a class, like ClientCity:
{
//I reflect the City class, if it isn't reflected yet:
if (!_classPropInfos.ContainsKey(info.PropertyType))
{
insertPropInfo(info.PropertyType);
}
//now I search for the property:
Boolean foundProperty = false;
object instance = _delegateGetters[info](obj); //Get the existing instance of ClientCity, so I can fill the CityID and CityName in the same object.
foreach (PropertyInfo subInfo in _classPropInfos[info.PropertyType])
{
if (subInfo.Name.Equals(colName))//did I found the property?
{
if (instance == null)
{
//This will happen if i'm trying to set the first property of the class, like CityID. I have to instanciate it, so in the next iteration it won't be null, and will have it's CityID filled.
instance = _initializers[info.PropertyType]();//A very fast object initializer. I'm worried about the Dictionary lookups, but i have no other idea about how to cache it.
}
_delegateSetters[subInfo](instance, colLineData);//set the data. This method is very fast. Search about lambda getters & setters using System.Linq.Expression.
foundProperty = true;
break;//I break the loops when I find the property, so it wont iterate anymore.
}
}
if (foundProperty)//if I found the property in the code above, I set the instance of ClientCity to the Client object.
{
_delegateSetters[info](obj, instance);
break;
}
}
}
}
There is a problem with this code: I can reach the CityID and CityName, and fill it. But CountryID and CountryName wont. Because this code can do a 2-level reflection, I need some recursive-approach to fill many levels I need. I tried to do this BUT i got so many stack overflows and null reference exceptions I almost gave up.
This code would make it much easier to fetch database rows, Did you already find some library or anything that does what I want? If not, how could I achieve a n-level reflection to make a proper List from a DataTable?
Your problem is really common and practically every ORM in circulation addresses this question.
Of course changing an already written application to take advantage of an ORM is often unpractical, but there are some simple ORM that are really easy to add to an existing application and let you replace incrementally the already written code.
One of these ORMs is DAPPER. It consists of just one source file that you can include directly in the same project with your POCO classes and repository methods (Or just reference the compiled assembly). It is really easy to learn and it is incredibly fast considering the complexity of the work to be carried out. Not to mention that the authors of this little gem are regularly on this site answering questions on their work. Just do a search with the #dapper tag
The only nuisances that I have found to date are the mapping one-to-one from your POCO properties and the field names and also the sometime eluding rules between PK and FK when your keys are not named ID. But that's me that I still haven't fully understood these rules.
Consider to use EntityFramework. It will automate all this work.
This is based on you getting a dataset with the 3 tables and creating the proper DataRelation.
On your particular case(200k lines) i dont know how it will perform but shouldnt be that bad :).
Your calling code could be something like this:
List<Clients> clients = Test.CreateListFromTable<Clients>(ds.Tables["Clients"]);
Remember as i said its based in you fettching the dataset and creating the relations.
Next here is the class with the methods in question(ClientsToCity and CityToCountry are the names of the datarelations,you can place your own):
public class Test
{
// function that set the given object from the given data row
public static void SetItemFromRow<T>(T item, DataRow row) where T : new()
{
foreach (DataColumn c in row.Table.Columns)
{
PropertyInfo prop = item.GetType().GetProperty(c.ColumnName);
if (prop != null && row[c] != DBNull.Value)
{
prop.SetValue(item, row[c], null);
}
else
{
if (c.ColumnName == "CityID")
{
object obj = Activator.CreateInstance(typeof(City));
SetItemFromRow<City>(obj as City, row.GetChildRows("ClientsToCity")[0]);
PropertyInfo nestedprop = item.GetType().GetProperty("ClientCity");
nestedprop.SetValue(item, obj, null);
}
else if (c.ColumnName == "CountryID")
{
object obj = Activator.CreateInstance(typeof(Country));
SetItemFromRow<Country>(obj as Country, row.GetChildRows("CityToCountry")[0]);
PropertyInfo nestedprop = item.GetType().GetProperty("CityCountry");
nestedprop.SetValue(item, obj, null);
}
}
}
}
// function that creates an object from the given data row
public static T CreateItemFromRow<T>(DataRow row) where T : new()
{
T item = new T();
SetItemFromRow(item, row);
return item;
}
// function that creates a list of an object from the given data table
public static List<T> CreateListFromTable<T>(DataTable tbl) where T : new()
{
List<T> lst = new List<T>();
foreach (DataRow r in tbl.Rows)
{
lst.Add(CreateItemFromRow<T>(r));
}
return lst;
}
}

What is a better, cleaner way of using List<T>

I'm looking to implement a few nicer ways to use List in a couple of apps I'm working on. My current implementation looks like this.
MyPage.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
BLL.PostCollection oPost = new BLL.PostCollection();
oPost.OpenRecent();
rptPosts.DataSource = oArt;
rptPosts.DataBind();
}
BLL Class(s)
public class Post
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostCreatedDate { get; set; }
public void OpenRecentInitFromRow(DataRow row)
{
this.PostId = (int) row["id"];
this.PostTitle = (string) row["title"];
this.PostContent = (string) row["content"];
this.PostCreatedDate = (DateTime) row["createddate"];
}
}
public class PostCollection : List<Post>
{
public void OpenRecent()
{
DataSet ds = DbProvider.Instance().Post_ListRecent();
foreach (DataRow row in ds.Tables[0].Rows)
{
Post oPost = new Post();
oPost.OpenRecentInitFromRow(row);
Add(oPost);
}
}
}
Now while this is working all well and good, I'm just wondering if there is any way to improve it, and just make it cleaner that having to use the two different classes do to something I think can happen in just one class or using an interface.
For one thing, I wouldn't derive from List<T> - you aren't really specializing the behaviour.
I'd also suggest that you could make Post immutable (at least externally), and write a static method (or constructor) to create one based on a DataRow:
public static Post FromDataRow(DataRow row)
Likewise you can have a list method:
public static List<Post> RecentPosts()
which returns them. Admittedly that might be better as an instance method in some sort of DAL class, which will allow mocking etc. Alternatively, in Post:
public static List<Post> ListFromDataSet(DataSet ds)
Now, as for the use of List<T> itself - are you using .NET 3.5? If so, you could make this considerably neater using LINQ:
public static List<Post> ListFromDataSet(DataSet ds)
{
return ds.Tables[0].AsEnumerable()
.Select(row => Post.FromDataRow(row))
.ToList();
}
Are you deriving from List<T> because you want to offer other consumers of PostCollection the ability to Add and Remove items? I'm guessing not, and that you actually just want a way to expose a collection you can bind to. If so, you could consider an iterator, perhaps:
class BLL {
...
public IEnumerable<Post> RecentPosts {
get {
DataSet ds = DbProvider.Instance().Post_ListRecent();
foreach (DataRow row in ds.Tables[0].Rows)
{
Post oPost = new Post();
oPost.OpenRecentInitFromRow(row);
yield return oPost;
}
}
}
...
}
Notwithstanding the fact that this might be considered poor form (in that we have a property getter that might be making a network call), this iterator approach will do away with the overhead of calling OpenRecentInitFromRow for Posts that are never enumerated.
You also become agnostic as to how potential consumers of your Posts might want to consume them. Code that absolutely, positively has to have every Post can do ToList(), but other code might want to use a LINQ query that short-circuits the enumeration after the right Post is found.
Edit: John Skeet's answer is probably a better option. But if you want to make just a few simple changes, read on:
Place the database access code, OpenRecentInitFromRow into the PostCollection and treat that as a Post manager class. That way the Post class is a plain old Data Transfer Object.
public class Post
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostCreatedDate { get; set; }
}
public class PostCollection : List<Post>
{
public void OpenRecent()
{
DataSet ds = DbProvider.Instance().Post_ListRecent();
foreach (DataRow row in ds.Tables[0].Rows)
{
Add(LoadPostFromRow(row));
}
}
private Post LoadPostFromRow(DataRow row)
{
Post post = new Post();
post.PostId = (int) row["id"];
post.PostTitle = (string) row["title"];
post.PostContent = (string) row["content"];
post.PostCreatedDate = (DateTime) row["createddate"];
return post;
}
}
I'm looking to implement a few nicer ways to use List
That seems like an odd request. The "List" type is a means, rarely an end. With that in mind, one nicer way to accomplish your real end is to use IEnumerable rather than List, because that List forces you to keep your entire collection in memory while IEnumerable only requires one object at a time. The trick is just that you have to wire everything in your processing stream, from the data layer all the way up through presentation, to use it.
I have a good example in the link below about how to do this in a very clean way:
Fastest method for SQL Server inserts, updates, selects
Depending on your existing data layer code you may be able to skim much of the first half of the (long) post - the main point is that you use an iterator block to turn an SqlDataReader into an IEnumerable<IDataRecord>. Once you have that, it's pretty straightforward the rest of the way through.
You could do this:
protected void Page_Load(object sender, EventArgs e)
{
BLL.PostCollection oPost = new BLL.PostCollection();
rptPosts.DataSource = Post.OpenRecent();
rptPosts.DataBind();
}
public class Post
{
public int PostId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostCreatedDate { get; set; }
public void OpenRecentInitFromRow(DataRow row)
{
this.PostId = (int) row["id"];
this.PostTitle = (string) row["title"];
this.PostContent = (string) row["content"];
this.PostCreatedDate = (DateTime) row["createddate"];
}
public static List<Post> OpenRecent()
{
DataSet ds = DbProvider.Instance().Post_ListRecent();
foreach (DataRow row in ds.Tables[0].Rows)
{
Post oPost = new Post();
oPost.OpenRecentInitFromRow(row);
Add(oPost); //Not sure what this is doing
}
//need to return a List<Post>
}
}

Categories

Resources