将C#类(类库)转换为SQL DDL(表)

3
我需要将一组C#类(类库)转换为SQL表格,以便通过数据库存储和操作数据。问题在于这些类的数量很大(超过1000个类),手动设置这样的数据库架构(表、索引、存储过程等)需要很长时间,更不用说我需要维护的类层次结构。
所以问题是:
是否有工具可以帮助我从C#类库创建数据库架构?
我并不寻求完美的工具(但如果有这样的工具,我会很高兴知道),但希望有一个至少可以帮助我创建起点的工具。
请注意,我希望您提出的解决方案最好参考.NET Framework 2.0版本,并基于传统实践(如T-SQL代码:CREATE TABLE ...),尽管每个建议都受到欢迎。

你在哪里工作?我绝对不想在那里工作!1000张表,祝你好运! - leppie
数据库设计通常是关系型的,不像你有成千上万个类。另外,今天的数据是如何存储的?它们不能全部存在内存中,也许你可以转换现有的数据库? - Kobi
2
@leppie:有时需要支持现有的代码。 - ileon
@Kobi:幸运的是,类库将数据维护在一个非常接近关系型数据库的模式中。有标识符(int值)、外键等,但所有操作都是用C#完成的。目的是将数据管理移至SQL Server。 - ileon
1
我以前在一個複雜的政府項目中工作,該項目必須與其他遺留系統共存,它也有超過1000個表。我同意它們不太美觀,有些是重複的,但有很多依賴關係是超出我們控制的,我們只能處理和支持它。 - Fadrian Sudaman
@ileon:关于创建1000个新表的事情,目前没有任何现成的东西。 - leppie
6个回答

2
如果您使用Visual Studio 2010 v4.0框架,那么有一些新的功能可以使用“模型优先”和实体框架来生成脚本。显然,这对于v2.0来说并没有什么帮助,但我认为值得一提。
“模型优先”功能允许您在实体设计器中设计一个概念性(CSDL)模型,然后生成存储(SSDL)模型以及它们之间的映射(MSL)。此外,我们还会生成用于创建数据库的T-SQL脚本。要运行“模型优先”向导,只需右键单击实体设计器界面,然后选择“从模型生成数据库”。这里有一篇深入的博客文章here。另外,请查看this article

欢迎提出您的建议。我只是不想强迫客户使用.NET 3.5或4.0版本。但如果我找不到2.0版本的解决方案,我将使用基于较新版本的.NET的解决方案。谢谢。 - ileon
我对“模型优先”这个概念还比较陌生,最近只是读了一些文章,所以我的知识相当有限。我知道你可以从实体设计器中创建表脚本等,但是如何定义1000多个类/表的初始模型呢?这不还是非常繁琐和手动的过程吗?如果是这样的话,那么完成这项工作所需的工作量将与编写创建表脚本并使用管理工具实际创建表的工作量相当。 - Fadrian Sudaman

2

我不知道有什么工具可以做到这一点,但是对于那么多的表格,也许值得编写一个小工具,使用反射获取所有属性,并输出带有列名和类型定义的创建表语句。完美的脚本很难实现,但正如你在问题中所述,这可以为您提供一个非常好的起点,然后调整字段类型和大小。


这是一个明智的做法。感谢您的建议。 - ileon

1

大多数对象关系映射工具都包括一个选项,可以基于您映射的类创建数据库模式。

以下是使用NHibernate和Fluent NHibernate完成此操作的完整示例。这是一个独立的C#控制台应用程序,将映射一组C#业务对象,并根据它们创建数据库模式。

两个重要的注意事项:

  1. NHibernate(和Fluent NH)要求您的业务对象的所有属性和方法必须声明为虚拟的;这可能需要修改您现有的一些代码。
  2. 永远不要对实时数据库运行此代码 - 默认情况下,它会为所有现有表生成DROP语句并重新创建它们。这将删除所有数据,这会让您感到悲伤。您已经被警告了。

这里是program.cs。请注意三个命名空间 - 一个包含程序本身,一个包含实体,一个包含Fluent NHibernate的映射覆盖示例(如果您的对象不遵循内置映射约定,则非常有用)

using System;
using System.Collections.Generic;
using System.IO;
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using Schematica.Entities;

namespace Schematica.ConsoleApp {

    class Program {

        const string SCHEMA_FILENAME = "schema.sql";
        const string CONNECTION_STRING = "Data Source=spotgeek;Initial Catalog=dylanhax;Integrated Security=True";

        public static void Main(string[] args) {

            if (File.Exists(SCHEMA_FILENAME)) File.Delete(SCHEMA_FILENAME);

            ConfigureNHibernate(CONNECTION_STRING, MapEntities);

            Console.WriteLine("Exported schema to " + (Path.GetFullPath(SCHEMA_FILENAME)));
            Console.ReadKey(false);
        }


        private static void MapEntities(MappingConfiguration map) {

            // Notice how we're constraining the auto-mapping to only map those entities
            // whose namespace ends with "Entities" - otherwise it'll try to 
            // auto-map every class in the same assembly as Customer.

            map.AutoMappings.Add(
                AutoMap.AssemblyOf<Customer>()
                .Where(type => type.Namespace.EndsWith("Entities"))
                .UseOverridesFromAssemblyOf<Customer>());
        }

        private static Configuration ConfigureNHibernate(string connectionString, Action<MappingConfiguration> mapper) {
            var database = Fluently.Configure().Database(MsSqlConfiguration.MsSql2005.ConnectionString(connectionString));
            return (database.Mappings(mapper).ExposeConfiguration(ExportSchema).BuildConfiguration());
        }

        private static void WriteScriptToFile(string schemaScript) {
            File.AppendAllText(SCHEMA_FILENAME, schemaScript);
        }

        private static void ExportSchema(Configuration config) {
            bool createObjectsInDatabase = false;
            new SchemaExport(config).Create(WriteScriptToFile, createObjectsInDatabase);
        }
    }
}

// This demonstrates how to override auto-mapped properties if your objects don't 
// adhere to FluentNH mapping conventions.
namespace Schematica.Mappings {
    public class ProductMappingOverrides : IAutoMappingOverride<Product> {
        public void Override(AutoMapping<Product> map) {

            // This specifies that Product uses ProductCode as the primary key, 
            // instead of the default Id field.
            map.Id(product => product.ProductCode);
        }
    }
}


// This is the namespace containing your business objects - the things you want to export to your database.
namespace Schematica.Entities {
    public class Customer {
        public virtual int Id { get; set; }
        public virtual string Forenames { get; set; }
        public virtual string Surname { get; set; }
    }

    public class Product {
        public virtual Guid ProductCode { get; set; }
        public virtual string Description { get; set; }
    }

    public class Order {
        public virtual int Id { get; set; }
        private IList<Product> products = new List<Product>();
        public virtual IList<Product> Products {
            get { return products; }
            set { products = value; }
        }
        public virtual Customer Customer { get; set; }
    }
}

以下是上述代码导出到schema.sql的内容:

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK952904EBD5E0278A]') AND parent_object_id = OBJECT_ID('[Product]'))
    alter table [Product]  drop constraint FK952904EBD5E0278A

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKD1436656C882C014]') AND parent_object_id = OBJECT_ID('[Order]'))
    alter table [Order]  drop constraint FKD1436656C882C014

if exists (select * from dbo.sysobjects where id = object_id(N'[Customer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Customer]
if exists (select * from dbo.sysobjects where id = object_id(N'[Product]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Product]
if exists (select * from dbo.sysobjects where id = object_id(N'[Order]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [Order]

create table [Customer] (
    Id INT IDENTITY NOT NULL,
   Forenames NVARCHAR(255) null,
   Surname NVARCHAR(255) null,
   primary key (Id)
)

create table [Product] (
    ProductCode UNIQUEIDENTIFIER not null,
   Description NVARCHAR(255) null,
   Order_id INT null,
   primary key (ProductCode)
)

create table [Order] (
    Id INT IDENTITY NOT NULL,
   Customer_id INT null,
   primary key (Id)
)

alter table [Product] 
    add constraint FK952904EBD5E0278A 
    foreign key (Order_id) 
    references [Order]

alter table [Order] 
    add constraint FKD1436656C882C014 
    foreign key (Customer_id) 
    references [Customer]

1

最新的"Code First" Entity Framework CTP已经发布。在稍微编写一些代码后,您可以使用它直接从代码生成数据库。

如果要创建1000个表格,仍需要花费一些时间。


我也考虑过那个选项,但是原帖作者仍需要将这些实体映射到现有的类。不过这样可能更容易些。 - Kobi
1
谢谢这个,这比模型优先更好。 - Sruly

1

如果类结构是关系型的,并且相对简单,可以转换为SQL Create Table或EF模型,也许您可以编写自己的代码生成脚本来创建.edmx或SQL。

您可以使用T4或CodeDom,这可能比手动创建1000个表要快得多。

编辑:我在第一次撰写答案时忘记了这一点。不久前,我看到Rob Conery的一个视频演示了Subsonic Simple Repositroy。Simple Repository允许您将对象插入到DB中,如果没有表,则会为您创建一个表。

在此处查看视频 http://subsonicproject.com/docs/Using_SimpleRepository


1

仅仅是为了更详细地解释其他人所说的--大多数ORM类型的工具都有模型优先的功能。就像EF4、NHibernate和Telerik Open Access一样,Subsonic(http://www.subsonicproject.com/)有一个简单的存储库模式,可以从类生成表。

另一个选择是编写一个简单的基于反射的工具来遍历您的类。


虽然你没有问,但我对你的数据访问需求很好奇。我们使用关系数据库来执行三个主要任务:持久性、高效检索信息和声明性参照完整性(例如外键)。仅为每个类创建一个表只解决了问题的一半。

你的数据量大吗(即数百GB或多TB)?有很多读取操作吗?很多写入操作吗?你的类有自然主键吗?还是使用替代键?你的类之间定义了关系吗(例如员工工厂中工作)。

类有很多字段吗?您是否有任何元数据指示精度的数据类型,即不仅是string Name,而且Name最多可以有80个字符并且不可为空?您只需要存储英语还是使用需要扩展字符集(如汉语)的语言?如果没有方法指示精度,则数据库将最终具有严重宽行(或潜在宽行)。许多数据库将最大行大小限制为8k-64k范围内的某些内容。宽行会影响读取和写入性能。使用text字段可能会避开页面大小限制,但会导致更昂贵的读取性能。

与实施数据库结构一样重要的是对其进行索引并使用引用完整性。类/表的数量随时间增长吗?也许更抽象的结构与较少的表格更合适。

这只是我的2美分。希望这有所帮助。


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