Maybe you can remove the inner loop, but I don't think you should as it would make your code much harder to read and wouldn't be faster. This implementation is fine in my opinion.
Think about it, Excel works with rows and columns and actually your code reflects this pretty well, it is good!
Though, there are 3 things I'd like to point out about your snippet,
object[,] arr = new object[52784, 21];
I'll assume you know why you use
21, but I don't. What I mean is, when other developers are looking at your code, we can't know if these numbers are random or not. This problem is a code smell called Magic Numbers. To solve it, we must introduce a variable that'd make everything explanatory. Since your array must be the size of your
DataTable, why not use the properties of the
DataTable to initialize your array to the good values :
object[,] arr = new object[data.Data.Tables.Rows.Count, data.Data.Tables.Columns.Count];
arr aren't the best variable names, I understand that when checking the code it is easy to see, but using
excelFormatted (this one isn't that good, ok) would be better!
Finally, since you access your
DataTable a lot, you should store it in a variable, it will save you the cost of using the
DataTable's indexer every time.
var table = data.Data.Tables;
object[,] excelFormatted = new object[table.Rows.Count, table.Columns.Count];
for (int rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++)
for (int columnIndex = 0; columnIndex < table.Column.Count; columnIndex++)
arr[rowIndex, columnIndex] = table.Rows[rowIndex][columnIndex];