动态DataTable到实体*没有提前*硬编码……?

题:

问:为什么不能/我如何动态使用DataTable中的数据在一个函数中使用reflection(?)来创建LINQ / EF,以根据可用的内容创建实体的实例?

我已经看过很多很多Qs和As关于硬编码 ,但没有动态地做这个。 我正在寻找一种替代方法来维护我所有实体的硬编码代码……

背景/validation我的方法:

  1. 我处在一个我有很多实体的情况。
  2. 我正在使用EF6 Code-First,MVC5和ASP.NET,管理员会上传电子表格。 每个工作簿都转换为DataSet,并将每个选项卡(工作表)转换为DataTable。
  3. 所有这些我做得很好 ,DataTable名称作为实体类的名称。
  4. 为用户提供了一个模板电子表格,该电子表格已经包含他们需要作为顶行填写的列的名称。 在这个问题之后我会手动解决这个问题。 [用户应该能够选择一个类,并且应该为它们创建一个工作簿,以便使用相应的列标题动态下载,但这在列表中]
  5. 我的问题首先是填入的工作表,该工作表已被引入每个类的DataTable,由它的选项卡名称/数据表名称决定。

给定: – 我知道类的名称(来自dt.Name),我已经validation了它是一个有效的实体。 – 我知道我将用每个DataRow更新的列的名称,因为它们在dt的列名中。

问题重述,现在有了上下文:为什么不能/我如何动态使用DataTable中的数据在一个函数中使用reflection(?)根据可用的东西创建实体的实例来制作LINQ / EF?

[Pseudo-Code] public void DataTableToEntities(DataTable dt){ string entityClassName = dt.Name; ArrayList/List colNames = dt.Columns. names, ... .... using (MyContext ctx = new MyContext()){ while (row dtRow in dtRows){ // magic follows: DeterminedEntity de = new DeterminedEntity(); // populate de with dtRow ctx.[DeterminedEntity].Add(de); // magic ends... } ctx.SaveChanges; } } 

理由:

我可以随意添加,编辑和删除实体,而无需像上面那样在导入场景中对这些微妙的更改进行硬编码。

下一步:使用WorkBook – > DataSetWorkSheets – > DataTables将动态工作簿和工作表创建作为上述过程的上传模板。

进展: 2016年11月6日

这个问题的关键似乎是如何从名称实例化DBContext类的实体实例。

我的Scratch控制台应用程序是一个单独的项目(相同的解决方案),所有模型和东西都复制过来……

到目前为止,我有:

 static void Main(string[] args) { ScratchProgram s = new ScratchConsole1.ScratchProgram(); Console.WriteLine("starting"); string fileLocation = @"C:\Users\...\SomeDbSetClass_test.xlsx"; string tableName = "SomeDbSetClass"; //---------------------------------------------------------- // FreeSpire.xls Workbook wb = new Workbook(); wb.LoadFromFile(fileLocation); Console.WriteLine("wb.WorkSheets count: " + wb.Worksheets.Count); //---------------------------------------------------------- Worksheet ws = wb.Worksheets[tableName]; Console.WriteLine("ws.Rows count: " + ws.Rows.Count()); //---------------------------------------------------------- DataTable dt = new DataTable(tableName); dt = ws.ExportDataTable(); Console.WriteLine("dt.Rows.Count: " + dt.Rows.Count); Console.WriteLine("dt.Name: " + dt.TableName); //========================================================== string pathToAssembly = @"C:\...\ScratchConsole1.dll"; var aClass = s.CreateInstanceOf(dt.TableName, pathToAssembly); // Now I have a valid class of the program-NOT the ef ctx... Console.WriteLine("aClass.FullName: " + aClass.GetType().FullName); MethodInfo[] aMethods = aClass.GetType().GetMethods(); // now I have an array of all get/sets... "get_" and "set_" // let's iterate through all the methods, printing them: for (int i2 = 0;i2 < aMethods.Count() ; i2++) { MethodInfo mi = aMethods[i2]; Console.WriteLine("method: " + mi.Name); } // the above isn't really useful, as I already have the property names in the dt header row using (DatabaseContext ctx = new DatabaseContext()) { //========================================================== // i is used as column index below, as dr.ItemArray is an array... int i = 0; // each dr should create a new instance of the ctx's ef class... foreach (DataRow dr in dt.Rows) { ??? ...Create new instance in ctx, using as class name: dt.TableName... ctxClass <--- assuming made using aClass ??? // now we can load each class property with the value from the dr: foreach (string drItem in dr.ItemArray) { Console.WriteLine(String.Format("================= col =================")); string entAttrName = dt.Columns[i].ToString(); // this is fine as string, but... string entAttrValue = dr[i].ToString(); // this is NOT <--- see Note1 below Console.WriteLine("[" + i + "] Item: " + entAttrName.ToString()); Console.WriteLine("[" + i + "] Value: " + entAttrValue.ToString()); ??? ctxClass.[entAttrName.ToString()] = entAttrValue.ToString(); <--- see Note1 below ??? //============================================================================== // Note1: // the above is far less than ideal, as it has every column/attribute/property type // being used as a String... Obviously, we want to leave doubles as doubles, ints as // ints, etc. // This becomes a MUCH larger problem when we introduce entities as properties... // like a State entity with many City entities as a List as a property... //============================================================================== i++; } ctx.[ef class].Add(ctxClass); } ctx.SaveChanges(); } Console.WriteLine("end."); Console.ReadKey(); } public object CreateInstanceOf(string ClassName, string AssemblyFullPath) { var assemblyFullPath = Assembly.LoadFrom(@"C:\...\ScratchConsole1.exe"); var assembly = Assembly.GetExecutingAssembly(); var type = assembly.GetTypes().First(t => t.Name == ClassName); return Activator.CreateInstance(type); } 

我希望这会让事情变得更容易理解。:)

更新和解决方案! SO用户Gert Arnold在这篇文章中更改了一行,解决了这个问题:

 // All details from the property to update/set: //PropertyInfo theProp = thisTypeInfo.GetDeclaredProperty(entAttrName); PropertyInfo theProp = thisTypeInfo.GetProperty(entAttrName); 

格特的解释:

thisTypeInfo.GetDeclaredProperty获取由类型本身声明的属性,而不是其超类型。 使用thisTypeInfo.GetProperty。 – 格特阿诺德

我无法相信我能够以一种更聪明的人能够解决的方式解决我几乎无法理解的问题!

================================================== =========================

这里的解决方案是:

此程序采用电子表格文件(.xlsx),读取EF DbSet类的选项卡名称,以及属性名称和值的列名称和值,并将它们作为单独的实体保存在EF安装的数据存储中(适用于两者SQLite和SQL Server 2014)

很酷的部分是,在EF类名,属性等应用程序中不需要硬编码。这意味着您可以创建一个Excel 模板 ,让某人填写它(只是您在模板中为它们提供的列),以及该文件可以毫不费力地导入。

您需要为自己重新创建的所有内容都在这篇文章中。

我希望这是我自己编写的应用程序,并且有点惊讶,没有人想要做同样的事情。 有了100多个EF类和基类,答案(甚至在这里)是笨拙的,咬紧牙关,并硬编码一切。

我将尽可能完整地发布工作代码 。 对于任何试图使其软件更易于使用的人来说,这段代码有望向前迈进一大步。 为了完全隔离问题/ Poc / Answer,我使用了:

VS 2015,SQLite最新,SQLite.CodeFirst,System.Data.SQLite [.Linq和.EF6](< - 可能不是全部必要,但我第一次与SQLite斗争),NLog,NLog.Config,Spire.XLS, EF6 ......应该是它。 一切都在NuGet完成。

笔记:
– 我在普通系统中使用SQL 2014 Ent,但发现SQLite具有相同的性能。 实际上,下面的示例和时间是在SQLite数据库文件上!
– 下面的时间是在我的开发笔记本电脑上,它在VirtBox VM上运行Win10,上面有VS2015。 同一台笔记本电脑上的另一台虚拟机正在运行SQL 2014 Enterprise,但对于此测试示例,我在同一台Win10虚拟机上使用了SQLite。
– 我的MyDbContext.csConfiguration.cs文件是裸骨,没有播种等。

 public DbSet Persons { get; set; } public DbSet Children { get; set; } 

– 有一个简单的test.xlsx文件,其中包含要插入DB的值。

这是它的.csv:

 sChildFoo,iChildBar,dChildBaz,PersonAge,PersonWeight,PersonName Norwich,29884,1.2,34,123,Fred Flintstone Waterford,34990,3.4,56,210,Barney Rubble 

重要说明:工作表中的选项卡标记为Child -exactly与您希望将值加载为的类相同。 有2个数据/实体类,Person()和Child()名为PersonChild.cs

 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DynamicEFLoading { public abstract class Person { [Key] public int PersonId { get; set; } public int PersonAge { get; set; } public int PersonWeight { get; set; } public string PersonName { get; set; } } class Child : Person { [Key] [Column(Order = 0)] public int Id { get; set; } public string sChildFoo { get; set; } public int iChildBar { get; set; } public double dChildBaz { get; set; } } } 

正如您所看到的,所有Child()实例都从Person()inheritance属性。 我甚至将Person()抽象化了。

这是实质内容,正如我在评论中所说的那样装饰, TestProgram.cs

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.SQLite; using NLog; using System.Reflection; using System.Data.Entity; using Spire.Xls; using System.Data; using System.Collections; namespace DynamicEFLoading { class TestProgram { private static Logger logit = LogManager.GetCurrentClassLogger(); static void Main() { DateTime dtStart = new DateTime(DateTime.Now.Ticks); DateTime dtStop = new DateTime(); TestProgram s = new TestProgram(); Utils u = new Utils(); sp("Starting at " + DateTime.Now.ToLongTimeString()); // for this test, I leave this door open the whole time... using (MyDbContext ctx = new MyDbContext()) { //########################################################################### // // create a row in db each time run... // Random rnd = new Random(); // Child c1 = new DynamicEFLoading.Child(); // Age, Weight, Name all come from base class Person() c1.PersonAge = rnd.Next(120); c1.PersonWeight = rnd.Next(85, 300); c1.PersonName = String.Format("{0} {1}", Utils.GenerateName(6), Utils.GenerateName(8)); sp(String.Format("Created .Name: {0}", c1.PersonName)); // c1.dChildBaz = rnd.NextDouble(); c1.iChildBar = rnd.Next(99999); c1.sChildFoo = String.Format("{0}", Utils.GenerateName(10)); sp(String.Format("Created .sParentFoo: {0}", c1.sChildFoo)); // ctx.Children.Add(c1); //_______________________________________________________ ctx.SaveChanges(); //########################################################################### // // in production, there would be no hard coding... // string fileLocation = @"C:\Users\\Desktop\test.xlsx"; // // NOTE! Here I am specifying the only tab(ws name) in the wb. This is easily changed // to access all tabs by index, using Worksheet ws = wb.Worksheets[index], and // a simple loop through them all. In my first test, I even verify the tab had // a corresponding table in DB before continuing... string tableName = "Child"; //---------------------------------------------------------- // freeSpire.xls Workbook wb = new Workbook(); wb.LoadFromFile(fileLocation); //---------------------------------------------------------- // see NOTE! just above... Worksheet ws = wb.Worksheets[tableName]; //---------------------------------------------------------- // create a DataTable DataTable dt = new DataTable(tableName); // load it with data from whoile worksheet (ws) dt = ws.ExportDataTable(); // from now on, we use DataTable-not spreadsheet //---------------------------------------------------------- sp(String.Format("wb.WorkSheets count: " + wb.Worksheets.Count)); sp(String.Format("ws.Rows count: " + ws.Rows.Count())); sp(String.Format("dt.Rows.Count: " + dt.Rows.Count)); sp(String.Format("dt.Name: " + dt.TableName)); //========================================================== // getting assembly name programmatically fails when a project is inside a solution // in VS. It assumes ...\ProjName\ProjName\... whis isn't how solutions go... string pathToAssembly = @"C:\Users\\Documents\Visual Studio 2015\Projects\DynamicEFLoading\DynamicEFLoading\bin\Debug\DynamicEfLoading.exe"; // string pathToAssembly = @".\DynamicEfLoading.exe"; // create an 'anonymous', or ghost class: var aClass = u.CreateInstanceOf(dt.TableName, pathToAssembly); // display class type sp(String.Format("aClass.FullName: " + aClass.GetType().FullName)); //========================================================== // // creating a DbSet for the dt's entities. It isn't good enough to just create // the class itself-it needs to be from the DbContext (ctx). or else you can't // ctx.SaveChanges(); // sp(String.Format("Creating 'dbs' object...")); DbSet dbs = ctx.Set(aClass.GetType()); // But you can't att attributes/properties to a DbSet-only to the class it is // derived from, so we then use the DbSet (dbs) for this class to create an // empty class that we can populate & later add to DB via ctx: var theObj = dbs.Create(aClass.GetType()); // make sure it's the right one: sp(String.Format("GetType: " + theObj.GetType())); //____________________________________________________________________________ int i = 0; // used to keep track of each column as we go through the dt foreach (DataRow dr in dt.Rows) // each dr in the dt is a separate instance of the theObj class { sp(String.Format("================= row ==================================")); i = 0; // I don't like to put var instantiation in a loop... // each drItem is the content for the row (theObj) foreach (string drItem in dr.ItemArray) { sp(String.Format("================= col {0} ", i)); string entAttrName = dt.Columns[i].ToString(); string entAttrValue = dr[i].ToString(); // column (property) name: sp("[" + i + "] Item: " + entAttrName.ToString()); // the value of that property to load into this class' property sp("[" + i + "] Value: " + entAttrValue.ToString()); // which type of data is this property? (string, int32, double...) // -also has data like if nullable, etc. of use in later refinements... TypeInfo thisTypeInfo = theObj.GetType().GetTypeInfo(); // All details from the property to update/set: PropertyInfo theProp = thisTypeInfo.GetProperty(entAttrName); //___________________________________________________________________ // need to determine the property type, converting entAttrValuefrom string: // good debugging info at this stage to see what we've discovered from the class dynamically at rn time... sp("theProp.DeclaringType.FullName of attr: " + theProp.DeclaringType.FullName); sp("theProp.GetSetMethod(true).ToString() of attr: " + theProp.GetSetMethod(true).ToString()); sp("theProp.GetType().ToString() of attr: " + theProp.GetType().ToString()); sp("theProp.Name of attr: " + theProp.Name); sp("theProp.PropertyType.ToString() of attr: " + theProp.PropertyType.ToString()); sp("theProp.ReflectedType.ToString() of attr: " + theProp.ReflectedType.ToString()); sp("theProp.ReflectedType.ToString() of attr: " + theProp.SetMethod.ReturnType.ToString()); /* update entAttrName with entAttrValue: * * At this point, my values in the DataTable are all strings, but the class itself * stores that value as who-knows-what. So here I just parse out what kind it is from three * common types. In future, may need to add more, but for now, these are the big 4: * * String, Integer, DatTime, and Double */ if (theProp.PropertyType.ToString() == "System.String") { theProp.SetValue(theObj, (String)entAttrValue); logit.Debug("Set {0} value: {1}", theProp.PropertyType.ToString(), entAttrValue); } else if (theProp.PropertyType.ToString().Contains("System.Int32")) { theProp.SetValue(theObj, int.Parse(entAttrValue)); logit.Debug("Set {0} value: {1}", theProp.PropertyType.ToString(), entAttrValue); } else if (theProp.PropertyType.ToString().Contains("System.DateTime")) { IFormatProvider culture = new System.Globalization.CultureInfo("en-US", true); DateTime dTime = DateTime.Parse(entAttrValue, culture, System.Globalization.DateTimeStyles.AssumeLocal); theProp.SetValue(theObj, entAttrValue); logit.Debug("Set {0} value: {1}", theProp.PropertyType.ToString(), entAttrValue); } else if (theProp.PropertyType.ToString().Contains("System.Double")) { theProp.SetValue(theObj, double.Parse(entAttrValue)); logit.Debug("Set {0} value: {1}", theProp.PropertyType.ToString(), entAttrValue); } else { logit.Error("Unexpected property type: {0} given. string value: {1}", theProp.PropertyType.ToString(), entAttrValue ); } i++; // increment column index... } // end foreach (string drItem in dr.ItemArray... // add class to context... dbs.Add(theObj); // to save one by one (each row): ctx.SaveChanges(); } // end of foreach (DataRow dr in dt.Rows... // or... to save as batch (at end of worksheet): // ctx.SaveChanges(); //########################################################################### dtStop = new DateTime(DateTime.Now.Ticks); TimeSpan tsDuration = dtStop - dtStart; sp(String.Format("end... took {0} seconds.", tsDuration.TotalSeconds.ToString())); Console.ReadKey(); } // end using DbContext... } /* * Convenience; writes to both... * */ //private static void p(string message) private void p(string message) { logit.Info(message); Console.WriteLine(message); } } } 

最后,这是包含Utils()类的Utils.cs文件。 您可以在上面将这些称为“u。*”,在TestProgram.cs顶部实例化的方式:

 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using NLog; using System.Collections; using System.Data; namespace DynamicEFLoading { class Utils { private static Logger logit = LogManager.GetCurrentClassLogger(); public ArrayList HeadersFromDataTable(DataTable theDataTable) { ArrayList arHeaders = new ArrayList(); try { arHeaders.AddRange(theDataTable.Columns); logit.Info("loaded {0} column headers...", arHeaders.Count); } catch (Exception ex) { logit.Fatal("exception: " + ex.ToString()); } return arHeaders; } public object CreateInstanceOf(string ClassName, string AssemblyFullPath) { var assemblyFullPath = Assembly.LoadFrom(@"C:\Users\\Documents\Visual Studio 2015\Projects\PoliticWebSite\ScratchConsole1\bin\Debug\ScratchConsole1.exe"); var assembly = Assembly.GetExecutingAssembly(); //var assembly = Assembly.LoadFrom(assemblyFullPath); var type = assembly.GetTypes().First(t => t.Name == ClassName); return Activator.CreateInstance(type); } public static string GenerateName(int len) { Random rndSeed = new Random(DateTime.Now.Millisecond); Random r = new Random(rndSeed.Next()); string[] consonants = { "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", "w", "x" }; string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" }; string Name = ""; Name += consonants[r.Next(consonants.Length)].ToUpper(); Name += vowels[r.Next(vowels.Length)]; int b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name. while (b < len) { //_________________________________________________________________ rndSeed = new Random(DateTime.Now.Millisecond); r = new Random(rndSeed.Next()); Name += consonants[r.Next(consonants.Length)]; b++; //_________________________________________________________________ rndSeed = new Random(DateTime.Now.Millisecond); r = new Random(rndSeed.Next()); Name += vowels[r.Next(vowels.Length)]; b++; //_________________________________________________________________ } return Name; } } } 

那么所有这些都是什么样的,因为程序遍历每一列(可以是任何顺序,BTW)?

 2016-11-08 22:18:14.2500 INFO Starting at 10:18:14 PM 2016-11-08 22:18:14.3499 INFO Created .Name: Tytaetae Tytaetaetae 2016-11-08 22:18:14.3499 INFO Created .sParentFoo: Baebabababa 2016-11-08 22:18:15.2181 INFO wb.WorkSheets count: 2 2016-11-08 22:18:15.2181 INFO ws.Rows count: 3 2016-11-08 22:18:15.2338 INFO dt.Rows.Count: 2 2016-11-08 22:18:15.2338 INFO dt.Name: Child 2016-11-08 22:18:15.2487 INFO aClass.FullName: DynamicEFLoading.Child 2016-11-08 22:18:15.2487 INFO Creating 'dbs' object... 2016-11-08 22:18:15.2644 INFO GetType: DynamicEFLoading.Child 2016-11-08 22:18:15.2644 INFO ================= row ================================== 2016-11-08 22:18:15.2644 INFO ================= col 0 2016-11-08 22:18:15.2801 INFO [0] Item: sChildFoo 2016-11-08 22:18:15.2801 INFO [0] Value: Norwich 2016-11-08 22:18:15.2801 INFO theProp.DeclaringType.FullName of attr: DynamicEFLoading.Child 2016-11-08 22:18:15.2958 INFO theProp.GetSetMethod(true).ToString() of attr: Void set_sChildFoo(System.String) 2016-11-08 22:18:15.2958 INFO theProp.GetType().ToString() of attr: System.Reflection.RuntimePropertyInfo 2016-11-08 22:18:15.3114 INFO theProp.Name of attr: sChildFoo 2016-11-08 22:18:15.3114 INFO theProp.PropertyType.ToString() of attr: System.String 2016-11-08 22:18:15.3271 INFO theProp.ReflectedType.ToString() of attr: DynamicEFLoading.Child 2016-11-08 22:18:15.3271 INFO theProp.ReflectedType.ToString() of attr: System.Void 2016-11-08 22:18:15.3271 DEBUG Set System.String value: Norwich 2016-11-08 22:18:15.3428 INFO ================= col 1 ... 2016-11-08 22:18:16.1237 INFO ================= row ================================== 2016-11-08 22:18:16.1394 INFO ================= col 0 2016-11-08 22:18:16.1394 INFO [0] Item: sChildFoo 2016-11-08 22:18:16.1551 INFO [0] Value: Waterford 2016-11-08 22:18:16.1551 INFO theProp.DeclaringType.FullName of attr: DynamicEFLoading.Child 2016-11-08 22:18:16.1551 INFO theProp.GetSetMethod(true).ToString() of attr: Void set_sChildFoo(System.String) 2016-11-08 22:18:16.1707 INFO theProp.GetType().ToString() of attr: System.Reflection.RuntimePropertyInfo 2016-11-08 22:18:16.1863 INFO theProp.Name of attr: sChildFoo 2016-11-08 22:18:16.1863 INFO theProp.PropertyType.ToString() of attr: System.String 2016-11-08 22:18:16.1863 INFO theProp.ReflectedType.ToString() of attr: DynamicEFLoading.Child 2016-11-08 22:18:16.2020 INFO theProp.ReflectedType.ToString() of attr: System.Void 2016-11-08 22:18:16.2020 DEBUG Set System.String value: Waterford 2016-11-08 22:18:16.2179 INFO ================= col 1 ... 2016-11-08 22:18:16.5772 INFO ================= col 5 2016-11-08 22:18:16.5772 INFO [5] Item: PersonName 2016-11-08 22:18:16.5772 INFO [5] Value: Barney Rubble 2016-11-08 22:18:16.5772 INFO theProp.DeclaringType.FullName of attr: DynamicEFLoading.Person 2016-11-08 22:18:16.5927 INFO theProp.GetSetMethod(true).ToString() of attr: Void set_PersonName(System.String) 2016-11-08 22:18:16.5927 INFO theProp.GetType().ToString() of attr: System.Reflection.RuntimePropertyInfo 2016-11-08 22:18:16.5927 INFO theProp.Name of attr: PersonName 2016-11-08 22:18:16.6084 INFO theProp.PropertyType.ToString() of attr: System.String 2016-11-08 22:18:16.6084 INFO theProp.ReflectedType.ToString() of attr: DynamicEFLoading.Child 2016-11-08 22:18:16.6240 INFO theProp.ReflectedType.ToString() of attr: System.Void 2016-11-08 22:18:16.6240 DEBUG Set System.String value: Barney Rubble 2016-11-08 22:18:16.6397 INFO end... took 2.391686 seconds. 

2.4秒加载文件,将其解析为DataSet / DataTable,然后将它们转换为类和EF实例,检查每列是否有效。 全部在Linux笔记本电脑上的Win10 VM中。

就目前而言,我提供了(我的杂乱和低效)解决方案来抓取和保存EF数据,而不需要对Excel模板的标题行进行硬编码。

要-DOS:
-add循环遍历工作簿,完成所有工作表。
在数据表中指示的-addvalidation类实际上存在于EF中(我在我的导频代码中执行此操作)。
-addvalidation文件是导入之前的有效.xlsx文件(Spire具有此function)等。

我花了很多时间来使它工作,因为我从正常的编程中走过头来,我会很感激任何关于使它变得更好/更安全等的反馈。 我通过绊倒Intellisense和书籍来做到这一点,主要是。

================================================== ================