Creating table at runtime - c#

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.

Related

How to use AutonumberAttribute.getNextNumber()?

When I use the AutonumberAttribute.getNextNumber(), it gives me the next number of the sequence but it also make the next number to change.
IE if I call 2 time in a row:
nextNumber = AutoNumberAttribute.GetNextNumber(ARLetteringPiece.Cache, LetteringPiece, numbering, DateTime.Now);
first time i'll get "0000001"
second time i'll get "0000002"
I want to be able to know what the next number will be without modifying it's next value.
Is there a way to achieve this ?
Thanks a lot
Edit to answer the comments :
I have a custom table, my UI key is generated with Autonumbering, and I need to put this key in the lines of my other tables to "bind" them to my custom table. So I need to know what will be the autogenerated number.
It depends on the relationship between your DACs (tables).
You can solve this by using the PXDBChildIdentity in the fields of all the tables that need to store the new key.
For example, if your DAC's autonumber field is of type integer and is called MyDAC.MyAutonumberField.
You can add the attribute to all fields in your other DACs that need to store the value like this:
[PXDBInt()]
[PXDBChildIdentity(typeof(MyDAC.myAutonumberField))]
public virtual int? MyDACID { get; set; }
If the other DACs are "children" of your custom DAC you should use the PXParent attribute in all the child DACs on the field that references their parent like this:
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(MyDAC.myAutonumberField))]
[PXParent(typeof(Select<MyDAC,
Where<MyDAC.myAutonumberField,
Equal<Current<myAutonumberField>>>>))]
public virtual int? MyParentDacID { get; set; }
I managed to do it in another way : First I save my "header", then I update the lines with the value autogenerated for my header and then I save it again.
public static void createLettering(List<ARRegister> lines)
{
// We build a new LELettering piece
Lettrage graph = CreateInstance<Lettrage>();
LELettering piece = new LELettering();
piece.Status = ListStatus._OPEN;
piece.LetteringDateTime = DateTime.Now;
piece = graph.ARLetteringPiece.Insert(piece);
// We fill the checked lines with the autonumber of the piece
bool lineUpdated = false;
foreach (ARRegister line in lines)
{
if (line.Selected.Value)
{
if (!lineUpdated)
{
piece.BranchID = line.BranchID;
piece.AccountID = line.CustomerID;
piece = graph.ARLetteringPiece.Update(piece);
graph.Actions.PressSave();
}
line.GetExtension<ARRegisterLeExt>().LettrageCD = graph.ARLetteringPiece.Current.LetteringCD;
graph.ARlines.Update(line);
lineUpdated = true;
}
}
// If there are lines in our piece, we save it
// It saves our lettering piece and our modifications on the ARLines
if (lineUpdated)
{
graph.Actions.PressSave();
}
}

What is the optimal way to store data from HTML table?

I am currently reading in an HTML document using CsQuery. This document has several HTML tables and I need to read in the data while preserving the structure. At the moment, I simply have a List of List of List of strings. This is a list of tables containing a list of rows containing a list of cells containing the content as a string.
List<List<List<string>>> page_tables = document_div.Cq().Find("TABLE")
.Select(table => table.Cq().Find("TR")
.Select(tr => tr.Cq().Find("td")
.Select(td => td.InnerHTML).ToList())
.ToList())
.ToList();
Is there a better way to store this data, so I can easily access particular tables, and specific rows and cells? I'm writing several methods that deal with this page_tables object so I need to nail down its formulation first.
Is there a better way to store this data, so I can easily access particular tables, and specific rows and cells?
On most occassions, well-formed HTML fits nicely into an XML structure so you could store it as an XML document. LINQ to XML would make querying very easy
XDocument doc = XDocument.parse("<html>...</html>");
var cellData = doc.Descendant("td").Select(x => x.Value);
Based on the comments I feel obliged to point out that there are a couple of other scenarios where this can fall over such as
When HTML-encoded content like is used
Valid HTML which doesn't require a closing tag e.g. <br> is used
(With that said, these things can be handled by some pre-processing)
To summarise, it's by all means not the most robust approach, however, if you can be sure that the HTML you are parsing fits the bill then it would be a pretty neat solution.
You could go fully OOP and write some model classes:
// Code kept short, minimal ctors
public class Cell
{
public string Content {get;set;}
public Cell() { this.Content = string.Empty; }
}
public class Row
{
public List<Cell> Cells {get;set;}
public Row() { this.Cells = new List<Cell>(); }
}
public class Table
{
public List<Row> Rows {get;set;}
public Table() { this.Rows = new List<Row>(); }
}
And then fill them up, for example like this:
var tables = new List<Table>();
foreach(var table in document_div.Cq().Find("TABLE"))
{
var t = new Table();
foreach(var tr in table.Cq().Find("TR"))
{
var r = new Row();
foreach(var td in tr.Cq().Find("td"))
{
var c = new Cell();
c.Contents = td.InnerHTML;
r.Cells.Add(c);
}
t.Rows.Add(r);
}
tables.Add(t);
}
// Assuming the HTML was correct, now you have a cleanly organized
// class structure representing the tables!
var aTable = tables.First();
var firstRow = aTable.Rows.First();
var firstCell = firstRow.Cells.First();
var firstCellContents = firstCell.Contents;
...
I'd probably choose this approach because I always prefer to know exactly what my data looks like, especially if/when I'm parsing from external/unsafe/unreliable sources.
Is there a better way to store this data, so I can easily access
particular tables, and specific rows and cells?
If you want to easily access table data, then create class which will hold data from table row with nicely named properties for corresponding columns. E.g. if you have users table
<table>
<tr><td>1</td><td>Bob</td></tr>
<tr><td>2</td><td>Joe</td></tr>
</table>
I would create following class to hold row data:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
Second step would be parsing users from HTML. I suggest to use HtmlAgilityPack (available from NuGet) for parsing HTML:
HtmlDocument doc = new HtmlDocument();
doc.Load("index.html");
var users = from r in doc.DocumentNode.SelectNodes("//table/tr")
let cells = r.SelectNodes("td")
select new User
{
Id = Int32.Parse(cells[0].InnerText),
Name = cells[1].InnerText
};
// NOTE: you can check cells count before accessing them by index
Now you have collection of strongly-typed user objects (you can save them to list, to array or to dictionary - it depends on how you are going to use them). E.g.
var usersDictionary = users.ToDictionary(u => u.Id);
// Getting user by id
var user = usersDictionary[2];
// now you can read user.Name
Since your parsing an HTML table. Could you use an ADO.Net DataTable? If the content doesn't have too many row or col spans this may be an option, you wouldn't have to roll your own and it could be easily saved to a database or list of entities or whatever. Plus you get the benefit of strongly typed data types. As long as the HTML tables are consistent I would prefer an approach like this to make interoperability with the rest of the framework seamless and a ton less work.

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.

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

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.

Categories