|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe SqlMetal tool is useful to generate C# or VB.NET objects mapped on SQL Server database. It provides classes with members and properties mapped on table and view columns. It reflects the relationships between tables. It provides also a class derived from base class DataContext which maps, on request, functions and stored procedures, making difference between table functions and scalar functions and between stored procedures which returns a rowset and those who perform only operations such as insert, update and delete. Another feature is the creation of the XML map used to store separately database objects definitions. The application DLinqEntityGenerator generates objects in a similar way as SqlMetal tool does, but it is built as an add-in on VS.NET and have more options like generating output files for each database object and creating a C# or VB.NET project attached to the current solution. It is not a tool to replace SqlMetal, but a wizard-style option to generate DLinq classes.
The idea started from the previous article DACBuilder – Data Access objects generation tool based on XML and XSL templates transformation, which have used XSL transformations to generate simple objects mapped on different data sources. It is much easier to use XML and XSL transformations, because in case of modifications and later features you only have to modify the template without changing the code and recompiling applications. Another reason is that you can customize the templates, using XSL parameters (using MetadataDatabase objects metadata definitions are provided using INFORMATION_SCHEMA views and FOR XML feature available on SQL Server. The objects called are:
The The The The The The wizard form (frmWizard) and DLinqEntitiesEventArgs classThe form used to collect information about the database objects and the entities which will map them is called When the user click Next or Back buttons, the The first step is the connection information. The server name or IP address, user name and password and a database name must be provided in order to pass to the next step. If the server name, user name and password are correct, when you "drop down" the databases combo box, it will be filled with all the databases existing on the specified server. ServerConnection cnn = new ServerConnection(txtServer.Text, txtUser.Text,
txtPassword.Text);
cnn.Connect();
Server srvr = new Server(cnn);
DatabaseCollection oColl = srvr.Databases;
The
The second step is the database objects selection step. These objects are stored in a public struct DBObject
{
public DBObjectTypes ObjectType;
public string ObjectName;
public string ObjectFriendlyName;
public string RoutineDataType; // null for tables, views, stored
// procedures; sql data type for scalar
// function; TABLE for inline and multi-
// statement table-valued functions
}
You can not go to the next step if at least one database object is not checked.
The third step consists in supplementary options, like the project name, the folder you want to store the resulting files, the generated code language, the XML map file and mapping option on generated classes. By default, the application creates the project in the selected language, but this could be disabled. In the case of a project creation, the selected folder must be empty. The project will be added to the current solution. If no solution is available, a new one will be created. Optionally, you can generate all the classes in a single file.
The summary after the third step recaps the information collected offering a short description for every setting.
The public delegate void DLinqEntitiesEventHandler(object sender,
DLinqEntitiesEventArgs e);
...
public event DLinqEntitiesEventHandler WizardFinished;
When the wizard finishes its job, all the collected information is stored in an object of private void OnWizardFinished()
{
ArrayList dbObjects = new ArrayList();
foreach(TreeNode root in tvDBObjects.Nodes)
{
foreach(DBObjectTreeNode tn in root.Nodes)
{
if(tn.Checked)
{
dbObjects.Add(tn.DBObj);
}
}
}
string dbName = cboDatabases.SelectedIndex >= 0 ?
DBHelper.CreateFriendlyName(
cboDatabases.SelectedItem.ToString()) : "";
LanguageTypes lt = rbCSharp.Checked ? LanguageTypes.CSharp :
LanguageTypes.VisualBasic;
string projectName = chkDontCreateProject.Checked ? null :
DBHelper.CreateFriendlyName(txtProjectName.Text);
string path = txtFolder.Text;
DLinqEntitiesEventArgs e = new DLinqEntitiesEventArgs(cnnString, dbName,
dbObjects, chkUseOneOnlyFile.Checked, lt,
path, projectName, txtXMLFile.Text,
chkUseMapping.Enabled &&
chkUseMapping.Checked);
if(WizardFinished != null)
{
WizardFinished(this, e);
}
}
The
The DBHelper classThe
All methods except The public string Transform(XmlReader xmlRdr, XmlDocument xslTemplateDoc)
{
string strXML = string.Empty;
try
{
XmlNamespaceManager nsmgr
= new XmlNamespaceManager(xslTemplateDoc.NameTable);
nsmgr.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
XmlUrlResolver resolver = new XmlUrlResolver();
XslCompiledTransform trans = new XslCompiledTransform();
trans.Load(xslTemplateDoc, null, resolver);
TextWriter writer = new StringWriter();
XmlTextWriter xmlWriter = new XmlTextWriter(writer);
xmlWriter.Formatting = Formatting.Indented;
trans.Transform(xmlRdr, xmlWriter);
strXML = writer.ToString();
}
catch(Exception ex)
{
strXML = "<errors>";
while(ex != null)
{
strXML += "<error>";
strXML += ex.Message;
strXML += "</error>";
ex = ex.InnerException;
}
strXML += "</errors>";
}
return strXML;
}
The First, the SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
if(rdr.FieldCount > 0)
{
...
return ProcedureTypes.Rowset;
}
else
{
...
return ProcedureTypes.ReturnValue;
}
If the procedure returns a rowset, metadata information is found in the schema table obtained using If some SQL errors occur, the return type is a public enum ProcedureTypes
{
UnableToHandleSQL = 0
, UnableToHandleOther = 1
, Rowset = 2
, ReturnValue = 3
}
Add-In Connect classThe public void Exec(string commandName, vsCommandExecOption executeOption,
ref object varIn, ref object varOut, ref bool handled)
This method instantiates an object of frmWizard frm = new frmWizard();
frm.WizardFinished += new DLinqEntitiesEventHandler(frm_WizardFinished);
DialogResult dr = frm.ShowDialog();
The handler private DTE2 _applicationObject;
If the project creation is selected, it uses specific methods to create the project in the selected language and to add references to if(e.CreateProject)
{
prjPath = e.Path;
prjTemplatePath = sln.GetProjectTemplate("ClassLibrary.zip",
e.LanguageType.ToString());
sln.AddFromTemplate(prjTemplatePath, prjPath, e.ProjectName, false);
prj = sln.Projects.Item(sln.Projects.Count);
ProjectItem item = sln.FindProjectItem(prjPath + "\\Class1." +
e.FileExtension);
item.Remove();
itemTemplatePath = sln.GetProjectItemTemplate("Class.zip",
e.LanguageType.ToString());
VSProject thisPrj = (VSProject)prj.Object;
thisPrj.References.Add("System.Data.DLinq");
thisPrj.References.Add("System.Query");
_applicationObject.StatusBar.Progress(true, "Creating project " +
e.ProjectName + "." + e.ProjectExtension + "...",
2, 100);
}
For each database object, depending on the settings, the application get the structure of the database object, performs the code generation, if it must be saved in its file, it is added to a xmlDoc = dbH.GetTableColumns(dbObj.ObjectName);
if(e.UseOneOnlyFile)
{
sCode += dbH.Transform(xmlDoc, xslDoc);
}
else
{
sCode = dbH.Transform(xmlDoc, xslDoc);
sw = new StreamWriter(e.Path + @"\" + dbObj.ObjectFriendlyName + "." +
e.FileExtension);
sw.Write(sCode);
sw.Close();
if(e.CreateProject)
{
prj = sln.Projects.Item(sln.Projects.Count);
prj.ProjectItems.AddFromFile(e.Path + @"\" + dbObj.ObjectFriendlyName
+ "." + e.FileExtension);
}
}
After all selected database objects are transformed, the application generates the main class derived from If the user asks the XML map creation, a XML file with database objects definitions is created at specified location. Also, if the user asks that the generated classes to use mapping, the custom attributes containing the database objects definitions will not be added to the classes. The XSL stylesheets and DLinq classesThe table and views classes, as TABLE functions and rowset stored procedures classes, are mapped using table_CSharp.xsl and table_VisualBasic.xsl stylesheets. They contain specific language syntax and templates for generating class definition, constructors, private members and public properties mapped on the columns, private members and public properties for relationships ( The main class derived from All the stylesheets include the stylesheet called type.xsl, used to map SQL types to C# or VB.NET primitive types and to include default values for these languages. The execute_procedure.xsl stylesheet is used to generate a valid T-SQL statement for execute stored procedures with default values for parameters, useful to distinguish between rowset stored procedures and stored procedures which return values. The map.xsl is used to obtain the XML map file which contain the database objects metadata definitions. The output is very similar to the one created by the SqlMetal tool, except [Table(Name="Customers")]
public partial class Customer
{
private int xCustomerID;
[Column(Storage="xCustomerID", DBType="int NOT NULL IDENTITY",
Id=true, AutoGen=true)]
public CustomerID
{
get
{
return xCustomerID;
}
set
{
if(this.xCustomerID != value)
{
this.OnPropertyChanging("CustomerID");
this.xCustomerID = value;
this.OnPropertyChanged("CusotmerID");
}
}
}
}
The [Table(Name="Customers")]
public class Customer
{
[Column(Id=true)]
public string CustomerID;
...
private EntitySet<Order> _Orders;
[Association(Storage="_Orders", OtherKey="CustomerID")]
public EntitySet<Order> Orders
{
get { return this._Orders; }
set { this._Orders.Assign(value); }
}
}
The child entity must have a reference to the parent, using the [Table(Name="Orders")]
public class Order
{
[Column]
public string CustomerID;
private EntityRef<Customer> _Customer;
[Association(Storage="_Customer", ThisKey="CustomerID")]
public Customer Customer
{
get { return this._Customer.Entity; }
set { this._Customer.Entity = value; }
}
}
The class derived from public partial class Northwind : DataContext
{
public Table<Customer> Customers;
public Table<Order> Orders;
public Northwind(string connection): base(connection) {}
[StoredProcedure(Name="CustOrderHist")]
public StoredProcedureResult<CustOrderHistResult>
CustOrderHist([Parameter(Name="CustomerID")] string customerID)
{
return this.ExecuteStoredProcedure<CustOrderHistResult>
(((MethodInfo)(MethodInfo.GetCurrentMethod())), customerID);
}
[StoredProcedure(Name="InsertRegion")]
public int InsertRegion([Parameter(Name="RegionID", DBType="int")]
System.Nullable
A simple usage of the generated classes, as the LINQ documentation shows: NorthwindDataContext db
= new NorthwindDataContext("c:\\northwind\\northwnd.mdf");
var q =
from c in db.Customers
where c.City == "London"
select c;
foreach (var cust in q)
Console.WriteLine("id = {0}, City = {1}",cust.CustomerID, cust.City);
Using the applicationFirst of all, you must run the scripts for tables, views, functions and stored procedures used to select metadata information. The database you want to be mapped over the DLinq entities must have all these objects created (available in the scripts folder). To make DLinqEntityGenerator add-in application available to VS.NET, the assembly and the .AddIn file must be copied in one of the directories specified in "Add-in File Paths" option (Tools - > Options -> Add-In/Macros Security). Also, the "Allow Add-in components to load" option must be checked. The Templates folder containing the XSL stylesheets used to generate classes must be copied in the selected directory. If you encounter difficulties running the DLinqEntityGenerator Add-in, copy in the same directory the additional assemblies provided with DlinqEntityGenerator.dll file. After the files(s) copy process, when you start VS.NET, in the Tools menu you will find at the beginning a new command called "DLinqEntityGenerator". The DLinqEntityGenerator application should be available now as a new tool in VS.NET environment.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||