2008-09-06 37 views
45

¿Alguien sabe una forma de autogenerar tablas de bases de datos para una clase determinada? No estoy buscando una capa de persistencia completa. Ya tengo una solución de acceso a datos que estoy usando, pero de repente tengo que almacenar mucha información de un gran número de clases y realmente no quiero tener que crear todas estas tablas a mano. Por ejemplo, dada la clase siguiente:¿Cómo puedo generar tablas de base de datos a partir de clases de C#?

class Foo 
{ 
    private string property1; 
    public string Property1 
    { 
     get { return property1; } 
     set { property1 = value; } 
    } 

    private int property2; 
    public int Property2 
    { 
     get { return property2; } 
     set { property2 = value; } 
    } 
} 

que esperaría el siguiente SQL:

CREATE TABLE Foo 
(
    Property1 VARCHAR(500), 
    Property2 INT 
) 

También me pregunto cómo se puede manejar tipos de complejos. Por ejemplo, en la clase citada anteriormente, si cambiamos que para ser:

class Foo 
{ 
    private string property1; 
    public string Property1 
    { 
     get { return property1; } 
     set { property1 = value; } 
    } 

    private System.Management.ManagementObject property2; 
    public System.Management.ManagementObject Property2 
    { 
     get { return property2; } 
     set { property2 = value; } 
    } 
} 

¿Cómo podría manejar esto?

He tratado de generar automáticamente las secuencias de comandos de la base de datos utilizando la reflexión para enumerar las propiedades de cada clase, pero es torpe y los tipos de datos complejos me han dejado perplejo.

+0

Jonathan: ¡Guau! ¡Gracias por todo eso, hiciste una increíble cantidad de trabajo! – VanOrman

+0

incluso estoy buscando la misma solución. ¿Podría ayudarme a generar tablas SQL de la clase? ? –

Respuesta

71

Es muy tarde, y sólo pasó unos 10 minutos en esto, por lo que su muy descuidado, sin embargo lo hace el trabajo y le dará un buen punto de partida:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Reflection; 

namespace TableGenerator 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      List<TableClass> tables = new List<TableClass>(); 

      // Pass assembly name via argument 
      Assembly a = Assembly.LoadFile(args[0]); 

      Type[] types = a.GetTypes(); 

      // Get Types in the assembly. 
      foreach (Type t in types) 
      { 
       TableClass tc = new TableClass(t);     
       tables.Add(tc); 
      } 

      // Create SQL for each table 
      foreach (TableClass table in tables) 
      { 
       Console.WriteLine(table.CreateTableScript()); 
       Console.WriteLine(); 
      } 

      // Total Hacked way to find FK relationships! Too lazy to fix right now 
      foreach (TableClass table in tables) 
      { 
       foreach (KeyValuePair<String, Type> field in table.Fields) 
       { 
        foreach (TableClass t2 in tables) 
        { 
         if (field.Value.Name == t2.ClassName) 
         { 
          // We have a FK Relationship! 
          Console.WriteLine("GO"); 
          Console.WriteLine("ALTER TABLE " + table.ClassName + " WITH NOCHECK"); 
          Console.WriteLine("ADD CONSTRAINT FK_" + field.Key + " FOREIGN KEY (" + field.Key + ") REFERENCES " + t2.ClassName + "(ID)"); 
          Console.WriteLine("GO"); 

         } 
        } 
       } 
      } 
     } 
    } 

    public class TableClass 
    { 
     private List<KeyValuePair<String, Type>> _fieldInfo = new List<KeyValuePair<String, Type>>(); 
     private string _className = String.Empty; 

     private Dictionary<Type, String> dataMapper 
     { 
      get 
      { 
       // Add the rest of your CLR Types to SQL Types mapping here 
       Dictionary<Type, String> dataMapper = new Dictionary<Type, string>(); 
       dataMapper.Add(typeof(int), "BIGINT"); 
       dataMapper.Add(typeof(string), "NVARCHAR(500)"); 
       dataMapper.Add(typeof(bool), "BIT"); 
       dataMapper.Add(typeof(DateTime), "DATETIME"); 
       dataMapper.Add(typeof(float), "FLOAT"); 
       dataMapper.Add(typeof(decimal), "DECIMAL(18,0)"); 
       dataMapper.Add(typeof(Guid), "UNIQUEIDENTIFIER"); 

       return dataMapper; 
      } 
     } 

     public List<KeyValuePair<String, Type>> Fields 
     { 
      get { return this._fieldInfo; } 
      set { this._fieldInfo = value; } 
     } 

     public string ClassName 
     { 
      get { return this._className; } 
      set { this._className = value; } 
     } 

     public TableClass(Type t) 
     { 
      this._className = t.Name; 

      foreach (PropertyInfo p in t.GetProperties()) 
      { 
       KeyValuePair<String, Type> field = new KeyValuePair<String, Type>(p.Name, p.PropertyType); 

       this.Fields.Add(field); 
      } 
     } 

     public string CreateTableScript() 
     { 
      System.Text.StringBuilder script = new StringBuilder(); 

      script.AppendLine("CREATE TABLE " + this.ClassName); 
      script.AppendLine("("); 
      script.AppendLine("\t ID BIGINT,"); 
      for (int i = 0; i < this.Fields.Count; i++) 
      { 
       KeyValuePair<String, Type> field = this.Fields[i]; 

       if (dataMapper.ContainsKey(field.Value)) 
       { 
        script.Append("\t " + field.Key + " " + dataMapper[field.Value]); 
       } 
       else 
       { 
        // Complex Type? 
        script.Append("\t " + field.Key + " BIGINT"); 
       } 

       if (i != this.Fields.Count - 1) 
       { 
        script.Append(","); 
       } 

       script.Append(Environment.NewLine); 
      } 

      script.AppendLine(")"); 

      return script.ToString(); 
     } 
    } 
} 

pongo estas clases en una asamblea para probarlo:

public class FakeDataClass 
{ 
    public int AnInt 
    { 
     get; 
     set; 
    } 

    public string AString 
    { 
     get; 
     set; 
    } 

    public float AFloat 
    { 
     get; 
     set; 
    } 

    public FKClass AFKReference 
    { 
     get; 
     set; 
    } 
} 

public class FKClass 
    { 
     public int AFKInt 
     { 
      get; 
      set; 
     } 
    } 

y generó el siguiente SQL:

CREATE TABLE FakeDataClass 
(
     ID BIGINT, 
     AnInt BIGINT, 
     AString NVARCHAR(255), 
     AFloat FLOAT, 
     AFKReference BIGINT 
) 


CREATE TABLE FKClass 
(
     ID BIGINT, 
     AFKInt BIGINT 
) 


GO 
ALTER TABLE FakeDataClass WITH NOCHECK 
ADD CONSTRAINT FK_AFKReference FOREIGN KEY (AFKReference) REFERENCES FKClass(ID) 
GO 

Algunas consideraciones adicionales ... Consideraría agregar un atributo como [SqlTable] a sus clases, de esa manera solo genera tablas para las clases que desea. Además, esto se puede limpiar un montón, errores corregidos, optimizados (el FK Checker es una broma), etc., etc. Solo para comenzar.

+6

No puedo creer que haya usado esto en 2016. ¡Muchas gracias por esto! – Gaspa79

+0

Sí y también en 2017! :). Gran trabajo gracias – haroonxml

1

Para tipos complejos, puede convertir recursivamente cada uno que encuentre en una tabla propia y luego tratar de gestionar las relaciones de claves externas.

Es posible que también desee especificar previamente que las clases se convertirán o no en tablas. En cuanto a los datos complejos que desea reflejar en la base de datos sin aumentar el esquema, puede tener una o más tablas para tipos diversos. En este ejemplo se utiliza tanto como 4:

CREATE TABLE MiscTypes /* may have to include standard types as well */ 
(TypeID INT, 
    TypeName VARCHAR(...) 
) 

CREATE TABLE MiscProperties 
(PropertyID INT, 
    DeclaringTypeID INT, /* FK to MiscTypes */ 
    PropertyName VARCHAR(...), 
    ValueTypeID INT /* FK to MiscTypes */ 
) 

CREATE TABLE MiscData 
( ObjectID INT, 
    TypeID INT 
) 

CREATE TABLE MiscValues 
(ObjectID INT, /* FK to MiscData*/ 
    PropertyID INT, 
    Value VARCHAR(...) 
) 
3

Creo que para los tipos de datos complejos, debe ampliarlos mediante la especificación de un método ToDB() que tiene su propia implementación para crear tablas en el DB, y de esta manera se vuelve auto-recursivo.

0

Además ... tal vez puedas usar alguna herramienta como Visio (no estoy seguro si Visio hace esto, pero creo que sí) para aplicar ingeniería inversa a tus clases en UML y luego usar el UML para generar el esquema DB. . O tal vez use una herramienta como esta http://www.tangiblearchitect.net/visual-studio/

0

Sé que está buscando una capa de persistencia completa, pero la tarea hbm2ddl de NHibernate puede hacer esto casi como una línea.

Hay un NAnt task disponible para llamarlo que puede ser de su interés.

13

@ Jonathan Holanda

Vaya, creo que es la obra más cruda que he visto alguna vez puse en un post StackOverflow. Bien hecho. Sin embargo , en lugar de construir sentencias DDL como cadenas, debería usar las clases SQL Server Management Objects introducidas con SQL 2005.

David Hayden tiene un post titulado Create Table in SQL Server 2005 Using C# and SQL Server Management Objects (SMO) - Code Generation que camina a través de cómo crear una tabla utilizando SMO. Los objetos de tipo fuerte hacen que sea muy fácil con métodos como:

// Create new table, called TestTable 
Table newTable = new Table(db, "TestTable"); 

y

// Create a PK Index for the table 
Index index = new Index(newTable, "PK_TestTable"); 
index.IndexKeyType = IndexKeyType.DriPrimaryKey; 

VanOrman, si está utilizando SQL 2005, sin duda hará SMO parte de su solución.

+1

Gran sugerencia. Nunca he visto esto antes. Su enlace está roto, pero aquí hay otro ejemplo: http://www.codeproject.com/Articles/127065/SMO-Tutorial-of-n-Programming-data-storage-objec – smoksnes

0

@PortMan,

Los Objetos de Datos SQL son frescas, no estaba al tanto de ellos. Eso definitivamente eliminaría parte del trabajo de mi código.

En realidad, mi código original era mucho más ordenado y se distribuía en 3 clases, pero después de que funcionó, decidí fusionarlo todo en una sola pasta para aquí.

Considero agregar un atributo [SqlTable] a las clases de dominio, así como [PrimaryKey] a la clave de ID principal, en lugar de tener el código de autogenerar una ID.

Al hacer eso, el código podría colocar correctamente las restricciones de PK, Índice y FK en las columnas correctas.

Sin embargo, VanOrMan podría probablemente usar mi código como está para generar el 99% de lo que necesita y luego simplemente ajustar el SQL en un editor antes de ejecutarlo.

0

Subsonic es también otra opción. A menudo lo uso para generar clases de entidades que se asignan a una base de datos. Tiene una utilidad de línea de comandos que le permite especificar tablas, tipos y un conjunto de otras cosas útiles

0

Pruebe DaoliteMappingTool para .net. Puede ayudarte a generar las clases. Descargar formulario Here

3

probar mi método de extensión CreateSchema objetos entre los http://createschema.codeplex.com/

Devuelve una cadena para cualquier objeto que contiene CREAR Scripts de TABLE.

2

A partir de 2016 (creo), puede utilizar Entity Framework 6 Código Primero para generar SQL esquema de clases poco C# o utilizar Database First para generar el código C# de sql. Code First to DB walkthrough

Cuestiones relacionadas