Pergunta

Alguém conhece uma maneira de gerar automaticamente tabelas de banco de dados para uma determinada classe?Não estou procurando uma camada de persistência inteira - já tenho uma solução de acesso a dados que estou usando, mas de repente tenho que armazenar muitas informações de um grande número de classes e realmente não quero ter que criar todas essas tabelas à mão.Por exemplo, dada a seguinte classe:

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; }
    }
}

Eu esperaria o seguinte SQL:

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

Também estou me perguntando como você poderia lidar com tipos complexos.Por exemplo, na classe citada anteriormente, se mudássemos isso para:

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; }
    }
}

Como eu poderia lidar com isso?

Eu tentei gerar automaticamente os scripts de banco de dados usando reflexão para enumerar as propriedades de cada classe, mas é desajeitado e os tipos de dados complexos me deixaram perplexo.

Foi útil?

Solução

É muito tarde e gastei apenas cerca de 10 minutos nisso, então é extremamente desleixado, mas funciona e lhe dará um bom ponto 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();
        }
    }
}

Coloquei essas classes em um assembly para testá-lo:

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;
        }
    }

E gerou o seguinte 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

Algumas reflexões adicionais...Eu consideraria adicionar um atributo como [SqlTable] às suas classes, dessa forma ele só gera tabelas para as classes que você deseja.Além disso, isso pode ser muito limpo, corrigidos bugs, otimizado (o FK Checker é uma piada) etc etc... Só para você começar.

Outras dicas

@Jonathan Holanda

Uau, acho que esse é o trabalho mais bruto que já vi colocado em uma postagem do StackOverflow.Bom trabalho. No entanto, em vez de construir instruções DDL como strings, você definitivamente deveria usar o Objetos de gerenciamento do SQL Server classes introduzidas com o SQL 2005.

David Hayden tem uma postagem intitulada Criar tabela no SQL Server 2005 usando C# e SQL Server Management Objects (SMO) - Geração de código que mostra como criar uma tabela usando SMO.Os objetos fortemente tipados facilitam muito com métodos como:

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

e

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

VanOrman, se você estiver usando o SQL 2005, definitivamente inclua o SMO em sua solução.

Experimente meu método de extensão CreateSchema para objetos em http://createschema.codeplex.com/

Ele retorna uma string para qualquer objeto contendo scripts CREATE TABLE.

Acho que para tipos de dados complexos, você deve estendê-los especificando um método ToDB() que contém sua própria implementação para a criação de tabelas no banco de dados, e desta forma torna-se auto-recursivo.

A partir de 2016 (eu acho), você pode usar o Entity Framework 6 Code First para gerar o esquema SQL a partir de classes poco c# ou usar o Database First para gerar código c# a partir de sql.Passo a passo do Code First para DB

Para tipos complexos, você pode converter recursivamente cada um que encontrar em uma tabela própria e, em seguida, tentar gerenciar relacionamentos de chave estrangeira.

Você também pode pré-especificar qual as classes serão ou não convertidas em tabelas.Quanto aos dados complexos que você deseja refletir no banco de dados sem sobrecarregar o esquema, você pode ter uma ou mais tabelas para tipos diversos.Este exemplo usa até 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(...)
 )

Você pode fazer o oposto, tabela de banco de dados para classes C# aqui:http://pureobjects.com/dbCode.aspx

Também...talvez você possa usar alguma ferramenta como o Visio (não tenho certeza se o Visio faz isso, mas acho que faz) para fazer engenharia reversa de suas classes em UML e depois usar a UML para gerar o esquema de banco de dados ...ou talvez use uma ferramenta como esta http://www.tangiblearchitect.net/visual-studio/

Eu sei que você está procurando por uma camada de persistência inteira, mas a tarefa hbm2ddl do NHibernate pode fazer isso quase como uma linha única.

Existe um Tarefa NAnt disponível para chamá-lo, o que pode muito bem ser de interesse.

Subsônico também é outra opção.Costumo usá-lo para gerar classes de entidade que mapeiam para um banco de dados.Possui um utilitário de linha de comando que permite especificar tabelas, tipos e uma série de outras coisas úteis

Experimente DaoliteMappingTool para .net.Isso pode ajudá-lo a gerar as classes.Baixar formulário Aqui

Existe um aplicativo gratuito, o Schematrix, que gera classes a partir do banco de dados, confira se faz o inverso também :) http://www.schematrix.com/products/schemacoder/download.aspx

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top