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






4.25/5 (3投票s)
一个用于从 LightSwitch 内部数据库生成 SQL Server 数据库脚本的工具
引言
在本文中,我们将介绍一个用于从 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
节点有两个子节点,分别是 Key
和 Property
。Key
节点描述主键,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]