Introduction
Recently, I was given the task of designing a new database application. It would be an update to a previously existing application, only it would be done with newer technologies and having a newer look and feel. The previous database was never given to me; instead I received meta data from which I created a blank database. After numerous requests for the actual database, I was instead given a series of XLS files dumped from that original database, and all named appropriately. For example, "Invoice.xls" stored records for the "Invoice
" table, etc. The column headers in the spreadsheets all matched the column names in my SQL database as well.
I decided to write a generic method that could import all of these files for me. Other options would be to do something such as creating insert
statements from the spreadsheets, however that proved very difficult. Some cases of a blank cell should be interpreted as null
, while others should be an empty string
. In the end, this turned out to be the easiest way to go.
Using the Code
Consider the sample class given below:
using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq.Mapping;
namespace MyClasses {
[Table(Name = "Invoice")]
public class InvoiceClass {
[Column(CanBeNull = false, DbType = "int",
Name = "InvoiceId", IsPrimaryKey = true)]
[Display(Name = "Invoice ID")]
public string Id { get; set; }
}
}
Notice the attributes added. The Table
attribute handles linking a specific class to a specific table. The Column
attribute handles all column
properties for a class property. Because I was using Linq to SQL in my application, this is how my classes were setup.
Please note: This is not a Linq to SQL article as those articles already exist. This is just an example of how to import spreadsheet data.
My application was simply a console app with all the code in the main
method. I wasn't concerned with anything fancy or a GUI. In theory, this would run only one time.
The chain of operations is to:
- Loop all defined class types (e.g.
InvoiceClass
) - Find the correct table name for the SQL database (e.g. "
Invoice
") - Check if records exist and clear if needed
- Find the associated XLS filel (e.g. "Invoice.xls")
- Load rows for the XLS file and loop them
- Create a new instance of whatever class I'm working with
- Loop all of the XLS row's columns and set the appropriate property (e.g.
invoice.Id = xlsColumn["InvoiceID"]
) - Adjust all properties as needed for any possible
null
values, defaults, string
truncation, etc. - Submit each resulting object instance to the Linq to SQL engine
The code is below. I have it commented fairly well, and rather than switch back and forth between code and article comments, I'm just letting the comments speak for themselves!
static void Main(string[] args) {
string xlsPath = @"C:\ThePathTo\TheXLSFiles";
string sqlDatabase = "Data Source=server;Initial Catalog=db;
User ID=me;Password=myPassword;Encrypt=True;TrustServerCertificate=True;";
string xlsFile = "Provider=Microsoft.ACE.OLEDB.12.0;
Data Source={0};Extended Properties=\"Excel 8.0;HDR=YES\";";
string classNamespace = "MyClasses";
var classList = Assembly.GetExecutingAssembly().GetTypes().Where(t =>
String.Equals(t.Namespace, classNamespace, StringComparison.Ordinal)).ToList();
var dir = new System.IO.DirectoryInfo(xlsPath);
var fileList = dir.GetFiles("*.*", System.IO.SearchOption.AllDirectories);
var fileQuery = from file in fileList
where file.Extension == ".xls"
orderby file.Name
select file.FullName;
var filesToProcess = fileQuery.ToList();
using (var dbContext = new DataContext(sqlDatabase)) {
var classQuery = (from tmpClass in classList
where !tmpClass.Name.StartsWith("I") select tmpClass);
foreach (var tmpClass in classQuery) {
var sqlTable = dbContext.GetTable(tmpClass);
var countQuery = (from object o in sqlTable select o);
if (!countQuery.Any()) {
var attributes = tmpClass.GetCustomAttributes(typeof(TableAttribute), true);
if (attributes.Any()) {
var tableName = ((TableAttribute)attributes[0]).Name;
var file = filesToProcess.FirstOrDefault(o =>
Path.GetFileNameWithoutExtension(o).Equals(tableName,
StringComparison.CurrentCultureIgnoreCase));
if (file == null) {
continue;
}
using (var dataAdapter = new OleDbDataAdapter
("SELECT * FROM [Sheet 1$]", string.Format(xlsFile, file))) {
using (var myDataSet = new DataSet()) {
dataAdapter.Fill(myDataSet, tableName);
using (var dataTable = myDataSet.Tables[tableName]) {
foreach (DataRow row in dataTable.Rows) {
var instance = Activator.CreateInstance(tmpClass);
var properties = tmpClass.GetProperties();
var propertyQuery = (from property in properties
where property.CanWrite select property);
foreach (PropertyInfo property in propertyQuery) {
var dbProperty = property.GetCustomAttribute
(typeof(ColumnAttribute), false) as ColumnAttribute;
if (dataTable.Columns.Contains(dbProperty.Name)) {
var val = row[dbProperty.Name];
if (val == DBNull.Value) {
val = null;
}
if (val == null) {
if ((property.PropertyType == typeof(DateTime)) ||
(property.PropertyType == typeof(DateTime?))) {
DateTime? nullableDate = null;
property.SetValue(instance, nullableDate);
}
else if (!dbProperty.CanBeNull) {
if (property.PropertyType == typeof(string)) {
property.SetValue(instance, string.Empty);
}
else {
var tmpVal = Activator.CreateInstance(property.PropertyType).GetType();
property.SetValue(instance, Activator.CreateInstance(tmpVal));
}
}
else {
property.SetValue(instance, null);
}
}
else if ((dbProperty.DbType.StartsWith("nvarchar") &&
(!string.IsNullOrEmpty(val.ToString())))) {
var sLength = dbProperty.DbType.Substring(("nvarchar(").Length);
sLength = sLength.Substring(0, sLength.Length - 1);
var iLength = Int32.Parse(sLength);
var newVal = val.ToString();
newVal = newVal.Substring(0, Math.Min(iLength, newVal.Length));
if ((property.PropertyType == typeof(char)) &&
(newVal.Length == 1)) {
property.SetValue(instance, newVal[0]);
}
else {
property.SetValue(instance, newVal);
}
}
else if (val.GetType() != property.PropertyType) {
if ((val.GetType() == typeof(DateTime)) ||
(val.GetType() == typeof(DateTime?))) {
DateTime? nullableDate = (DateTime)val;
property.SetValue(instance, nullableDate);
}
else if ((property.PropertyType == typeof(DateTime)) ||
(property.PropertyType == typeof(DateTime?))) {
var newVal = val.ToString();
var nullableDate = (string.IsNullOrWhiteSpace
(newVal) ? (DateTime?)null : DateTime.Parse(newVal));
property.SetValue(instance, nullableDate);
}
else {
var pType = property.PropertyType;
if ((property.PropertyType.IsGenericType) &&
(property.PropertyType.GetGenericTypeDefinition().
Equals(typeof(Nullable<>)))) {
pType = Nullable.GetUnderlyingType(property.PropertyType);
}
var newProp = Convert.ChangeType(val, pType);
property.SetValue(instance, newProp);
}
}
else {
property.SetValue(instance, val);
}
}
}
sqlTable.InsertOnSubmit(instance);
}
dbContext.SubmitChanges();
}
}
}
}
}
}
}
}
The only thing that needs to be done with the above code is to set the initial values of the string
s at the top. A few assumptions are made such as XLS sheets being named "Sheet 1" etc.
Points of Interest
Junk I learned: Reflection is an amazingly powerful piece of C#. String
s have no default constructor because they are immutable. Linq-to-SQL is pretty awesome. I hope at least one person finds this helpful as I was unable to locate something like this when I began this task.
History
- Initial version: 12/8/2013