Generalized DTO population method with different query select lists - c#

For reasons that I do not quite understand, I have chosen not to use an ORM Framework and have gone with a generalized ADO.NET data access layer. I initially created a single database class from which all my controllers had access. As anyone but myself could have predicted, this access object has become a monstrosity.
In an attempt to refactor my data layer, I have created a 'database adapter' class as a DI injected service and have created a 'service layer' to utilize it. So each controller now has a 'domain service' that will use the database adapter to query the database and return a generic data table. The service will then populate the result of the queries and return the domain objects back to the controller where it can assemble the view models.
I am running into an issue where I cannot seem to abstract the code designed to map the DataSets returned from the database access layer because each query may select different fields. For example, a simple reference data service:
public class ReferenceDataService : IReferenceDataService
{
private IDatabaseAdapter _dbAdapter;
public ReferenceDataService(IDatabaseAdapter dbAdapter)
{
_dbAdapter = dbAdapter;
}
public IEnumerable<ReferenceData> GetReferenceData(string table)
{
List<ReferenceData> rdList = new List<ReferenceData>();
StringBuilder sb = new StringBuilder();
sb.Append("SELECT [CODE], [LABEL] FROM [dbo].");
sb.Append(table);
sb.Append(" WHERE END_DATETIME > GETDATE()");
DataSet ds = _dbAdapter.ExecuteDataSet(sb.ToString(), null);
foreach (DataRow row in ds.Tables[0].Rows)
{
rdList.Add(PopulateRecord(row));
}
return rdList;
}
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = (int)row["REFERENCE_ID"],
Code = (string)row["CODE"],
Label = (string)row["LABEL"],
Description = (string)row["DESCRIPTION"],
BeginDatetime = (DateTime)row["BEGIN_DATETIME"],
EndDatetime = (DateTime)row["END_DATETIME"],
UpdatedBy = (string)row["UPDATED_BY"],
UpdatedOn = (DateTime)row["UPDATED_ON"],
CreatedBy = (string)row["CREATED_BY"],
CreatedOn = (DateTime)row["CREATED_ON"]
};
}
}
In this example, I have an exception thrown from the populate method, because as you can see, I am only selecting code and label for this particular method. I'd like to avoid a custom mapping for every method but I also do not want to needlessly return ALL the data from each table row to the controller. I'd like to keep the populate method generic so that any query against that table will be mapped appropriately.
I realize I'm basically almost rolling my own ORM, but I'd like to use a service pattern without it because at this point I am way too invested.

After some digging around, it appears there was a very obvious and straightforward solution that I had been missing. The DataRow instance object has the ability to check it's parent table columns for existence. By wrapping each assignment from the table row in one of these checks, then the population method will not care what was actually selected into the DataTable and will be able to populate an object regardless of the amount of data returned from the query.
So in my example, if I want to keep a generic population method for ReferenceData but use a query that only retuns the CODE and LABEL columns, the following change would keep the population of the returned business object agnostic and error free:
private ReferenceData PopulateRecord(DataRow row)
{
return new ReferenceData
{
ReferenceId = row.Table.Columns.Contains("REFERENCE_ID") ? (int)row["REFERENCE_ID"] : default(int),
Code = row.Table.Columns.Contains("CODE") ? (string)row["CODE"] : default(string),
Label = row.Table.Columns.Contains("LABEL") ? (string)row["LABEL"] : default(string),
Description = row.Table.Columns.Contains("DESCRIPTION") ? (string)row["DESCRIPTION"] : default(string),
BeginDatetime = row.Table.Columns.Contains("BEGIN_DATETIME") ? (DateTime)row["BEGIN_DATETIME"] : default(DateTime),
EndDatetime = row.Table.Columns.Contains("END_DATETIME") ? (DateTime)row["END_DATETIME"] : default(DateTime),
UpdatedBy = row.Table.Columns.Contains("UPDATED_BY") ? (string)row["UPDATED_BY"] : default(string),
UpdatedOn = row.Table.Columns.Contains("UPDATED_ON") ? (DateTime)row["UPDATED_ON"] : default(DateTime),
CreatedBy = row.Table.Columns.Contains("CREATED_BY") ? (string)row["CREATED_BY"] : default(string),
CreatedOn = row.Table.Columns.Contains("CREATED_ON") ? (DateTime)row["CREATED_ON"] : default(DateTime)
};
}
This would allow me to use PopulateRecord on a select statement that only returned CODE and LABEL (as I would want to do if I was populating a SelectItemList for a dropdown for example).
I do not know what kind of performance hit this may or may not incur so that is something to possibly consider. But this allows for the flexibility I was looking for. I hope this post will help someone else who might be looking for the same type of solution.
If there are better ways to approach this please let me know. Thanks!

Related

EF query readonly Iqueryable

I have tens of different views and on some, the data that comes from database, must be manipulated before shown to user.
for example:
var students = _repository.Students.Select(c => { c.StartDate = FixDateToCurrentYear(c.StartDate ); c.EndDate = FixDateToCurrentYear(c.EndDate); return c; }).ToList();
So basically I now have a variable, where Students StartDate and EndDate have been fixed by some logic, which is not relevant at the moment.
The problem here is that, whenever somewhere a SaveChanges() function is called, then the changed dates for students are also persisted to database.
I only want to use the changed dates for the logic in specific view. How could I handle this?
I have heard about .AsNoTracking(); but this must be put directly to DbSet item, but I want to handle it on controller side, is it possible?
Pull the students from the db first and the do a select to an anonymous type. EF won't know how to track changes on that.
var students =
_repository.Students.ToList()
.Select(s =>
new { StartDate = FixDateToCurrentYear(c.StartDate),
EndDate = FixDateToCurrentYear(c.EndDate),
Student = s });
The quickest thing I can think of is to perform your fix at the point you display it to the user, e.g. in Razor:
#FixDateToCurrentYear(student.StartDate)
In this way, you're not actually changing the value, you're just changing how it's displayed.
If you need to do more than display, you will need to create a partial buddy class, add a new property and modify its get method:
public partial class Student
{
DateTime StartDateComputed {
get
{
return FixDateToCurrentYear(StartDate);
}
set;
}
}
Now, use StartDateComputed instead of StartDate and the same for EndDate.

Best practice Objects from SQL

I tried searching but couldn't find a proper answer.
I am creating an application that contains a lot of different objects. The data for these objects is saved in an MSSQL database. What is the best way to get data out?
For simplicity I will use two objects here:
ItemObject
UserObject
Both of them has a constructor which will get data from the Database:
public ItemObject(int ID) //same one for UserObject
{
//Code to get the data from the database for this particular item
}
ItemObject has a property called CreatedBy which is a UserObject.
Now the question is what is the best way to create the ItemObject?
I have two possible solutions:
Solution #1:
public ItemObject(int ID)
{
DataTable dt = dal.GetDataTable("SELECT TOP 1 * FROM Items WHERE ID = #ID")
this.CreatedBy = new UserObject((int)dt.rows[0]["UserID"])
}
Solution #2
public ItemObject(int ID)
{
DataTable dt = dal.GetDataTable("SELECT TOP 1 * FROM Items INNER JOIN Users ON Items.CreatedBy = Users.ID WHERE Items.ID = #ID")
this.CreatedBy = new UserObject((int)dt.rows[0]["UserID"], dt.rows[0]["Username"].ToString())
}
public UserObject(int ID, string Username)
{
this.ID = ID;
this.Username = Username;
}
In solution #1 I ask for data twice but in solution #2 I ask for data once. Although solution #1 is much "cleaner" and easier to read.
Edited after Steves correction.
I would go with solution two. From my point of view solution 1 is not acceptable, though it is "cleaner".
And I think there is no best practice for reading to objects. I like much Entity Framework for this purpose.

Sort datagridview by created columns. Entity Framework c# Winforms

I´m having a problem, I retrieve all the Loans I have stored in my database like this:
list_loans = db.Loan.Where(x => x.State.id_state != 6).ToList();
db is the Object context.
Then, I assign that list as the DataSource for my DataGridView.
dgv_Loans.Datasource = list_loans;
With that info, I add some columns. Like for example, installments left to pay. I get that value by counting the result of a query.
The user can order the result using some options. Is easy to order the result from the fields that the entity have (using linq), but I dont know how to order the results using this new columns.
I read some posts here and tried this:
dgv_Loans.Sort(dgv_Loans.Columns["installments_left"], ListSortDirection.Ascending);
By doing this, I´m getting the following exception at runtime:
"DataGridView control must be bound to an IBindingList object to be sorted."
Is there anyway to use linq to orderby created columns in a DataGridViewColumn? Or how can I solve this error?
I know there are related posts, but after reading them, I can´t find a solution to this specific problem. Thats why I showed how I implemented to get some advice..
Rather than binding directly to the list retrieved from database, what I generally do is have a view class and have all the calculated properties in that class
public class LoanView : Loan {
public LoanView(Loan loan){
}
public int InsallmentsLeft { get { return ...; } }
}
and then bind the datasource to a list of this, this keeps sorting working.
Concerning about Sort datagridview by created columns using Entity Framework
I guess you need this Presenting the SortableBindingList<T>
Usage:
loanBindingSource.DataSource = new SortableBindingList<Loan>(list_loans.ToList());
dgv_Loans.Datasource = loanBindingSource;
int ID = Convert.ToInt32(cmbDepartments.SelectedValue);
var EmployeeList = from Employee in db.Employee
where Employee.DepartmentID == ID
select new
{
Employee.FirstName,
Employee.LastName
};
dataGridView1.DataSource = EmployeeList.ToList();
You could directly give the data source to dataGridView1.DataSource but you must write ToList() at the end of your query:
int ID = Convert.ToInt32(cmbDepartmanlar.SelectedValue);
dataGridView1.DataSource = (from Employee in db.Employee
where Employee.DepartmentID == ID
select new
{
Employee.FirstName,
Employee.LastName
}).ToList();

Loading information from a database problem in c#

This is what i am trying to do. I have a database that i am reading from using the code:
OleDbCommand command;
command = new OleDbCommand("SELECT " + Student.ID + " FROM " + newStudent.DataFile, conn);
conn.Open();
dt.Load(command.ExecuteReader());
conn.Close();
I then have the datatable bind to a datagridview and display the contents of the table.Now the problem is, i have more information to add to the datatable dt that is not in the database. For example, i have a field for the student object called Grade that is not found in the datafile but entered in by the user and stored in a property for the student object.
Instead of loading the query result into a datatable, is there a way to load it into a list so i can manually create rows and columns for a datatable in another method and then add the contents of the list(containing id) and the grade information in the student object manually?
If you don't fancy going for a full blown ORM framework such as the one #Bas has suggested...
Take a look at the ToTable method available from on a Datatable's Dataview. You can get the DataView for your Datatable simply using DataTable.DefaultView:
List<Long> myList = dt.DefaultDataView.ToTable(True, "ID").AsEnumerable().ToList()
myList.Add(1234)
//etc
Alternatively, you can load the additional data you want to append into a second datatable, and use the DataTable.Merge Method
EDIT: To account for wanting to add additional columns, you can change the above list suggestion as follows:
// Create a class to hold the information you want to bind,
// you could use anonymous types if preferred
class MyDataRow
{
public long ID { get; set; }
public string AnotherColumn { get; set; }
public string AndAnotherColumn { get; set; }
}
// then later on when creating that list use something along the lines of:
List<MyDataRow> myList = dt.DefaultDataView.ToTable(True, "ID").AsEnumerable().Select(x => new MyDataRow { ID = x.ID }).ToList()
// you now have a list of MyDataRow which you can work with
// for example...
if (myList.Any())
myList.First().AnotherColumn = "foo";
// as an exmaple of using an anoymous type (not my preference, but an option nonetheless)
var anonymousList = dt.DefaultDataView.ToTable(True, "ID").AsEnumerable().Select(x => new { ID = x.ID, whateverYouWantToCallIt = "some other data but this is read only property" }).ToList()
// you can work with the anonymous list in much the same way, it just isn't explicitly declared
// and the properties are Read Only
if (anonymousList.Any())
Console.WriteLine(anonymousList.First().whateverYouWantToCallIt);
You could use Entity Framework to extract an object model from your database. Afterwards you could add the property for grade to your object (due to the fact that these objects are created in partial classes). This provides a (vastly) more structured / easy to use way of adding custom logic and attributes to your data structure.
You can bind your GUI components to entity framework objects in a similar way as you would using conventional ADO.NET.

How do I extract this LinqToSql data into a POCO object?

with my Repository classes, I use LinqToSql to retrieve the data from the repository (eg. Sql Server 2008, in my example). I place the result data into a POCO object. Works great :)
Now, if my POCO object has a child property, (which is another POCO object or an IList), i'm trying to figure out a way to populate that data. I'm just not too sure how to do this.
Here's some sample code i have. Please note the last property I'm setting. It compiles, but it's not 'right'. It's not the POCO object instance .. and i'm not sure how to code that last line.
public IQueryable<GameFile> GetGameFiles(bool includeUserIdAccess)
{
return (from q in Database.Files
select new Core.GameFile
{
CheckedOn = q.CheckedOn.Value,
FileName = q.FileName,
GameFileId = q.FileId,
GameType = (Core.GameType)q.GameTypeId,
IsActive = q.IsActive,
LastFilePosition = q.LastFilePosition.Value,
UniqueName = q.UniqueName,
UpdatedOn = q.UpdatedOn.Value,
// Now any children....
// NOTE: I wish to create a POCO object
// that has an int UserId _and_ a string Name.
UserAccess = includeUserIdAccess ?
q.FileUserAccesses.Select(x => x.UserId).ToList() : null
});
}
Notes:
Database.Files => The File table.
Database.FilesUserAccess => the FilesUserAccess table .. which users have access to the GameFiles / Files table.
Update
I've now got a suggestion to extract the children results into their respective POCO classes, but this is what the Visual Studio Debugger is saying the class is :-
Why is it a System.Data.Linq.SqlClient.Implementation.ObjectMaterializer<..>
.Convert<Core.GameFile> and not a List<Core.GameFile> containing the POCO's?
Any suggestions what that is / what I've done wrong?
Update 2:
this is what i've done to extract the children data into their respective poco's..
// Now any children....
UserIdAccess = includeUserIdAccess ?
(from x in q.FileUserAccesses
select x.UserId).ToList() : null,
LogEntries = includeUserIdAccess ?
(from x in q.LogEntries
select new Core.LogEntry
{
ClientGuid = x.ClientGuid,
ClientIpAndPort = x.ClientIpAndPort,
// ... snip other properties
Violation = x.Violation
}).ToList() : null
I think that all you need to do is to put another Linq query in here:
q.FileUserAccesses.Select(x => x.UserId).ToList()
i.e. You want to select data from the FileUserAccess records - which I'm assuming are Linq to SQL classes, so to do this you can have something like:
(from fua in q.FileUserAccesses
select new PocoType
{
UserID = fua.UserID,
Name = fua.User.UserName // Not sure at this point where the name comes from
}).ToList()
That should get you pointed in the right direction at least.
What is the type of UserIdAccess? How is it not 'right'? Are you getting the 'wrong' data? if so have you checked your database directly to make sure the 'right' data is there?

Categories