Writing to Excel cells while releasing all COM objects - c#

I am following this posts advice and trying to release all of my COM objects, including all Ranges while writing a List of string tuples to an Excel spreadsheet (using his ComObjectManager class).
I have the solution below which works, but looks like is not stacking the Range COM objects introduced by the
cells[j, column] = strings[i].Item1;
(et al) in the ComObjectManager object and therefore not releasing them. I have the commented out solution which would release the objects but I must not be understanding the syntax correctly to set the cells because it will not compile. I have also tried using
var roomNameCell = com.Get<object>(() => cells[j, column]);
for roomNameCell and areaCell, but this results in nothing being written to the cell.
I have researched the MSDN guides but can't figure out how to do this, please help.
internal void InsertStrings(List<Tuple<string, string>> strings, Excel.Range selection, ComObjectManager com)
{
const int singleRowCell = 1;
const int columnsToArea = 2;
int firstRow = selection.Row;
int column = selection.Column;
for (int i = 0, j = firstRow; i < strings.Count; )
{
selection = com.Get<Excel.Range>(() => app.Cells[j, column]);
var mergeArea = com.Get<Excel.Range>(() => selection.MergeArea);
var mergeAreaRows = com.Get<Excel.Range>(() => mergeArea.Rows);
var cells = com.Get<Excel.Range>(() => app.Cells);
if (selection.MergeCells && mergeAreaRows.Count > singleRowCell)
{
//var roomNameCell = com.Get<Excel.Range>(() => cells[j, column]);
//var areaCell = com.Get<Excel.Range>(() => cells[j, column + columnsToArea]);
//doesn't compile - cannont explicity convert from string to Range
//roomNameCell = strings[i].Item1;
//areaCell = strings[i].Item2;
//works but the Range COM objects aren't being disposed?
cells[j, column] = strings[i].Item1;
cells[j, column + columnsToArea] = strings[i].Item2;
++i;
}
j += mergeAreaRows.Count;
}
}

I figured it out, I just needed to set the Values property like this:
internal void InsertStrings(List<Tuple<string, string>> strings, Excel.Range selection, ComObjectManager com)
{
const int singleRowCell = 1;
const int columnsToArea = 2;
int firstRow = selection.Row;
int column = selection.Column;
for (int i = 0, j = firstRow; i < strings.Count; )
{
selection = com.Get<Excel.Range>(() => app.Cells[j, column]);
var mergeArea = com.Get<Excel.Range>(() => selection.MergeArea);
var mergeAreaRows = com.Get<Excel.Range>(() => mergeArea.Rows);
var cells = com.Get<Excel.Range>(() => app.Cells);
if (selection.MergeCells && mergeAreaRows.Count > singleRowCell)
{
var roomNameCell = com.Get<Excel.Range>(() => cells[j, column]);
var areaCell = com.Get<Excel.Range>(() => cells[j, column + columnsToArea]);
roomNameCell.Value = strings[i].Item1;
areaCell.Value = strings[i].Item2;
++i;
}
j += mergeAreaRows.Count;
}
}

Related

Inserting Datagridview rows into database using Entity Framework

If I want to add records to datagridview, it is okay, but I want to add records to the database which rows in datagridview.
There is a method - how am I adding Datagridview rows.
public void SepeteEkle()
{
decimal? tutar;
decimal? toplamtutar = 0;
int miktarsum = 0;
DataRow urun = dt.NewRow();
tutar = Convert.ToDecimal(lblFiyat.Text) * Convert.ToInt32(txtMiktar.Text);
bool itemfound = false;
for (int i = 0; i < dgvSepet.Rows.Count; i++)
{
if (Convert.ToString(dgvSepet.Rows[i].Cells[0].Value) == lblStokID.Text)
{
miktarsum += Convert.ToInt32(dgvSepet.Rows[i].Cells[4].Value) + Convert.ToInt32(txtMiktar.Text);
toplamtutar += Convert.ToDecimal(dgvSepet.Rows[i].Cells[5].Value) + tutar;
dgvSepet.Rows[i].Cells[4].Value = miktarsum;
dgvSepet.Rows[i].Cells[5].Value = toplamtutar;
itemfound = true;
}
}
if (itemfound == false)
{
urun["Id"] = lblStokID.Text;
urun["StokKodu"] = txtStokKodu.Text;
urun["Ürün"] = txtStokAdi.Text;
urun["Fiyat"] = lblFiyat.Text;
urun["Miktar"] = txtMiktar.Text;
urun["Toplam"] = tutar;
dt.Rows.Add(urun);
dgvSepet.DataSource = dt;
}
}
I want to add database this all created rows using Entity Framework.
What I tried:
public void DatabaseSepetAktar()
{
xstHar stokHareketModel = new xstHar();
stokHareketModel.stharCariKod = txtCariKodu.Text;
stokHareketModel.stharTarih = DateTime.Now;
stokHareketModel.stharDovFiyat = Convert.ToDecimal(lblTotal.Text);
for (int i = 0; i < dgvSepet.Rows.Count -1 ; i++)
{
stokHareketModel.masterId = Convert.ToInt32(dgvSepet.Rows[i].Cells[0].Value);
stokHareketModel.stokKodu = dgvSepet.Rows[i].Cells[1].Value.ToString();
stokHareketModel.stharGcMik1 = Convert.ToDecimal(dgvSepet.Rows[i].Cells[4].Value);
using (fastCellDb db = new fastCellDb())
{
db.xstHars.Add(stokHareketModel);
db.SaveChanges();
}
}
}
But I get an error
Validation exception
from Entity Framework.
How can I solve this problem ?
Validation exception raises when you try to convert value with wrong type, that is due to wrong indexing. As you add a row to your DataGridView dt.Rows.Add(urun) using text indexing, change the indexing way from number to text everywhere: Cells[0] → Cells["Id"] etc.
And look to the line: for (int i = 0; i < dgvSepet.Rows.Count -1 ; i++), it seems -1 is excess there.

EPPLUS: Length of a DataValidation list cannot exceed 255 characters

This question is answered on a basic level on another post: here However for my case I am not able to hard code the validation values into the sheet I am pulling them from a database based on the content of the cell and will need to do a separate validation for 4 columns on every row. Is there a way this can be achieved? Thank you in advance.
// Data Validations //
// Product Validation //
for (int i = 2; i < rowCount; i++)
{
var val = ws.DataValidations.AddListValidation(ws.Cells[i, 5].Address);
val.ShowErrorMessage = true;
val.ErrorTitle = "Entry was invalid.";
val.Error = "Please choose options from the drop down only.";
var ticketEntity = ticketQueryable.Where(o => o.TTSTicketNumber == ws.Cells[i, 3].Value.ToString()).Single<CustCurrentTicketEntity>();
var prodIds = prodExtQueryable.Where(p => p.ZoneId == ticketEntity.ZoneId && p.TicketTypeId == ticketEntity.TicketTypeId);
if (ticketEntity != null)
{
var prodIdsList = new List<int>();
foreach (var prodId in prodIds)
{
prodIdsList.Add(prodId.ProductId);
}
var ProductList = ProductCache.Instance.AllProducts.Where(p => prodIdsList.Contains(p.ProductId)).Select(p => new SelectListItem() { Value = p.ProductId.ToString(), Text = p.Name });
foreach (var Result in ProductList)
{
var product = Result.Text;
val.Formula.Values.Add(product);
}
}
}
So yes as Ernie said What I did was add a second sheet "ProductValidations" and set it to Hidden (unhide it to check that it is working). I then Load my data from the DataTable and then add some basic EPPLUS formatting. I then iterate over the Rows and Insert values into the "ProductValidations" sheet for each cell. Next I convert my column number to the correct Excel Column letter name (A, AC, BCE etc) I then create a string to pass back as an Excel formula targeting the correct range of cells in the "ProductValidations" sheet. Also to anyone having an issue downloading the Excel file from the server this guid method works just fine for me.
public ActionResult DownloadExcel(EntityReportModel erModel, string filename)
{
var dataResponse = iEntityViewService.LoadEntityView(new EntityViewInput
{
SecurityContext = SessionCache.Instance.SecurityContext,
EntityViewName = "Ticket",
Parameters = new Dictionary<string, object> {
{"MinTicketDateTime", "04/26/16"}
}
});
var table = dataResponse.DataSet.Tables[0];
filename = "TICKETS-" + DateTime.Now.ToString("yyyy-MM-dd--hh-mm-ss") + ".xlsx";
using (ExcelPackage pack = new ExcelPackage())
{
ExcelWorksheet ws = pack.Workbook.Worksheets.Add(filename);
//Add second sheet to put Validations into
ExcelWorksheet productVal = pack.Workbook.Worksheets.Add("ProductValidations");
// Hide Validation Sheet
productVal.Hidden = OfficeOpenXml.eWorkSheetHidden.Hidden;
// Load the data from the datatable
ws.Cells["A1"].LoadFromDataTable(table, true);
ws.Cells[ws.Dimension.Address].AutoFitColumns();
int columnCount = table.Columns.Count;
int rowCount = table.Rows.Count;
// Format Worksheet//
ws.Row(1).Style.Font.Bold = true;
List<string> deleteColumns = new List<string>() {
"CurrentTicketId",
};
List<string> dateColumns = new List<string>() {
"TicketDateTime",
"Updated",
"InvoiceDate"
};
ExcelRange r;
// Format Dates
for (int i = 1; i <= columnCount; i++)
{
// if cell header value matches a date column
if (dateColumns.Contains(ws.Cells[1, i].Value.ToString()))
{
r = ws.Cells[2, i, rowCount + 1, i];
r.AutoFitColumns();
r.Style.Numberformat.Format = #"mm/dd/yyyy hh:mm";
}
}
// Delete Columns
for (int i = 1; i <= columnCount; i++)
{
// if cell header value matches a delete column
if ((ws.Cells[1, i].Value != null) && deleteColumns.Contains(ws.Cells[1, i].Value.ToString()))
{
ws.DeleteColumn(i);
}
}
int col = 0;
int Prow = 0;
int valRow = 1;
// Data Validations //
// Iterate over the Rows and insert Validations
for (int i = 2; i-2 < rowCount; i++)
{
Prow = 0;
col++;
valRow++;
// Add Validations At this row in column 7 //
var ProdVal = ws.DataValidations.AddListValidation(ws.Cells[valRow, 7].Address);
ProdVal.ShowErrorMessage = true;
ProdVal.ErrorTitle = "Entry was invalid.";
ProdVal.Error = "Please choose options from the drop down only.";
var ticketEntity = ticketQueryable.Where(o => o.TTSTicketNumber == ws.Cells[i, 3].Value.ToString()).Single<CustCurrentTicketEntity>();
// Product Validation //
var prodIds = prodExtQueryable.Where(p => p.ZoneId == ticketEntity.ZoneId && p.TicketTypeId == ticketEntity.TicketTypeId);
if (ticketEntity != null)
{
var prodIdsList = new List<int>();
foreach (var prodId in prodIds)
{
prodIdsList.Add(prodId.ProductId);
}
var ProductList = ProductCache.Instance.AllProducts.Where(p => prodIdsList.Contains(p.ProductId)).Select(p => new SelectListItem() { Value = p.ProductId.ToString(), Text = p.Name });
//For Each Item in the list move the row forward and add that value to the Validation Sheet
foreach (var Result in ProductList)
{
Prow++;
var product = Result.Text;
productVal.Cells[Prow, col].Value = product;
}
// convert column name from a number to the Excel Letters i.e A, AC, BCE//
int dividend = col;
string columnName = String.Empty;
int modulo;
while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
// Pass back to sheeet as an Excel Formula to get around the 255 Char limit for Validations//
string productValidationExcelFormula = "ProductValidations!" + columnName + "1:" + columnName + Prow;
ProdVal.Formula.ExcelFormula = productValidationExcelFormula;
}
}
// Save File //
var fileStream = new MemoryStream(pack.GetAsByteArray());
string handle = Guid.NewGuid().ToString();
fileStream.Position = 0;
TempData[handle] = fileStream.ToArray();
// Note we are returning a filename as well as the handle
return new JsonResult()
{
Data = new { FileGuid = handle, FileName = filename }
};
}
}
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if (TempData[fileGuid] != null)
{
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else
{
//Log err
return new EmptyResult();
}
}

Check if textbox has same value to other textbox

I want to check if there are similar value in my textboxes, I have 10 Textboxes, and on button click I want to validate if there are the same vales
for (int c = 1; c <= 10; c++)
{
TextBox check_subjName = table_textboxes.FindControl("subject_name" + c.ToString()) as TextBox;
for (int b = 1; b <= 10; b++)
{
TextBox check_subjName2 = table_textboxes.FindControl("subject_name" + b.ToString()) as TextBox;
if (c != b)
{
if (check_subjName.Text == check_subjName2.Text)
{
//there are similar values
}
}
}
}
So it is valid if all textboxes have different values. You could use LINQ:
List<string> textList = table_textboxes.Controls.OfType<TextBox>()
.Where(txt => txt.ID.StartsWith("subject_name"))
.Select(txt => txt.Text.Trim())
.ToList();
var distinctTexts = new HashSet<string>(textList);
bool allDifferent = textList.Count == distinctTexts.Count;
here a slightly optimized approach (in this case micro optimization):
var textList = table_textboxes.Controls.OfType<TextBox>()
.Where(txt => txt.ID.StartsWith("subject_name"))
.Select(txt => txt.Text.Trim());
HashSet<string> set = new HashSet<string>();
bool allDifferent = textList.All(set.Add);

c# Show datagridview column's distinct value in combobox item

On runtime I am changing some columns of datagridview into combobox columns. Now how do I get the existing distinct values in the combobox items? I am using entity model as datasource. My code is:
dgvLoadTable.DataSource = null;
var context = new AdminEntities();
var TableName = cboSelectTable.Text.ToString();
var rawData = context.GetType().GetProperty(TableName)
.GetValue(context, null);
var truncatedData = ((IQueryable<object>)rawData).Take(0);
var source = new BindingSource { DataSource = truncatedData };
dgvLoadTable.DataSource = source;
dgvLoadTable.ReadOnly = false;
dgvLoadTable.AllowUserToAddRows = true;
for (int row= 0; row < dgvLoadTable.Rows.Count; row++
{
for (int col = 0; col < dgvLoadTable.Columns.Count; col++)
{
if (col == 2 || col == 4)
{
this.dgvLoadTable[col, row] = new DataGridViewComboBoxCell();
var item = dgvLoadTable.Rows.Cast<DataGridViewRow>()
.Where(r => r.Cells[0].Value != null)
.Select(r => r.Cells[col].Value)
.Distinct();
((DataGridViewComboBoxCell)dgvLoadTable[col,row]).Items
.AddRange(item
.ToArray());
dgvLoadTable[col, row].ValueType = typeof(var);
}
}
}
dgvLoadTable.Refresh();
But this is not working, any suggestion?
dgvLoadTable[col, row].ValueType = typeof(var);
This is wrong. Please either leave it out or make it the type of the data, not its collection! What data types are in columns 2 & 4?
for string or integer you could write
dgvLoadTable[col, row].ValueType = typeof(string);
or
dgvLoadTable[col, row].ValueType = typeof(int);
But: You probably don't need to control the datatype since it was generated with the column and you have onyl change the column type, not the value type.
However I found that the values need to be restored after changing the cells; so the whole piece of code ought to look like this:
for (int row= 0; row < dgvLoadTable.Rows.Count; row++)
{
for (int col = 0; col < dgvLoadTable.Columns.Count; col++)
{
if (col == 2 || col == 4)
{
var temp = dgvLoadTable[col, row].Value;
this.dgvLoadTable[col, row] = new DataGridViewComboBoxCell();
dgvLoadTable[col, row].Value = temp;
var items = dgvLoadTable.Rows.Cast<DataGridViewRow>()
.Where(r => r.Cells[0].Value != null)
.Where(r => r.Cells[col].Value != null)
.Select(r => r.Cells[col].Value)
.OrderBy(r => r).Distinct();
((DataGridViewComboBoxCell) dgvLoadTable[col, row])
.Items.AddRange(items.ToArray());
}
}
}
dgvLoadTable.Refresh();
Other than that our code works fine, provided you have data in item.
It will contain all those distinct values, provided there are data in the column. I have added an optional sort and an additional where clause to make sure no null values creep in..
Please note the order of things: first we store the value, then change the cell, then restore the value and finally we fill the combobox items!

Loop and update object value in a collection using LINQ

I want to iterate through each object in a collection and then update a property on each object with another array collection
My use case is I have some entities which I initially read from my collection into an array, and perform some complex calculation and then update back my original collection.
public void WhatIfCalculation(string Product, string SiteID, System.Linq.IQueryable<DAL.OMS_StockStatus> prodStatus, ref System.Linq.IQueryable<PivotViewModel> activityInfo)
{
var sff = activityInfo.Where(a => a.Activity == "System Forecast" && a.SiteID == SiteID).ToList();
aSFF = new double?[sff.Count()];
for (int i = 0; i < sff.Count(); i++)
{
aSFF[i] = sff.ElementAt(i).Value + 1;
}
var prf = activityInfo.Where(a => a.Activity == "Planned Receipts" && a.SiteID == SiteID).ToList();
aPRF = new double?[prf.Count()];
for (int i = 0; i < prf.Count(); i++)
{
aPRF[i] = prf.ElementAt(i).Value + 2;
}
// Will perform some calculation here. And then update back my collection.
for (int i = 0; i < aSFF.Length; i++)
{
activityInfo.Where(a => a.Activity == "System Forecast" && a.SiteID == SiteID).ToList().ElementAt(i).Value = aSFF[i];
}
for (int i = 0; i < aPRF.Length; i++)
{
activityInfo.Where(a => a.Activity == "Planned Receipts" && a.SiteID == SiteID).ToList().ElementAt(i).Value = aPRF[i];
}
}
But above update is not updating my collection. When I browse back through my activityInfo - its still showing original value.
Try the following:
public void WhatIfCalculation(string Product, string SiteID, List<DAL.OMS_StockStatus> prodStatus, List<PivotViewModel> activityInfo)
{
var sff = activityInfo.Where(a => a.Activity == "System Forecast"); // Do this in the calling function >> .Where(a => a.SiteID == SiteID).ToList();
aSFF = new double?[sff.Count()];
for (var i = 0; i < sff.Count(); i++)
{
aSFF[i] = sff.ElementAt(i).Value + 1;
}
var prf = activityInfo.Where(a => a.Activity == "Planned Receipts");
aPRF = new double?[prf.Count()];
for (var i = 0; i < prf.Count(); i++)
{
aPRF[i] = prf.ElementAt(i).Value + 2;
}
// Will perform some calculation here. And then update back my collection.
for (var i = 0; i < aSFF.Length; i++)
{
sff.ElementAt(i).Value = aSFF[i];
}
for (var i = 0; i < aPRF.Length; i++)
{
prf.ElementAt(i).Value = aPRF[i];
}
}
As you can see I am sending a list instead of an IQueryable to the method. Reason is that the IQueryable is in fact an query statement and not the record itself.
Basically what you are doing is sending a query to the method which you than use to select the items and change them. The actual items are not changed.
Using the below example you are sending a list of the actual items. The items can than be changed. I also removed some double select statements from your method to enhance the speed.
this should also be useful: Differences between IQueryable, List, IEnumerator?

Categories