我该如何从C#类生成数据库表?

66

有人知道如何根据给定的类自动生成数据库表格吗?我不是要寻找整个持久层 - 我已经有一个正在使用的数据访问解决方案,但我突然需要存储大量来自许多类的信息,我真的不想手动创建所有这些表格。例如,考虑以下类:

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

我期望以下SQL:

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

我也想知道你如何处理复杂类型。例如,在之前引用的类中,如果我们改为:

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

我该如何处理这个问题?

我曾试图使用反射自动生成数据库脚本,枚举每个类的属性,但操作繁琐复杂的数据类型让我困惑不解。


我也在寻找同样的解决方案。你能帮我从类中生成SQL表吗? - Keerthi Kumar
12个回答

110

现在已经很晚了,我只花了约10分钟时间完成这个任务,因此做得非常草率,但它确实有效,可以为你提供一个很好的起点:

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

我把这些类放在一个程序集中进行测试:

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

它生成了以下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

一些进一步的想法...我建议为你的类添加一个属性,比如[SqlTable],这样它只会为你想要的类生成表。此外,这可以被大量优化、修复bug(外键检查器真的很糟糕)等等……只是为了让你开始。


9
我简直不敢相信我在2016年使用过这个。非常感谢! - Gaspa79
4
2019年 - 登记报道 - jamesbascle
这是从C#类创建表的绝佳起点。如果有人需要关于SQL数据类型到CLR类型映射的文档,以下是来自Microsoft的链接。https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/sql-clr-type-mapping - Nikunj Ratanpara
如何通过参数传递程序集名称? - Zoyeb Shaikh

13

哇,我认为这是我在StackOverflow上看到过的最原始的工作了。 干得好。然而,与其将DDL语句构建为字符串,您应该绝对使用SQL 2005引入的SQL Server管理对象类。

David Hayden撰写了一篇题为“使用C#和SQL Server管理对象(SMO)创建SQL Server 2005中的表 - 代码生成”的文章,介绍了如何使用SMO创建表。 强类型化的对象使它变得非常容易,例如:

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

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

如果您正在使用SQL 2005,建议将SMO作为您的解决方案的一部分。


1
很好的建议。我以前从未见过这个。你的链接有问题,但是这里有另一个例子:http://www.codeproject.com/Articles/127065/SMO-Tutorial-of-n-Programming-data-storage-objec - smoksnes

4

请尝试使用我的CreateSchema对象扩展方法,网址为http://createschema.codeplex.com/

它可以返回任何包含CREATE TABLE脚本的对象的字符串。


3

截至2016年(我想),您可以使用Entity Framework 6 Code First从poco c#类生成SQL模式,或者使用Database First从SQL生成c#代码. Code First to DB walkthrough


3

我认为对于复杂的数据类型,你应该通过指定一个ToDB()方法来扩展它们,该方法包含了它们自己在数据库中创建表的实现方式,这样就可以实现自动递归。


1
对于复杂类型,您可以递归地将遇到的每个类型转换为自己的表,然后尝试管理外键关系。
您还可以预先指定要或不要转换为表格的类。至于想要在数据库中反映的复杂数据,而又不想使架构膨胀,您可以为各种类型设置一个或多个表。此示例最多使用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(...)
 )

0

0

另外...也许你可以使用一些工具,例如Visio(不确定Visio是否支持此功能,但我认为它是可以的),将你的类反向工程成UML,然后使用UML生成DB Schema...或者使用这样的工具http://www.tangiblearchitect.net/visual-studio/


0

我知道你正在寻找一个完整的持久层,但NHibernate的hbm2ddl任务几乎可以作为一行代码来完成这个任务。

有一个NAnt任务可用于调用它,可能会很有帮助。


0

Subsonic也是另一个选择。我经常使用它来生成映射到数据库的实体类。它有一个命令行工具,可以让您指定表、类型和其他一些有用的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接