Loading information from a database problem in c# - 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.

Related

Create list of dynamic objects with order and columns from another list

i have to implement functionality that export some data of type TEST to excel. The View has a table which is fill with some data of type TEST. To make it more interesting, user could select what columns from this type T want to export.
So i cant make model for every case (maybe i need to use reflection and create classes in runtime?) because this type TEST has about 50 properties (columns).
I figured out that i can create a list of dynamic objects with selected properties (columns) but i can't imagine how i can do that.
I have a method to need to provide worksheet, list of full data model TEST and list of columns names (for excel header):
public static void FillSheetWithData(ExcelWorksheet ws, List<TEST> data, List<string> columns)
{
for... [creating header]
//create dynamic object from TEST only with properties from columns parameter
List<dynamic> mappedData = data.???
//fill excel with data
ws.Cells["A2"].LoadFromCollection(mappedData);
}
For example:
Main model =>
public class TEST
{
public string Name;
public string Title;
public string Client;
public decimal Price;
public string Address;
public string Owner;
public DateTime Date;
...
}
List of columns =>
List<string> columns = new List<string>
{
"Title",
"Price",
"Name",
"Date"
};
Dynamic object should looks like =>
dynamic obj = new {
Title = "data from main model",
Price = "data from main model",
Name = "data from main model",
Date = "data from main model"
}
If you have some idea i will be grateful!
I am assuming you are using EPPlus for your excel manipulation.
Looking at their documentation of the LoadFromCollection method there is overload which accepts MemberInfo[] which is used to populate only specific properties from the provided objects. This means you can reuse your full object with all properties and provide only the member info you need to export.
Something to start with:
var flags = System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.GetProperty;
var memberInfos = typeof(TEST)
.GetMembers(flags)
.Where(x => columns.Contains(x.Name))
.ToArray();
ws.Cells["A2"].LoadFromCollection(data, ..., ..., flags, memberInfos);
If you want to learn more about GetMebers and reflection in general there is good starting page at docs.microsoft

Creating table at runtime

I'm new to databases and I'm not sure how to handle this situation. I have 3 tables connected this way:
Session <- 1:1 -> Document <- 1:1 -> DocumentData
So basically there is always 1 Session that has a Document which has a DocumentData.
I want to be able to add different types and columns of data to DocumentData, so for example I can have DocumentData with 3 columns of type DateTime,Int32,Int32. And then have another table with 5 columns of types Datetime,double,Int32,Int32,Int32. Basically what I'm going for is to have something like this in my code:
using(var unit = new UnitOfWork(new SessionContext()))
{
var data = unit.Sessions.GetCurrent().Document.DocumentData;
var row = data.Column[0].Rows[5]... etc.
}
This is because DocumentData is generated from csv specified by a user, so each DocumentData is made of different columns.
EDIT:
I want to know how to create a table on runtime and assign whatever columns I want to it. So I want to be able to do something like:
var doc = new Document();
session.Document = doc;
doc.Columns.Add(new Column() {Rows = rows});
doc.Columns.Add(new Column() {Rows = rows2});
doc.SaveChanges();
and then have second table with different columns.
EDIT2:
To make it more clear I want to convert this:
public class DocumentData {
public List<DocumentColumn> Columns { get; set; }
}
public class DocumentColumn {
public string ColumnName { get; set; }
public List<object> Rows { get; set; }
}
into ado.net entities so I can save them to database.
You can use a SQL statement to create tables at runtime (via dbcontext). I don't think that its possible to bind such a table to an entity / class at runtime after the database / context is initialized.
But if you don't have to use the different / variable columns as query / selection parameters, simply serialize the document class in a single BLOB column and your done.

Why do the members of my list get overwritten with the last member of said list?

I am trying to write a program that prints out (in a string variable) the following information about an mdb database:
Table Name
Total number of columns of the table
List of columns as follows:
Column Name:
Column Data Type:
To accomplish this I used two custom types (public classes) and of course, lists. Here is the code I have so far (which by the way has been adjusted not in small part thanks to questions and answers gathered here):
Here are the classes I created to define the two new types I am using:
public class ClmnInfo
{
public string strColumnName { get; set; }
public string strColumnType { get; set; }
}
public class TblInfo
{
public string strTableName { get; set; }
public int intColumnsQty { get; set; }
public List<ClmnInfo> ColumnList { get; set; }
}
Here is the code that actually gets the data. Keep in mind that I am using OleDB to connect to the actual data and everything works fine, except for the problem I will describe below.
As a sample, I am currently testing this code with a simple 1 table db, containing 12 columns of type string save for 1 int32 (Long Int in Access).
//Here I declare and Initialize all relevant variables and Lists
TblInfo CurrentTableInfo = new TblInfo();
ClmnInfo CurrentColumnInfo = new ClmnInfo();
List<TblInfo> AllTablesInfo = new List<TblInfo>();
//This loop iterates through each table obtained and imported previously in the program
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
CurrentTableInfo.strTableName = Globals.tblSchemaTable.Rows[i][2].ToString(); //Gets the name of the current table
CurrentTableInfo.intColumnsQty = dt.Columns.Count; //Gets the total number of columns in the current table
CurrentTableInfo.ColumnList = new List<ClmnInfo>(); //Initializes the list which will house all of the columns
//This loop iterates through each column in the current table
foreach (DataColumn dc in dt.Columns)
{
CurrentColumnInfo.ColumnName = dc.ColumnName; // Gets the current column name
CurrentColumnInfo.ColumnType = dc.DataType.Name; // Gets the current column data type
CurrentTableInfo.ColumnList.Add(CurrentColumnInfo); // adds the information just obtained as a member of the columns list contained in CurrentColumnInfo
}
//BAD INSTRUCTION FOLLOWS:
AllTablesInfo.Add(CurrentTableInfo); //This SHOULD add The collection of column_names and column_types in a "master" list containing the table name, the number of columns, and the list of columns
}
I debugged the code and watched all variables. It works great (the table name and column quantity gets registered correctly, as well as the list of column_names, column_types for that table), but when the "bad" instruction gets executed, the contents of AllTablesInfo are not at all what they should be.
The table name is correct, as well as the number of columns, and the columns list even has 12 members as it should have, but each member of the list is the same, namely the LAST column of the database I am examining. Can anyone explain to me why CurrentTableInfo gets overwritten in this manner when it is added to the AllTablesInfo list?
You're creating a single TblInfo object, and then changing the properties on each iteration. Your list contains lots of references to the same object. Just move this line:
TblInfo CurrentTableInfo = new TblInfo();
to the inside of the first loop, and this line:
ClmnInfo CurrentColumnInfo = new ClmnInfo();
inside the nested foreach loop, so that you're creating new instances on each iteration.
Next:
Important
Make sure you understand why it was failing before. Read my article on references if you're not sure how objects and references (and value types) work in C#
Use camelCased names instead of CamelCased ones for local variables
Consider using an object initializer for the ClmnInfo
Change your type names to avoid unnecessary abbreviation (TableInfo, ColumnInfo)
Change your property names to avoid pseudo-Hungarian notation, and make them PascalCased
Consider rewriting the whole thing as a LINQ query (relatively advanced)
The pre-LINQ changes would leave your code looking something like this:
List<TableInfo> tables = new List<TableInfo>();
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
TableInfo table = new TableInfo
{
Name = Globals.tblSchemaTable.Rows[i][2].ToString(),
// Do you really need this? Won't it be the same as Columns.Count?
ColumnCount = dt.Columns.Count,
Columns = new List<ColumnInfo>()
};
foreach (DataColumn dc in dt.Columns)
{
table.Columns.Add(new ColumnInfo {
Name = dc.ColumnName,
Type = dc.DataType.Name
});
}
tables.Add(table);
// I assume you meant to include this?
i++;
}
With LINQ:
List<TableInfo> tables =
dtImportedTables.Tables.Zip(Globals.tblSchemaTable.Rows.AsEnumerable(),
(table, schemaRow) => new TableInfo {
Name = schemaRow[2].ToString(),
// Again, only if you really need it
ColumnCount = table.Columns.Count,
Columns = table.Columns.Select(column => new ColumnInfo {
Name = column.ColumnName,
Type = column.DataType.Name
}).ToList()
}
}).ToList();
You have only created one instance of TblInfo.
It's because you only have a single instance of TblInfo, which you keep updating in your loop and then add another reference to it to the List. Thus your list has many references to the same object in memory.
Move the creation of the CurrentTableInfo instance inside the for loop.

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();

Accepted collection for working with Excel data

I have a bunch of data that I'm pulling into my application which frankly is best represented as an Excel spreadsheet. By this I mean:
There are a lot of columns which need 'summing up'
There is a reasonable amount of data (basically a sheet of numbers)
At the moment this is just raw data in a database, but I also have a spreadsheet which shows this data (along with formulas that I need to replicate in my app).
At the moment I've just got a List<of T> of each row, however I believe there might be a better collection for storing data of this type. I basically need to be able to manipulate these numbers easily.
Any suggestions?
One option would be to use a DataTable which also has a builtin aggregation method.
For example(from MSDN):
// Presumes a DataTable named "Orders" that has a column named "Total."
DataTable table;
table = dataSet.Tables["Orders"];
// Declare an object variable.
object sumObject;
sumObject = table.Compute("Sum(Total)", "EmpID = 5");
Another advantage is that it supports LINQ queries with LINQ-To-DataSet.
If your "excel data" can be represented in models, I'd just use models. For example like so:
public class ExcelModel()
{
public string Id { get; set; }
public double value1 { get; set; }
public int value1 { get; set; }
}
Then you can easily create a List<ExcelModel>, and get the total like so:
List<ExcelModel> model = repository.GetAll(); //just an example
var total = model.sum(x => x.value1);

Categories