This project is an ASP.Net Api project with Angular. What I'm trying to do is export data from a database table and into an excel file. So far, I've managed to export all the table data into an excel file, but struggle to select 2 or 3 fields in the table to export.
[HttpGet("download")]
public IActionResult DownloadExcel(string field)
{
string dbFileName = "DbTableName.xlsx";
FileInfo file = new FileInfo(dbFileName);
byte[] fileContents;
var stream = new MemoryStream();
using (ExcelPackage package = new ExcelPackage(file))
{
IList<UserTable> userList = _context.UserTable.ToList();
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("DbTableName");
int totalUserRows = userList.Count();
}
return File(fileContents, fileType, dbFileName);
}
There's no need to write so many if ... else if ... else if ... else if ... to get the related field names.
A nicer way is to
Use a field list (IList<string>)as a parameter.
And then generate a required field list by intersect.
Finally, we could use reflection to retrieve all the related values.
Implementation
public IActionResult DownloadExcel(IList<string> fields)
{
// get the required field list
var userType = typeof(UserTable);
fields = userType.GetProperties().Select(p => p.Name).Intersect(fields).ToList();
if(fields.Count == 0){ return BadRequest(); }
using (ExcelPackage package = new ExcelPackage())
{
IList<UserTable> userList = _context.UserTable.ToList();
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("DbTableName");
// generate header line
for(var i= 0; i< fields.Count; i++ ){
var fieldName = fields[i];
var pi= userType.GetProperty(fieldName);
var displayName = pi.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
worksheet.Cells[1,i+1].Value = string.IsNullOrEmpty(displayName ) ? fieldName : displayName ;
}
// generate row lines
int totalUserRows = userList.Count();
for(var r=0; r< userList.Count(); r++){
var row = userList[r];
for(var c=0 ; c< fields.Count;c++){
var fieldName = fields[c];
var pi = userType.GetProperty(fieldName);
// because the first row is header
worksheet.Cells[r+2, c+1].Value = pi.GetValue(row);
}
}
var stream = new MemoryStream(package.GetAsByteArray());
return new FileStreamResult(stream,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
}
You could configure the display name using the DsiplayNameAttribute:
public class UserTable
{
public int Id{get;set;}
[DisplayName("First Name")]
public string fName { get; set; }
[DisplayName("Last Name")]
public string lName { get; set; }
[DisplayName("Gender")]
public string gender { get; set; }
}
It's possible to add any properties as you like without hard-coding in your DownloadExcel method.
Demo :
passing a field list fields[0]=fName&fields[1]=lName&fields[2]=Non-Exist will generate an excel as below:
[Update]
To export all the fields, we could assume the client will not pass a fields parameter. That means when the fields is null or if the fields.Count==0, we'll export all the fields:
[HttpGet("download")]
public IActionResult DownloadExcel(IList<string> fields)
{
// get the required field list
var userType = typeof(UserTable);
var pis= userType.GetProperties().Select(p => p.Name);
if(fields?.Count >0){
fields = pis.Intersect(fields).ToList();
} else{
fields = pis.ToList();
}
using (ExcelPackage package = new ExcelPackage()){
....
}
}
if you want to use the datatable then we can define which you need to select from the datatable in this way
string[] selectedColumns = new[] { "Column1","Column2"};
DataTable dt= new DataView(fromDataTable).ToTable(false, selectedColumns);
or else if you wanna you list then you can use linq for selection of particular columns
var xyz = from a in prod.Categories
where a.CatName.EndsWith("A")
select new { CatName=a.CatName, CatID=a.CatID, CatQty = a.CatQty};
Related
I have idea to insert my model from excel using mapping from table. Because my excel hv more then 100 column. It will be tired if I code manualy like I do. I hv idea to save to database the name of header excel, the function, and row for my table
First, I read my excel then capture to dataset, then foreach dataset, then for each list mapping, then using if to know is empty or not, then read function using replace, then set model using mapping table
What I do:
[HttpPost]
public ActionResult upload(HttpPostedFileBase uploadFile)
{
private DB_Entities db = new DB_Entities();
dataset ds = new dataset();
ds = GetFileExcel(uploadFile);
foreach (DataRow dr in ds.Tables["Header$"].Rows)
{
tbl_header header = new tbl_header();
string name = dr["Name"].ToString();
if (name != "") {
var resultName = Convert.ToInt32(name);
header.name = resultName;
}
string address = dr["Address"].ToString();
if (address != "") {
var resultAddress = DateTime.ParseExact(address, "dd MMM yyyy", provider);
header.address = resultAddress;
}
string country = dr["Country"].ToString();
if (country != "") {
var resultCountry = db.tbl_Item.Where(a => a.Code == country).Select(a => a.Id).SingleOrDefault();
header.country = ResultCountry;
}
db.tbl_header.Add(header);
db.SaveChanges();
}
}
public partial class tblT_Pengeluaran_Header
{
public int name { get; set; }
public DateTime address { get; set; }
public int country { get; set; }
}
What I want:
[HttpPost]
public ActionResult upload(HttpPostedFileBase uploadFile)
{
private DB_Entities db = new DB_Entities();
dataset ds = new dataset();
ds = GetFileExcel(uploadFile);
var mapping = db.Tbl_Mapping.toList();
foreach (DataRow dr in ds.Tables["Header$"].Rows)
{
tbl_header header = new tbl_header();
//foreach list mapping to doing:
//{
// string excel = ReadExcel
// if (excel != "") {
// string resultFunction = ReadFunction.Replace('[data]',excel);
// var result = Execute(resultFunction);
// header.ReadTable = result;
// }
//}
db.tbl_header.Add(header);
db.SaveChanges();
}
}
table Mapping Example: Tbl_Mapping
table | excel | function
_____________________________________
name | Name | Convert.ToInt32([data]);
address | Address | DateTime.ParseExact([data], "dd MMM yyyy", provider);
country | Country | db.tbl_Item.Where(a => a.Code == [data]).Select(a => a.Id).SingleOrDefault();
.....
.....
How to do this? any idea??? Thank you
I can see two issues in your code.
The country object fetch from the database within the foreach
The DB save within the foreach.
The above both are bad for the performance. Consider the below instead.
First, fetch all countries from the dataset and do single DB lookup and use AddRange instead of add.
For the Dataset to mapping object conversion, use Linq as below.
[HttpPost]
public ActionResult upload(HttpPostedFileBase uploadFile)
{
private DB_Entities db = new DB_Entities();
DataSet ds = GetFileExcel(uploadFile);
string[] countryCodes = ds.Tables["Header$"].AsEnumerable().Select(row => row.Field<string>("country")).ToArray();
var selectedCountries = db.tbl_Item.Where(q => countryCodes.Contains(q)).Select(s => new { Id = s.Id, Code = s.Code }).ToList();
List<tbl_header> headers = ds.Tables["Header$"].AsEnumerable().Select
(
row => new tbl_header
{
name = row.Field<string>("Name") == string.Empty ? 0 : int.Parse(row.Field<string>("Name")),
address = row.Field<string>("Address") == string.Empty ? DateTime.MinValue : DateTime.ParseExact((row.Field<string>("Address")), "dd MMM yyyy", provider),
country = selectedCountries.SingleOrDefault(q=> q.Code == row.Field<string>("Country")).Id
}
).ToList();
db.tbl_header.AddRange(headers);
db.SaveChanges();
}
I have a collection of ienumerable entities to be added to the database but it seems some conversion is needed. Can anyone point me in the right direction?
bool InsertDetails(DataTable detailTable, string fileName)
{
using (SunseapEBTContext context = new SunseapEBTContext())
{
if (InsertMaster(fileName))//if creating master record successful
{
int masterId = GetSPReadingM(m => m.FileName == fileName).SPReadingMasterId; //get MasterID of file uploaded
var details = detailTable.AsEnumerable().Select(row => new LeasingSPReadingDetailEntity()//new entity from datatable
{
//SPReadingId = row.Field<long>("ProductID"),
SPReadingMasterId = masterId,
BillCycleYear = int.Parse(row.Field<int>("Bill Cycle").ToString().Substring(0, 4)),
BillCycleMonth = byte.Parse(row.Field<byte>("Bill Cycle").ToString().Substring(4))
});
foreach(IEnumerable<LeasingSPReadingDetailEntity> detail in details)
{
context.LeasingSPReadingDetailEntities.AddObject(detail);
}
context.SaveChanges();
}
return true;
}
}
In the foreach loop, exception is thrown
CS1503 Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable' to 'SunseapEBT.Domain.BillingModule.LeasingContract.Entity.LeasingSPReadingDetailEntity'
LeasingSPReadingDetailEntity class:
public class LeasingSPReadingDetailEntity
{
public long SPReadingId { get; set; }
public int SPReadingMasterId { get; set; }
public int BillCycleYear { get; set; }
public byte BillCycleMonth { get; set; }
}
More info:
A file which contains details is uploaded and will create a master record in one table. The details in the file are to be added to a separate table with the auto generated masterId from the first table. The issue is not being able to add the details into the database.
Edit:
I have found out why there was an error. The error was because of the contents of the file. Some rows had no values entered and the last row did not follow the format for the rest of the rows as it shows the total number of rows. Thanks all for the help!
#slawekwin comment is the answer. But I think there is a better solution, because it seems like your code is iterating 2x: 1st to generate new Enumerable (wasting memory), 2nd to add the object to context.
Might as well add the object directly when you iterate each rows.
foreach(var row in detailTable.AsEnumerable())
{
context.LeasingSPReadingDetailEntities.AddObject(
new LeasingSPReadingDetailEntity()//new entity from datatable
{
//SPReadingId = row.Field<long>("ProductID"),
SPReadingMasterId = masterId,
BillCycleYear = int.Parse(row.Field<string>("Bill Cycle").Substring(0, 4)),
BillCycleMonth = byte.Parse(row.Field<string>("Bill Cycle").Substring(4)),
AccountNumber = row.Field<string>("Account No."),
PeriodStart = row.Field<DateTime>("Period Start"),
PeriodEnd = row.Field<DateTime>("Period End"),
TownCouncil = row.Field<string>("Customer Name"),
Service = row.Field<string>("Service Type"),
Adjustment = row.Field<string>("Adjustment"),
Block = row.Field<string>("Blk"),
AddressLine1 = row.Field<string>("Adress Line 1"),
AddressLine2 = row.Field<string>("Adress Line 2"),
AddressLine3 = row.Field<string>("Postal Code"),
Usage = row.Field<decimal>("Usage"),
Rate = row.Field<decimal>("Rate"),
Amount = row.Field<decimal>("Amount")
}
);
}
--- EDIT ---
I am not sure, but I can guess that "Bill Cycle" field is neither int nor byte. Therefore, you should have retrieved it as string, then parse it to your new object. This is the part the I changed:
BillCycleYear = int.Parse(row.Field<string>("Bill Cycle").Substring(0, 4)),
BillCycleMonth = byte.Parse(row.Field<string>("Bill Cycle").Substring(4)),
You can change the problematic foreach loop to either of the below ones. I would prefer the first one as it is less verbose.
foreach(var detail in details)
{
context.LeasingSPReadingDetailEntities.AddObject(detail);
}
OR
foreach(LeasingSPReadingDetailEntity detail in details)
{
context.LeasingSPReadingDetailEntities.AddObject(detail);
}
If you look at the syntax of foreach loop, the very first construct variableType is type of the element being stored in the collection (LeasingSPReadingDetailEntity in your case) and NOT the type of the collection. You did the later which is why you are getting the invalid cast error.
foreach(variableType currentElementBeingIterated in collection){
//code block to operate on currentElement
}
Change it to List then add the item.
var details = detailTable.AsEnumerable().Select(row => new LeasingSPReadingDetailEntity()//new entity from datatable
{
SPReadingId = row.Field<long>("ProductID"),
SPReadingMasterId = masterId,
BillCycleYear = int.Parse(row.Field<int>("Bill Cycle").ToString().Substring(0, 4)),
BillCycleMonth = byte.Parse(row.Field<byte>("Bill Cycle").ToString().Substring(4)),
}).ToList();
foreach(var detail in details)
{
context.LeasingSPReadingDetailEntities.Add(detail);
}
or better still:
var details = detailTable.AsEnumerable().Select(row => new LeasingSPReadingDetailEntity()//new entity from datatable
{
SPReadingId = row.Field<long>("ProductID"),
SPReadingMasterId = masterId,
BillCycleYear = int.Parse(row.Field<int>("Bill Cycle").ToString().Substring(0, 4)),
BillCycleMonth = byte.Parse(row.Field<byte>("Bill Cycle").ToString().Substring(4)),
}).ToList();
context.LeasingSPReadingDetailEntities.AddRange(details);
I have a input of type file that is sending a HttpPostedFileBase (Excel.xlxs) to my controller. This file do I need to save temporary to use in another action. This HttpPostedFileBase am I converting to DataSet using Excel Data Reader. This works fine when I work directly with the file, but not when I have stored it in TempData.
First action:
[HttpPost]
public ActionResult CompareExcel(ExcelModel model, string submitValue, string compareOption)
{
TempData["firstFile"] = model.Files[0];
TempData["secondFile"] = model.Files[1];
if (submitValue == "Compare calculations")
{
var firstFile = TempData["firstFile"];
var secondFile = TempData["secondFile"];
if (firstFile == null || secondFile == null)
return View("CompareExcel");
else
return CompareColumn(compareOption);
}
//Assume submitValue is "Compare calculations"
}
Second action:
[HttpPost]
public ActionResult CompareColumn(string compareOption)
{
List<Calculation> cList = new List<Calculation>();
Calculation calc = new Calculation();
BigModel m = new BigModel();
HttpPostedFileBase firstFile = (HttpPostedFileBase)TempData["firstFile"];
HttpPostedFileBase secondFile = (HttpPostedFileBase)TempData["secondFile"];
DataSet firstSet = ExcelToDataSet.ConvertToDataSet(firstFile);
DataSet secondSet = ExcelToDataSet.ConvertToDataSet(secondFile);
for (var i = 0; i < firstSet.Tables[0].Rows.Count; i++) //Get Object reference not set to an instance of an object
{
DataRow data = firstSet.Tables[0].Rows[i];
calc.PresentValue = Convert.ToDecimal(data.Table.Rows[i]["Capital balance"]);
}
return View();
}
My model:
public partial class GetExcel
{
public List<HttpPostedFileBase> Files { get; set; }
public GetExcel()
{
Files = new List<HttpPostedFileBase>();
}
}
My method to convert my HttpPostedFileBase (Excel.xlxs) to DataSet. The method is working when sending the file directly but not when using TempData:
public static DataSet ConvertToDataSet(HttpPostedFileBase excelFile)
{
DataSet result = null;
if (excelFile != null && excelFile.ContentLength > 0)
{
// .xlsx
IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(excelFile.InputStream);
// .xls
//IExcelDataReader reader = ExcelReaderFactory.CreateBinaryReader(file.InputStream);
reader.IsFirstRowAsColumnNames = true; // if your first row contains column names
result = reader.AsDataSet();
reader.Close();
}
return result;
}
When I debug and look at firstFile and secondFile I can see there is something there. I can see the filename of the HttpPostedFileBase but when I use ConvertToDataSet it fails in the first if. So obviously it's something wrong with the file saved in TempData. Is it possible to make this work with TempData or can someone suggest some easy way to go around this? Should it work and I'm doing something wrong? The whole thing is that the HttpPostedFileBase somehow needs to be saved temporary (in a easy way).
private void getRRvalue(string DELRRNO)
{
try {
DBSFCDataContext SFC = new DBSFCDataContext();
var query = (from i in SFC.POP10500s where i.POPRCTNM == DELRRNO select new { PONO = i.PONUMBER, DATEREC = i.DATERECD, VENDID = i.VENDORID, ITEMCODE = i.ITEMNMBR, QTYBAGS = i.QTYBAGS, QTYSHIP = i.QTYSHPPD, DEPT = i.TRXLOCTN });
foreach (var r in query)
{
string[] row = {
DELRRNO,
r.PONO,
Convert.ToDateTime(r.DATEREC).ToString(),
r.VENDID,
r.ITEMCODE,
r.QTYBAGS.ToString(),
r.QTYSHIP.ToString(),
r.DEPT
};
//glbVariables.getRRNO = ;
//glbVariables.getPONO = ;
//glbVariables.getRRdateRec = ;
//glbVariables.getVendID = ;
//glbVariables.getItemNO = ;
//glbVariables.getQtyBags = ;
//glbVariables.getQtyShipped = ;
//glbVariables.getLocnCode = ;
}
SFC.Connection.Close();
}
catch (Exception ex)
{ MessageBox.Show(ex.Message.ToString()); }
}
I'm new to C#.NET and I was just thinking if I could use a dynamic array like for this code above do I need to declare a global array like this --> "public static string[] row;" so I can use this array string in another form by calling it with the data that I have stored from this function, could this happen in c#?
I need help here please anyone that is good at arrays in c#...
To get you desired results, you will have to do little more work. I explain your solution using List.
First create a class for your one query result:
public class OneRowData
{
public string DELRRNO;
public string PONO;
public string DATEREC;
public string VENDID;
public string ITEMCODE;
public string QTYBAGS;
public string QTYSHIP;
public string DEPT;
}
In your given code, create a List of OneRowData type and make it public static to access it from outside the class as well:
public static List<OneRowData> QueryResults = new List<OneRowData>();
Now in your foreach loop, create an object of OneRowData, assing values to it and add it to the List:
foreach (var r in query)
{
OneRowData Obj = new OneRowData();
//assing values to them
Obj.DATEREC = Convert.ToDateTime(r.DATEREC).ToString();
Obj.DELRRNO = DELRRNO;
Obj.DEPT = r.DEPT;
Obj.ITEMCODE = r.ITEMCODE;
Obj.PONO = r.PONO;
Obj.QTYBAGS = r.QTYBAGS.ToString();
Obj.QTYSHIP = r.QTYSHIP.ToString();
Obj.VENDID = r.VENDID;
//then add the object to your list
QueryResults.Add(Obj);
}
Now you can simply call your List any where and fetch your data like this:
foreach (OneRowData Row in QueryResults)
{
//Row.DATEREC
//Row.DELRRNO
//call them like this and use as you need
}
Below is my code:
string Query = "SELECT EmpName, EmpCode FROM EmpDetail WHERE ZCode=101 ORDER BY EmpName";
var db = new PetaPoco.Database("conCustomer");
var result = db.Fetch<string>(query);
TextBox1.Text = result.ToString(); //This is giving first column
TextBox2.Text = .... // pick second column
I want to know how to pick the second column from the result.
I believe the issue you're having is that you're not using a class as part of the fetch. Try creating a simple class and performing the fetch with that:
public class EmpDetail
{
public string EmpName { get; set; }
public string EmpCode { get; set; }
}
var result = db.Fetch<EmpDetail>(Query);
Then try iterating over that list of EmpDetail:
foreach (var detail in result)
{
var x = detail.EmpName; // First column
var y = detail.EmpCode; // Second column
}
EDIT: According to this (h/t Robert Koritnik), it does look like it will support a dynamic query like so (untested):
foreach (var detail in db.Fetch<dynamic>(query))
{
var x = detail.EmpName; // First column
var y = detail.EmpCode; // Second column
}