65.9K
CodeProject 正在变化。 阅读更多。
Home

Visual Studio LightSwitch 应用程序数据库脚本生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (3投票s)

2012 年 1 月 23 日

CPOL

4分钟阅读

viewsIcon

32464

downloadIcon

352

一个用于从 LightSwitch 内部数据库生成 SQL Server 数据库脚本的工具

ExportLSDbasScript

引言

在本文中,我们将介绍一个用于从 LightSwitch 内部数据库以及外部数据源生成 SQL Server 数据库脚本的工具。

设想一种情况,我们正在开发 LightSwitch 业务应用程序,并使用内部数据库 [ApplicationData] 来存储数据。最近,我们决定将 LightSwitch 内部数据库迁移到 SQL Server。

将内部数据库迁移到 SQL Server 的第一种方法是使用 Visual Studio LightSwitch 中的发布向导。当我们发布应用程序时,其中有一个步骤,我们可以指定生成数据库脚本的路径,以部署 LightSwitch 内部数据库作为 SQL Server 数据库。

第二种方法是创建一个工具来从 LightSwitch 内部数据库生成脚本。因此,我尝试了第二种方法,现在分享给您……

背景

每当我们向 LightSwitch 应用程序添加一个数据源(例如 ApplicationData)时,LightSwitch 运行时会在构建应用程序时,以 XML 格式在以下文件中为创建的数据源创建结构。

  • ApplicationData.SSDL
  • ApplicationData.MSL
  • ApplicationData.CSDL

这三个文件在构建应用程序时生成,并位于 \ServerGenerated\GeneratedArtifacts\ 目录下。这些文件描述了我们创建的表(实体)结构。

设计工具

我们将使用 WPF 开发一个工具。下面的 XAML 显示了我们工具的主屏幕,它将包含一个浏览按钮和一个 RichTextBox 来显示生成的脚本。

 <Grid Margin="20,20,20,20">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="25"/>
            <RowDefinition Height="*" />
            <RowDefinition Height="25" />
        </Grid.RowDefinitions>
        
        <StackPanel Orientation="Horizontal" 
                    Background="Wheat" 
                    Grid.Column="0" 
                    Grid.Row="0" 
                    HorizontalAlignment="Stretch">
            <TextBlock Name="txtbFolder" 
                       Margin="0,5,0,0" 
                       FontSize="14" 
                       FontFamily="Calibri" 
                       FontWeight="Bold" 
                       TextOptions.TextRenderingMode="Grayscale" 
                       Text="Please select the folder where 
                             your LightSwitch application is.." />
            <Button Content="Browse" 
                    Margin="30,0,30,0" 
                    HorizontalAlignment="Right"
                    Name="btnBrowse" 
                    Click="btnBrowse_Click"  />
        </StackPanel>

        <RichTextBox  Name="rtxtTables" 
                      Foreground="Blue" 
                      IsReadOnly="True" 
                      Grid.Row="1" 
                      HorizontalAlignment="Stretch" 
                      VerticalAlignment="Stretch" 
                      HorizontalScrollBarVisibility="Auto" 
                      VerticalScrollBarVisibility="Auto" />
        
        <StackPanel  Grid.Row="2" Background="Wheat" >
            <TextBlock Name="txtProg" 
                       Margin="0,5,0,0" 
                       Foreground="Red" 
                       FontWeight="ExtraBold" 
                       FontFamily="Calibri" 
                       FontSize="14" />
        </StackPanel>
</Grid>

当您单击浏览按钮时,将弹出文件夹浏览器,并要求您选择 LightSwitch 应用程序文件夹。一旦您选择了LightSwitch文件夹,我们就需要弹出一个窗口,其中列出了所选 LightSwitch 应用程序可用的数据源。

<Popup Name="popupListDB" Height="350" Width="550" Placement="Center">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="25" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="25"/>
                </Grid.RowDefinitions>
                <TextBlock Name="txtbList" 
                           Grid.Column="0" 
                           Grid.Row="0" 
                           Margin="0,3,0,0" 
                           FontWeight="ExtraBold"  
                           Foreground="FloralWhite" 
                           Text="Select the database(s) to Export as script" />
                <ListBox Name="lstData" 
                         Grid.Row="1" 
                         Grid.Column="0" 
                         SelectionMode="Multiple" 
                         SelectionChanged="lstData_SelectionChanged">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding DatabaseName}" 
                                Width="Auto" />
                                <TextBlock Text="{Binding DatabaseSize}" 
                                Margin="25,0,0,0"/>
                                <CheckBox Margin="25,0,0,0" 
                                          Name="chkDrop" 
                                          Click="chkDrop_Click" 
                                          IsChecked="True"  
                                          Content="Drop if already exists." />
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>

                <StackPanel Orientation="Horizontal" 
                Grid.Row="2" Grid.Column="0">
                    <TextBlock Name="txtError" 
                               Margin="0,3,0,0" 
                               FontWeight="ExtraBold" 
                               HorizontalAlignment="Right" 
                               Foreground="Red" />
                </StackPanel>

                <StackPanel Orientation="Horizontal" 
                HorizontalAlignment="Right" 
                 Grid.Row="2" Grid.Column="0">
                    <Button Name="btnOk" 
                            Width="50" 
                            Content="OK" 
                            Click="btnOk_Click"/>
                    <Button Name="btnCancel" 
                            Content="Cancel" 
                            Width="50" 
                            Margin="30,0,0,0" 
                            Click="btnCancel_Click"/>
                </StackPanel>
            </Grid>
        </Popup> 

在上面的弹出窗口代码片段中,我们有一个 ListBox,我们将在此列表中添加可用的数据源。

SSDL 文件结构

我们知道,.SSDL 文件包含 XML 内容,我们将讨论其中的节点。

.SSDL 文件的根是 Schema。有三个子节点,如下所示:

  • EntityContainer
  • EntityType
  • 关联

EntityType 节点描述了数据源中使用的实体。如果您在数据源中有三个表,那么 Schema 根节点将有三个 EntityType 子节点。

<EntityType Name="Employee">
    <Key>
      <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="int" 
    Nullable="false" StoreGeneratedPattern="Identity" />
    <Property Name="Name" Type="nvarchar" 
    Nullable="false" MaxLength="255" />
    <Property Name="Gender" Type="nvarchar" 
    Nullable="false" MaxLength="255" />
    <Property Name="DOB" Type="datetime" 
    Nullable="false" />
    <Property Name="Email" Type="nvarchar" 
    Nullable="false" MaxLength="255" />
    <Property Name="Mobile" Type="nvarchar" 
    Nullable="false" MaxLength="255" />
    <Property Name="Employee_Designation" Type="int" 
    Nullable="false" />
    <Property Name="Employee_Project" Type="int" 
    Nullable="false" />
  </EntityType> 

您可以在上面的代码片段所示的 .SSDL 文件中看到示例实体结构。EntityType 节点有两个子节点,分别是 KeyPropertyKey 节点描述主键,Property 节点描述表中的列。

<Association Name="Employee_Designation">
    <End Role="Designation" 
    Type="ApplicationDataStore.Designation" Multiplicity="1" />
    <End Role="Employee" 
    Type="ApplicationDataStore.Employee" Multiplicity="*" />
    <ReferentialConstraint>
      <Principal Role="Designation">
        <PropertyRef Name="Id" />
      </Principal>
      <Dependent Role="Employee">
        <PropertyRef Name="Employee_Designation" />
      </Dependent>
    </ReferentialConstraint>
  </Association> 

接下来,Association 子节点描述了实体之间的关联。上面的代码片段显示了 Employee 实体和 Destination 实体之间的关联。Association 节点有两个子节点。ReferencialContraint 节点下的 Dependent 子节点描述外键,Principal 子节点描述外键的引用。

代码隐藏

选择 LightSwitch 应用程序文件夹后,我们将搜索 .SSDL 文件,这些文件将包含实体详细信息。

            FolderBrowserDialog fbd = new FolderBrowserDialog();

            fbd.ShowNewFolderButton = false;

            fbd.RootFolder = Environment.SpecialFolder.MyComputer; // default location

            if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                DBDetails db = new DBDetails(); // An entity which has 
                                                // the available Data Sources details

                txtProg.Text = "Please wait.....";

                var files = Directory.GetFiles(fbd.SelectedPath, 
                "*.ssdl", SearchOption.AllDirectories); // get all .SSDL files

                if (files.Length > 0)
                {
                    foreach (var item in files)
                    {

                        FileInfo fi = new FileInfo(item);

                        db.DBDetailsCollection.Add(

                            new DBDetails() {
                                                DatabaseName = fi.Name.Replace
                                                               (".ssdl", string.Empty),

                                                DatabaseSize = fi.Length.ToString() + 
                                                               " Bytes in size",

                                                DatabaseFilename = fi.FullName, 
                                                                   IsDropChecked = true
                                             });

                    }

                    lstData.ItemsSource = db.DBDetailsCollection;

                    popupListDB.IsOpen = true; // Display the available Data Sources 
                                               // in pop up window.

                    txtProg.Text = "";
                }
                else
                {
                    txtProg.Text =  "No data sources found in the 
                       selected LightSwitch application !"; // Status message
                }
            } 

在上面的代码片段中,我们获取所有 .SSDL 文件,并将文件的详细信息添加到 DBDetails 类实例中。DBDetails 实体具有用于存储可用数据源文件信息的属性。

        class DBDetails
        {
            public string DatabaseName { get; set; }
            public string DatabaseSize { get; set; }
            public bool IsDropChecked { get; set; }
            public string DatabaseFilename { get; set; }
            
            public DBDetails()
            {
                DatabaseFilename = string.Empty;
                DatabaseName = string.Empty;
                IsDropChecked = true;
                DatabaseSize = string.Empty;
            }            
        } 

上面的代码片段显示了具有文件信息属性的 DBDetails 实体。一旦您在弹出窗口中选择列出的 ApplicationData 文件,我们就从 .SSDL 文件生成脚本。

要生成数据库脚本,我们有四个方法:

  • CreateColumn
  • GenerateEntityScript
  • GenerateConstraintScript
  • GenerateContraintCheckScript

CreateColumn 方法用于创建表的列。

 private string CreateColumn(XmlNode item)
        {
            StringBuilder column = new StringBuilder(string.Empty);

            foreach (XmlAttribute xAttri in item.Attributes)
            {
                switch (xAttri.Name)
                {
             // Name of the Column
                    case "Name":
                        column.Append(string.Format("{0}[{1}] ", 
                                      Environment.NewLine, xAttri.Value));
                        break;
                    // Type of the Column
                    case "Type":
                        column.Append(xAttri.Value.ToUpper() + " ");
                        if (item.Attributes["MaxLength"] != null) // Size of the Column
                        {
                            column.Append(string.Format("( {0} ) ",
                            item.Attributes["MaxLength"].Value));
                        }
                        break;
                    // Is the Column nullable
                    case "Nullable": 
                        column.Append(string.Format("{0} ",xAttri.Value.Equals("false") ? 
                                      "NOT NULL " : "NULL "));
                        break;
                    // Is it Identity Column
                    case "StoreGeneratedPattern":
                        column.Append(string.Format("{0} ( 1, 1 ) ", xAttri.Value.ToUpper()));
                        break;
                }
            }

            if (item.ParentNode.FirstChild.HasChildNodes) // Creating Primary Key
            {
                if (item.ParentNode.FirstChild.ChildNodes[0].Attributes
                   ["Name"].Value.Equals(item.Attributes["Name"].Value))
                {
                    column.Append(string.Format(
                                             "{1}PRIMARY KEY CLUSTERED ( [{0}] ASC ) "
                                            +"{1}WITH ("
                                            +"{1}       ALLOW_PAGE_LOCKS = ON, "
                                            +"{1}       ALLOW_ROW_LOCKS = ON, " 
                                            +"{1}       PAD_INDEX = OFF, " 
                                            +"{1}       IGNORE_DUP_KEY = OFF, "
                                            +"{1}       STATISTICS_NORECOMPUTE = OFF "
                                            +"{1}      ) ", item.Attributes["Name"].Value,
                                                        Environment.NewLine));
                }
            }

            return string.Format("{0}",column.Insert(column.Length, ", ").ToString());
        }

GenerateEntityScript 方法用于生成数据源中创建的表的脚本。

private string GenerateEntityScript(XmlNodeList xNodeList)
        {
            StringBuilder script = new StringBuilder(string.Empty);
          
            foreach (XmlNode item in xNodeList)
            {
                if (item.HasChildNodes)
                {
                    string scrt = GenerateEntityScript(item.ChildNodes);

                    if (!item.Name.Equals("Key"))
                    {
                        strScript.Append(string.Format(
                                                        "IF NOT EXISTS (SELECT 1 FROM 
                                                        sys.objects WHERE object_id = 
                                                        OBJECT_ID(N'[dbo].[{1}]') AND 
                                                        type in (N'U'))"
                                                       +"{0}BEGIN"
                                                       +"{0}CREATE TABLE [dbo].[{1}] ( {2} " 
                                                       +"{0});"
                                                       +"{0}     PRINT 'Table ''{1}'' 
                                                                 created successfully !.'"
                                                       +"{0}END"
                                                       +"{0}ELSE"
                                                       +"{0}BEGIN"
                                                       +"{0}     PRINT 'The Table ''{1}'' 
                                                       already exists ! Please drop it & 
                                                       try again.'"
                                                       +"{0}END{0}",
                                                       Environment.NewLine,
                                                       item.Attributes["Name"].Value, 
                                                       scrt.ToString().Trim().TrimEnd(','))
                                                       ); 
                    }
                }
                else
                {
                    if (item.Name.Equals("Property"))
                    {
                        script.Append(string.Format("{0}",CreateColumn(item)));
                    }

                }
            }

            return script.ToString();
        } 

我们将 EntityType 节点作为参数传递给上面的 GenerateEntityScript 方法。

接下来,我们需要创建外键脚本。GenerateConstraintScript 方法用于创建为表定义的外键。我们拥有的最后一个方法是 GenerateContraintCheckScript

private string GenerateConstraintCheckScript(XmlNodeList xmlNLAssociation)
        {
            StringBuilder script = new StringBuilder(Environment.NewLine + 
                       "--Creating CHECK constraints for the tables starts here--");

            foreach (XmlNode item in xmlNLAssociation)
            {
                string[] names = item.Attributes["Name"].Value.StartsWith
                 ("FK_", StringComparison.CurrentCulture) ? 
                 item.Attributes["Name"].Value.Split('_') : 
                 ("FK_" + item.Attributes["Name"].Value).Split('_');

                if (names.Length >1)
                {
                    script.Append(string.Format(
                                                  "{0}IF  EXISTS (SELECT 1 
                                                  FROM sys.foreign_keys WHERE object_id = 
                                                  OBJECT_ID(N'[dbo].[{2}]') AND 
                                                  parent_object_id = OBJECT_ID(N'[dbo].[{1}]'))"
                                                 +"{0}BEGIN"
                                                 +"{0}      ALTER TABLE [dbo].[{1}]"
                                                 +"{0}      WITH CHECK CHECK CONSTRAINT {2} "
                                                 +"{0}      PRINT 'CHECK executed 
                                                            successfully on ''{2}''..'"
                                                 +"{0}END"
                                                 +"{0}ELSE"
                                                 +"{0}BEGIN"
                                                 +"{0}     PRINT 'The Foreign Key ''{2}'' 
                                                   does not exists ! Cant execute CHECK.'"
                                                 +"{0}END",
                                                 Environment.NewLine,
                                                 names[1],
                                                 item.Attributes["Name"].Value.StartsWith
                                                 ("FK_", StringComparison.CurrentCulture) ? 
                                                 item.Attributes["Name"].Value : 
                                                 ("FK_" + item.Attributes["Name"].Value))); 
                }
            }

            return script.Append(Environment.NewLine + "--Creating CHECK constraints 
                                 for the tables ends here--" ).ToString();
        } 

我们已经具备了从选定的 LightSwitch 应用程序创建完整数据库脚本所需的所有方法。当我们在弹出窗口中单击确定按钮时,我们需要调用这些方法。

 private void btnOk_Click(object sender, RoutedEventArgs e)
        {
            txtProg.Text = "";

            IList fileNames = (IList)lstData.SelectedItems;

            if (fileNames.Count > 0)
            {
                popupListDB.IsOpen = false;

                rtxtTables.Document.Blocks.Clear();

                txtProg.Text = "Generating script.....";

                foreach (DBDetails fileName in fileNames)
                {
                    XmlDocument d = new XmlDocument();

                    d.Load(new FileStream(fileName.DatabaseFilename, FileMode.Open, 
                                          FileAccess.Read, FileShare.Read));

                    string script = string.Empty;

                    XmlNodeList xmlNLEntityType = d.DocumentElement.GetElementsByTagName
                                               ("EntityType") // Getting the EntityType nodes 

                    GenerateEntityScript(xmlNLEntityType);    // Generating script for 
                                                              // creating table.

                    colScript.Add(strScript.ToString());      // Adding the partial script 
                                                              // to the string collection

                    XmlNodeList xmlNLAddAssociation = d.DocumentElement.GetElementsByTagName
                                        ("Association"); // Getting the Association nodes

                    if (xmlNLAddAssociation.Count > 0)
                    {
                        strScript = new StringBuilder(string.Empty);

                        GenerateConstraintScript(xmlNLAddAssociation,false); // Generating 
                                                                    // the Foreign Key script
                       
                        strScript = new StringBuilder(string.Empty);

                        colScript.Add(GenerateConstraintCheckScript(xmlNLAddAssociation));

                        foreach (var item in colScript)
                        {
                            rtxtTables.AppendText(item.ToString() + 
                                 Environment.NewLine); // Displaying the completely 
                                                       // generated script in the RichTextBox.
                        }
                    }
                }
                txtProg.Text = "Done.....";
            }
            else
            {
                txtError.Text = "Please select a database ! ";
            }
        }

就这样!我们创建了一个完整的工具,用于从 LightSwitch 应用程序生成 SQL Server 数据库脚本。

当您准备好代码和屏幕设计后,只需构建应用程序并开始使用它。

您可以从 Visual Studio Gallery 获取此工具。

编码愉快……

历史

  • 2012-01-24:添加了此项目的二进制文件 [EXE]
© . All rights reserved.