I am new to Revit API and am working in C#. I want to get the schedule element parameters value using C#. I used the below code to get the view schedule.
var viewSchedule = new FilteredElementCollector(document)
.OfClass(typeof(ViewSchedule))
.FirstOrDefault(e => e.Name == "MyScheduleName") as ViewSchedule;
Schedule Element Data
From the above schedule, I used the below code to get the element data (please refer the above screenshot link) but it taking long time to reflect the output (10 to 15 seconds).
var rowCount = viewSchedule.GetTableData().GetSectionData(SectionType.Body).NumberOfRows;
var colCount = viewSchedule.GetTableData().GetSectionData(SectionType.Body).NumberOfColumns;
for (int i = 0; i < rowCount; i++)
{
for (int j = 0; j < colCount; j++)
{
data += viewSchedule.GetCellText(SectionType.Body, i, j);
}
}
Please let me know is there any alternate approach to get the schedule data using C#.
Thanks in advance.
Maybe you can also use ViewSchedule.Export as demonstrated by The Building Coder discussing The Schedule API and Access to Schedule Data.
Yes, you can easily access Schedule data without exporting.
Firstly, get all the schedules and read the data cell by cell. Secondly, create dictionary and store data in form of key, value pairs. Now you can use the schedule data as you want. I have tried this in Revit 2019.
Here is the implementation.
public void getScheduleData(Document doc)
{
FilteredElementCollector collector = new FilteredElementCollector(doc);
IList<Element> collection = collector.OfClass(typeof(ViewSchedule)).ToElements();
String prompt = "ScheduleData :";
prompt += Environment.NewLine;
foreach (Element e in collection)
{
ViewSchedule viewSchedule = e as ViewSchedule;
TableData table = viewSchedule.GetTableData();
TableSectionData section = table.GetSectionData(SectionType.Body);
int nRows = section.NumberOfRows;
int nColumns = section.NumberOfColumns;
if (nRows > 1)
{
//valueData.Add(viewSchedule.Name);
List<List<string>> scheduleData = new List<List<string>>();
for (int i = 0; i < nRows; i++)
{
List<string> rowData = new List<string>();
for (int j = 0; j < nColumns; j++)
{
rowData.Add(viewSchedule.GetCellText(SectionType.Body, i, j));
}
scheduleData.Add(rowData);
}
List<string> columnData = scheduleData[0];
scheduleData.RemoveAt(0);
DataMapping(columnData, scheduleData);
}
}
}
public static void DataMapping(List<string> keyData, List<List<string>>valueData)
{
List<Dictionary<string, string>> items= new List<Dictionary<string, string>>();
string prompt = "Key/Value";
prompt += Environment.NewLine;
foreach (List<string> list in valueData)
{
for (int key=0, value =0 ; key< keyData.Count && value< list.Count; key++,value++)
{
Dictionary<string, string> newItem = new Dictionary<string, string>();
string k = keyData[key];
string v = list[value];
newItem.Add(k, v);
items.Add(newItem);
}
}
foreach (Dictionary<string, string> item in items)
{
foreach (KeyValuePair<string, string> kvp in item)
{
prompt += "Key: " + kvp.Key + ",Value: " + kvp.Value;
prompt += Environment.NewLine;
}
}
Autodesk.Revit.UI.TaskDialog.Show("Revit", prompt);
}
Related
I have an ASP.NET Core app, with a model, the aim is to allow user to upload an excel file and then save the file to the model/table. I have the below method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload(IFormFile file)
{
string webRootPath = _hostEnvironment.WebRootPath;
var uploads = Path.Combine(webRootPath, "Upload");
var files = HttpContext.Request.Form.Files;
var extension = Path.GetExtension(files[0].FileName);
using (var filesStream = new FileStream(Path.Combine(uploads, file.FileName), FileMode.Create))
{
files[0].CopyTo(filesStream);
}
var list = new List<User>();
using (var stream = new MemoryStream())
{
await file.CopyToAsync(stream);
using (var package = new ExcelPackage(stream))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[0];
var rowcount = worksheet.Dimension.Rows;
for (int row = 2; row <= rowcount; row++)
{
list.Add(new User
{
Name = worksheet.Cells[row, 1]?.Value?.ToString().Trim(),
Address1 = worksheet.Cells[row, 2]?.Value?.ToString().Trim(),
PostCode = worksheet.Cells[row, 3]?.Value?.ToString().Trim(),
Mobile = worksheet.Cells[row, 4]?.Value?.ToString().Trim(),
});
}
}
}
foreach (var user in list)
{
_db.User.AddAsyncy(user);
}
_db.SaveChangesAsyncy();
return View();
}
This code works fine by processing an excel file uploaded by a user but the problem I'm having is that when the file is large say above 3 mb, it takes well over 8 minutes to upload.
Any idea how to speed this up please? Thanks.
There are two things you can do to increase speed.
1)Instead of reading excel file with ExcelWorksheet class go with a library called ExcelDataReader which can read around 600k records under a minute.
sample code
Model
class Person
{
public int id,
public string name
}
//and excel file has both columns in model ,the we can read with below code
using ExcelDataReader;
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
var fileName = "./Person.xlsx";
var timer = new Stopwatch();
timer.Start();
int counter=0;
List<Person> persons = new List<Person>();
using (var stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.Read))
{
using (var reader = ExcelReaderFactory.CreateReader(stream))
{
while (reader.Read()) //Each row of the file
{
var person = new Person
{
id = reader.GetValue(0).ToString(),
name = reader.GetValue(1).ToString()
}
persons.Add(person)
counter++;
}
timer.Stop();
duration = timer.ElapsedMilliseconds / 1000;
//to check performace print duration and persons list
}
}
https://github.com/ExcelDataReader/ExcelDataReader
2)Once you read and store data in a list, you can store that data in DataTable class and insert into database using Oracle.ManagedDataAccess.Client Nuget package instead of EFcore. This method is fast. Please go through below link for doing this with Oracle database.
https://www.c-sharpcorner.com/article/two-ways-to-insert-bulk-data-into-oracle-database-using-c-sharp/
var db_timer = new Stopwatch();
db_timer.Start();
DataTable dt = new DataTable();
dt.Columns.Add("id");
dt.Columns.Add("name");
for (int i = 0; i < counter; i++)
{
DataRow dr = dt.NewRow();
dr["id"] = persons[i].id;
dr["name"] = persons[i].name;
dt.Rows.Add(dr);
}
using (var connection = new OracleConnection(oracleConString))
{
connection.Open();
int[] ids = new int[dt.Rows.Count];
string[] names = new string[dt.Rows.Count];
for (int j = 0; j < dt.Rows.Count; j++)
{
ids[j] = Convert.ToString(dt.Rows[j]["id"]);
names[j] = Convert.ToString(dt.Rows[j]["name"]);
}
OracleParameter id = new OracleParameter();
id.OracleDbType = OracleDbType.Int32;
id.Value = ids;
OracleParameter name = new OracleParameter();
name.OracleDbType = OracleDbType.Varchar2;
name.Value = names;
OracleCommand cmd = connection.CreateCommand();
cmd.CommandText = "INSERT INTO TEST(id,name) VALUES (:1,:2)";
cmd.ArrayBindCount = ids.Length;
cmd.Parameters.Add(id);
cmd.Parameters.Add(name);
cmd.ExecuteNonQuery();
}
just sample code you can user timer to check how much time it is taking to execute.
When I remove Thread pooling or uncomment Console.WriteLine() from code then code works fine, but to improve performance I want to process each DataTable column on separate Task. It throws index out of range exception.
DataTable dt = new DataTable();
private async void btnExcel_Click(object sender, RoutedEventArgs e)
{
dt = new DataTable("worksheet");
dt.Columns.Add("Id");
dt.Columns.Add("MobileNo");
dt.Columns.Add("Name");
dt.Columns.Add("Name1");
dt.Columns.Add("Name2");
dt.Columns.Add("Name3");
dt.Columns.Add("Name4");
for (int i = 0; i < 100; i++)
dt.Rows.Add(i, "99999", "ABC" + i, "n1", "n2", "n3", "n4");
//var tasksInFlight = new Task[dt.Columns.Count];
var tasksInFlight = new List<Task>();
for (int index = 0; index < dt.Columns.Count; index++)
{
tasksInFlight.Add(updateDt(index, "col " + index));
}
//await Task.Factory.ContinueWhenAll(tasksInFlight, cT => { string a = "abc"; });
await Task.WhenAll(tasksInFlight);
}
public async Task updateDt(int colNum, string data)
{
try
{
Task t = Task.Run(() =>
{
for (int i = 0; i < dt.Rows.Count; i++)
{
// Console.WriteLine("Col Num : " + colNum + " i = " + i);
dt.Rows[i][colNum] = data;
}
});
await t;
}
catch (Exception ex)
{
}
}
When you call tasksInFlight.Add(updateDt(index, "col " + index)); the value of index is not read; you merely store a task that will be executed later - when you call Task.WhenAll. It is when the tasks are executed when the value of index is evaluated. Which happens after the loop is over and the value of index is now equal to dt.Columns.Count, which is out of the range of the array.
Read about C# closures here.
To fix it, you can do like this:
for (int index = 0; index < dt.Columns.Count; index++)
{
int tmpIndex = index;
tasksInFlight.Add(updateDt(tmpIndex, "col " + tmpIndex));
}
EDIT: After further investigation it turns out that DataTable is not thread-safe.
In addition to the fix above, DataTable should be accessed in a lock:
lock (dt)
{
dt.Rows[i][colNum] = data;
}
However, unless retrieving the actual data to put in the DataTable CPU-intensive, the lock eliminates all benefits of concurrency in this case.
I would like to generate a HTML table from an excel file. The EPPlus package provides a .net API for manipulating excel file. I would be happy to know whether it is possible to generate a HTML table code from an Excel file using EPPlus? I couldn't find anything on the documentation, but intuition tells me that there should be a way to do it
Thank you!
If you are looking for something built into EPPlus, I havent seen anything that will export directly HTML.
Best thing would be to bring in the Excel file to EPPlus and extract the data to a collection or DataTable. That should be pretty straight forward.
From there, there is plenty of documentation on how to get that to an html table. Quick search turned up this as the first hit:
Datatable to html Table
I wrote some code, you can try this.
static void Main(string[] args)
{
ExcelPackage p = new ExcelPackage(new System.IO.FileInfo("X.XLSX"));
var sheet = p.Workbook.Worksheets["HMTD"];
var noOfCol = sheet.Dimension.End.Column;
var noOfRow = sheet.Dimension.End.Row;
StringBuilder s = new StringBuilder();
s.Append("<table>");
for (int i = 1; i < noOfRow; i++)
{
s.Append("<tr>");
for (int j = 1; j < noOfCol; j++)
{
int colspan = 1;
int rowspan = 1;
if (!sheet.Cells[i, j].Merge || (sheet.Cells[i, j].Merge && isFirstMergeRange(sheet, sheet.Cells[i, j].Address, ref colspan, ref rowspan)))
{
s.Append("<td rowspan='" + rowspan + "' colspan='" + colspan + "'>");
if(sheet.Cells[i,j] != null && sheet.Cells[i,j].Value != null)
s.Append(sheet.Cells[i,j].Value.ToString());
s.Append("</td>");
}
}
s.Append("</tr>");
}
s.Append("</table>");
System.IO.File.WriteAllText("duc.html",s.ToString());
Console.ReadKey();
}
private static bool isFirstMergeRange(ExcelWorksheet sheet, string address, ref int colspan, ref int rowspan)
{
colspan = 1;
rowspan = 1;
foreach (var item in sheet.MergedCells)
{
var s = item.Split(':');
if (s.Length > 0 && s[0].Equals(address)){
ExcelRange range = sheet.Cells[item];
colspan = range.End.Column - range.Start.Column;
rowspan = range.End.Row - range.Start.Row;
if(colspan == 0) colspan = 1;
if(rowspan == 0) rowspan = 1;
return true;
}
}
return false;
}
Based on the answer from Duc Tran Minh, it works fine for me after I modified the code like this:
static void Main(string[] args)
{
ExcelPackage p = new ExcelPackage(new System.IO.FileInfo("X.XLSX"));
var sheet = p.Workbook.Worksheets["HMTD"];
var noOfCol = sheet.Dimension.End.Column;
var noOfRow = sheet.Dimension.End.Row;
StringBuilder s = new StringBuilder();
s.Append("<table>");
for (int i = 1; i <= noOfRow; i++)
{
s.Append("<tr>");
for (int j = 1; j <= noOfCol; j++)
{
int colspan = 1;
int rowspan = 1;
if (!sheet.Cells[i, j].Merge || (sheet.Cells[i, j].Merge && isFirstMergeRange(sheet, sheet.Cells[i, j].Address, ref colspan, ref rowspan)))
{
s.Append("<td rowspan='" + rowspan + "' colspan='" + colspan + "'>");
if(sheet.Cells[i,j] != null && sheet.Cells[i,j].Value != null)
s.Append(sheet.Cells[i,j].Value.ToString());
s.Append("</td>");
}
}
s.Append("</tr>");
}
s.Append("</table>");
System.IO.File.WriteAllText("duc.html",s.ToString());
Console.ReadKey();
}
bool isFirstMergeRange(ExcelWorksheet sheet, string address, ref int colspan, ref int rowspan)
{
colspan = 1;
rowspan = 1;
foreach (var item in sheet.MergedCells)
{
var s = item.Split(':');
if (s.Length > 0 && s[0].Equals(address))
{
ExcelRange range = sheet.Cells[item];
colspan = range.End.Column - range.Start.Column + 1;
rowspan = range.End.Row - range.Start.Row + 1;
return true;
}
}
return false;
}
I am trying to change all the Business Unit references I got after importing a solution to the ones in the Acceptance environment.
QueryExpression ViewQuery = new QueryExpression("savedquery");
String[] viewArrayFields = { "name", "fetchxml" };
ViewQuery.ColumnSet = new ColumnSet(viewArrayFields);
ViewQuery.PageInfo = new PagingInfo();
ViewQuery.PageInfo.Count = 5000;
ViewQuery.PageInfo.PageNumber = 1;
ViewQuery.PageInfo.ReturnTotalRecordCount = true;
EntityCollection retrievedViews = service.RetrieveMultiple(ViewQuery);
//iterate though the values and print the right one for the current user
int oldValues = 0;
int accValuesUpdated = 0;
int prodValuesUpdated = 0;
int total = 0;
foreach (var entity in retrievedViews.Entities)
{
total++;
if (!entity.Contains("fetchxml"))
{ }
else
{
string fetchXML = entity.Attributes["fetchxml"].ToString();
for (int i = 0; i < guidDictionnary.Count; i++)
{
var entry = guidDictionnary.ElementAt(i);
if (fetchXML.Contains(entry.Key.ToString().ToUpperInvariant()))
{
Console.WriteLine(entity.Attributes["name"].ToString());
oldValues++;
if (destinationEnv.Equals("acc"))
{
accValuesUpdated++;
Console.WriteLine();
Console.WriteLine("BEFORE:");
Console.WriteLine();
Console.WriteLine(entity.Attributes["fetchxml"].ToString());
string query = entity.Attributes["fetchxml"].ToString();
query = query.Replace(entry.Key.ToString().ToUpperInvariant(), entry.Value.AccGuid.ToString().ToUpperInvariant());
entity.Attributes["fetchxml"] = query;
Console.WriteLine();
Console.WriteLine("AFTER:");
Console.WriteLine();
Console.WriteLine(entity.Attributes["fetchxml"].ToString());
}
else
{
prodValuesUpdated++;
string query = entity.Attributes["fetchxml"].ToString();
query = query.Replace(entry.Key.ToString().ToUpperInvariant(), entry.Value.ProdGuid.ToString().ToUpperInvariant());
entity.Attributes["fetchxml"] = query;
}
service.Update(entity);
}
}
}
}
Console.WriteLine("{0} values to be updated. {1} shall be mapped to acceptance, {2} to prod. Total = {3} : {4}", oldValues, accValuesUpdated, prodValuesUpdated, total, retrievedViews.Entities.Count);
I see that the new value is corrected, but it does not get saved. I get no error while updating the record and publishing the changes in CRM does not help.
Any hint?
According to your comments, it sounds like the value you're saving the entity as, is the value that you want it to be. I'm guessing your issue is with not publishing your change. If you don't publish it, it'll still give you the old value of the FetchXml I believe.
Try calling this method:
PublishEntity(service, "savedquery");
private void PublishEntity(IOrganizationService service, string logicalName)
{
service.Execute(new PublishXmlRequest()
{
ParameterXml = "<importexportxml>"
+ " <entities>"
+ " <entity>" + logicalName + "</entity>"
+ " </entities>"
+ "</importexportxml>"
});
}
I want to create a String from an ArrayList. Currently, I am only able to return the last value from the ArrayList. My code:
eachstep = new ArrayList<String>();
for (int i = 0; i < parsedsteps.size(); i++) {
eachstep.add(parsedsteps.get(i).replaceAll("<[^>]*>", ""));
}
for (int i = 0; i < eachstep.size(); i++) {
String directions = i + "."+" "+eachstep.get(i)+"\n"+;
}
Gives me:
3. This is step 3.
Instead of:
1. This is step 1.
2. This is step 2.
3. This is step 3.
How do I make my for loop create a String with all the values from the ArrayList?
You'll need to declare your string outside of the loop, and I suggest using StringBuilder as well, it's more efficient for building strings like this.
StringBuilder directions = new StringBuilder();
for( int i = 0; i < eachstep.size(); i++ )
{
directions.append( i + "." + " " + eachstep.get( i ) + "\n" );
}
Then when you want to get the string out of the StringBuilder, just call directions.toString().
try this
eachstep = new ArrayList<String>();
for (int i = 0; i < parsedsteps.size(); i++) {
eachstep.add(parsedsteps.get(i).replaceAll("<[^>]*>", ""));
}
String directions="";
for (int i = 0; i < eachstep.size(); i++) {
directions += i + "."+" "+eachstep.get(i)+"\n"+;
}
If you have large size of string array, you might want to consider using StringBuilder, e.g
StringBuilder builder = new StringBuilder();
for(String str: eachstep ){
builder.append(i).append(".").append(str).append("\n");
}
String direction = builder.toString();
String directions = "";
for (int i = 0; i < eachstep.size(); i++) {
directions += i + "."+" "+eachstep.get(i)+"\n";
}