DataConnectionBase.cs 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Data.Common;
  6. using System.Drawing.Design;
  7. using System.Linq;
  8. using System.Reflection;
  9. using FastReport.Data.JsonConnection;
  10. using FastReport.Utils;
  11. namespace FastReport.Data
  12. {
  13. /// <summary>
  14. /// The base class for all data connection components such as <see cref="MsSqlDataConnection"/>.
  15. /// </summary>
  16. /// <example>This example shows how to add a new MS Access connection to the report.
  17. /// <code>
  18. /// Report report1;
  19. /// MsAccessDataConnection conn = new MsAccessDataConnection();
  20. /// conn.DataSource = @"c:\data.mdb";
  21. /// report1.Dictionary.Connections.Add(conn);
  22. /// conn.CreateAllTables();
  23. /// </code>
  24. /// </example>
  25. public abstract partial class DataConnectionBase : DataComponentBase, IParent
  26. {
  27. #region Fields
  28. private DataSet dataSet;
  29. private TableCollection tables;
  30. private bool isSqlBased;
  31. private bool canContainProcedures;
  32. private string connectionString;
  33. private string connectionStringExpression;
  34. private bool loginPrompt;
  35. private int commandTimeout;
  36. private string lastConnectionString;
  37. #endregion
  38. #region Properties
  39. /// <summary>
  40. /// Gets an internal <b>DataSet</b> object that contains all data tables.
  41. /// </summary>
  42. [Browsable(false)]
  43. public DataSet DataSet
  44. {
  45. get
  46. {
  47. if (dataSet == null)
  48. dataSet = CreateDataSet();
  49. return dataSet;
  50. }
  51. }
  52. /// <summary>
  53. /// Gets a collection of data tables in this connection.
  54. /// </summary>
  55. /// <remarks>
  56. /// To add a table to the connection, you must either create a new TableDataSource and add it
  57. /// to this collection or call the <see cref="CreateAllTables()"/> method which will add
  58. /// all tables available in the database.
  59. /// </remarks>
  60. [Browsable(false)]
  61. public TableCollection Tables
  62. {
  63. get { return tables; }
  64. }
  65. /// <summary>
  66. /// Gets or sets a connection string that contains all connection parameters.
  67. /// </summary>
  68. /// <remarks>
  69. /// <para>To modify some parameter of the connection, use respective
  70. /// <b>ConnectionStringBuilder</b> class.</para>
  71. /// <para><b>Security note:</b> the connection string may contain a user name/password.
  72. /// This information is stored in a report file. By default, it is crypted using the standard
  73. /// FastReport's password. Since FastReport's source code is available to anyone who paid for it,
  74. /// it may be insecure to use the standard password. For more security, you should use own
  75. /// password. To do this, specify it in the <b>Crypter.DefaultPassword</b> property.</para>
  76. /// </remarks>
  77. /// <example>This example demonstrates how to change a connection string:
  78. /// <code>
  79. /// OleDbConnectionStringBuilder builder = new OleDbConnectionStringBuilder(oleDbConnection1.ConnectionString);
  80. /// builder.PersistSecurityInfo = false;
  81. /// oleDbConnection1.ConnectionString = builder.ToString();
  82. /// </code>
  83. /// </example>
  84. [Category("Data")]
  85. [Browsable(true)]
  86. public string ConnectionString
  87. {
  88. get
  89. {
  90. if (Report != null && Report.IsRunning && !String.IsNullOrEmpty(ConnectionStringExpression))
  91. {
  92. string value = (string)Report.Calc(ConnectionStringExpression);
  93. if (!string.IsNullOrEmpty(value))
  94. return value;
  95. }
  96. return connectionString;
  97. }
  98. set
  99. {
  100. SetConnectionString(Crypter.DecryptString(value));
  101. }
  102. }
  103. /// <summary>
  104. /// Gets or sets an expression that returns a connection string.
  105. /// </summary>
  106. /// <remarks>
  107. /// Use this property to set the connection string dynamically.
  108. /// <para/>The recommended way to do this is to define a report parameter. You can do this in the
  109. /// "Data" window. Once you have defined the parameter, you can use it to pass a value
  110. /// to the connection. Set the <b>ConnectionStringExpression</b> property of the
  111. /// connection object to the report parameter's name (so it will look like [myReportParam]).
  112. /// To pass a value to the report parameter from your application, use the
  113. /// <see cref="Report.SetParameterValue"/> method.
  114. /// <note type="caution">
  115. /// Once you set value for this property, the <see cref="ConnectionString"/> property will be ignored
  116. /// when report is run.
  117. /// </note>
  118. /// </remarks>
  119. [Category("Data")]
  120. [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))]
  121. public string ConnectionStringExpression
  122. {
  123. get
  124. {
  125. return connectionStringExpression;
  126. }
  127. set { connectionStringExpression = value; }
  128. }
  129. /// <summary>
  130. /// Gets or sets a value indicates if this connection is SQL-based.
  131. /// </summary>
  132. [Browsable(false)]
  133. public bool IsSqlBased
  134. {
  135. get { return isSqlBased; }
  136. set { isSqlBased = value; }
  137. }
  138. /// <summary>
  139. /// Gets or sets a value indicates if this connection can contain procedures.
  140. /// </summary>
  141. [Browsable(false)]
  142. public bool CanContainProcedures
  143. {
  144. get { return canContainProcedures; }
  145. set { canContainProcedures = value; }
  146. }
  147. /// <summary>
  148. /// Gets or sets a value indicating whether a login dialog appears immediately before opening a connection.
  149. /// </summary>
  150. /// <remarks>
  151. /// Set <b>LoginPrompt</b> to <b>true</b> to provide login dialog when establishing a connection. If this
  152. /// property is <b>false</b> (by default), you should provide login information (user name and password)
  153. /// in the <see cref="ConnectionString"/> property. Though that property is stored in a crypted form,
  154. /// this may be insecure.
  155. /// <para/>Another way to pass login information to the connection is to use
  156. /// <see cref="ConnectionStringExpression"/> property that is bound to the report parameter. In that
  157. /// case you supply the entire connection string from your application.
  158. /// </remarks>
  159. [Category("Data")]
  160. [DefaultValue(false)]
  161. public bool LoginPrompt
  162. {
  163. get { return loginPrompt; }
  164. set { loginPrompt = value; }
  165. }
  166. /// <summary>
  167. /// Gets or sets the command timeout, in seconds.
  168. /// </summary>
  169. [Category("Data")]
  170. [DefaultValue(30)]
  171. public int CommandTimeout
  172. {
  173. get { return commandTimeout; }
  174. set { commandTimeout = value; }
  175. }
  176. internal List<XmlItem> TablesStructure { get; set; }
  177. #endregion
  178. #region Private Methods
  179. private void GetDBObjectNames(string name, List<string> list)
  180. {
  181. DataTable schema = null;
  182. DbConnection conn = GetConnection();
  183. try
  184. {
  185. OpenConnection(conn);
  186. schema = conn.GetSchema("Tables", new string[] { null, null, null, name });
  187. }
  188. finally
  189. {
  190. DisposeConnection(conn);
  191. }
  192. foreach (DataRow row in schema.Rows)
  193. {
  194. list.Add(row["TABLE_NAME"].ToString());
  195. }
  196. }
  197. private string PrepareSelectCommand(string selectCommand, string tableName, DbConnection connection)
  198. {
  199. if (String.IsNullOrEmpty(selectCommand))
  200. {
  201. selectCommand = "select * from " + QuoteIdentifier(tableName, connection);
  202. }
  203. return selectCommand;
  204. }
  205. private TableDataSource FindTableDataSource(DataTable table)
  206. {
  207. foreach (TableDataSource c in Tables)
  208. {
  209. if (c.Table == table)
  210. return c;
  211. }
  212. return null;
  213. }
  214. #endregion
  215. #region Protected Methods
  216. /// <inheritdoc/>
  217. protected override void Dispose(bool disposing)
  218. {
  219. base.Dispose(disposing);
  220. if (disposing)
  221. DisposeDataSet();
  222. }
  223. /// <summary>
  224. /// Initializes a <b>DataSet</b> instance.
  225. /// </summary>
  226. /// <returns>The <b>DataSet</b> object.</returns>
  227. /// <remarks>
  228. /// This method is used to support FastReport infrastructure. You don't need to use it.
  229. /// </remarks>
  230. protected virtual DataSet CreateDataSet()
  231. {
  232. DataSet dataSet = new DataSet();
  233. dataSet.EnforceConstraints = false;
  234. return dataSet;
  235. }
  236. /// <summary>
  237. /// Disposes a <b>DataSet</b>.
  238. /// </summary>
  239. /// <remarks>
  240. /// This method is used to support FastReport infrastructure. You don't need to use it.
  241. /// </remarks>
  242. protected void DisposeDataSet()
  243. {
  244. if (dataSet != null)
  245. dataSet.Dispose();
  246. dataSet = null;
  247. }
  248. /// <summary>
  249. /// Sets the connection string.
  250. /// </summary>
  251. /// <param name="value">New connection string.</param>
  252. /// <remarks>
  253. /// Use this method if you need to perform some actions when the connection string is set.
  254. /// </remarks>
  255. protected virtual void SetConnectionString(string value)
  256. {
  257. connectionString = value;
  258. }
  259. /// <summary>
  260. /// Gets a connection string that contains username and password specified.
  261. /// </summary>
  262. /// <param name="userName">User name.</param>
  263. /// <param name="password">Password.</param>
  264. /// <remarks>
  265. /// Override this method to pass login information to the connection. Typical implementation
  266. /// must get the existing <see cref="ConnectionString"/>, merge specified login information into it
  267. /// and return the new value.
  268. /// </remarks>
  269. protected virtual string GetConnectionStringWithLoginInfo(string userName, string password)
  270. {
  271. return ConnectionString;
  272. }
  273. #endregion
  274. #region IParent Members
  275. /// <inheritdoc/>
  276. public bool CanContain(Base child)
  277. {
  278. return child is TableDataSource;
  279. }
  280. /// <inheritdoc/>
  281. public void GetChildObjects(ObjectCollection list)
  282. {
  283. foreach (TableDataSource c in Tables)
  284. {
  285. list.Add(c);
  286. }
  287. }
  288. /// <inheritdoc/>
  289. public void AddChild(Base child)
  290. {
  291. Tables.Add(child as TableDataSource);
  292. }
  293. /// <inheritdoc/>
  294. public void RemoveChild(Base child)
  295. {
  296. Tables.Remove(child as TableDataSource);
  297. }
  298. /// <inheritdoc/>
  299. public int GetChildOrder(Base child)
  300. {
  301. // we don't need to handle database objects' order.
  302. return 0;
  303. }
  304. /// <inheritdoc/>
  305. public void SetChildOrder(Base child, int order)
  306. {
  307. // do nothing
  308. }
  309. /// <inheritdoc/>
  310. public void UpdateLayout(float dx, float dy)
  311. {
  312. // do nothing
  313. }
  314. #endregion
  315. #region Public Methods
  316. /// <summary>
  317. /// Fills the <see cref="Tables"/> collection with all tables available in the database.
  318. /// </summary>
  319. /// <remarks>
  320. /// This method does not read the table data; to do this, call the
  321. /// <see cref="TableDataSource.LoadData"/> method of each table.
  322. /// </remarks>
  323. public void CreateAllTables()
  324. {
  325. CreateAllTables(true);
  326. }
  327. /// <summary>
  328. /// Fills the <see cref="Tables"/> collection with all tables available in the database.
  329. /// </summary>
  330. /// <param name="initSchema">Set to <b>true</b> to initialize each table's schema.</param>
  331. public virtual void CreateAllTables(bool initSchema)
  332. {
  333. List<string> tableNames = new List<string>();
  334. tableNames.AddRange(GetTableNames());
  335. FilterTables(tableNames);
  336. // remove tables with tablename that does not exist in the connection.
  337. for (int i = 0; i < Tables.Count; i++)
  338. {
  339. TableDataSource table = Tables[i];
  340. // skip queries and procedures
  341. if (!String.IsNullOrEmpty(table.SelectCommand) || table is ProcedureDataSource)
  342. continue;
  343. bool found = false;
  344. foreach (string tableName in tableNames)
  345. {
  346. if (String.Compare(table.TableName, tableName, true) == 0)
  347. {
  348. found = true;
  349. break;
  350. }
  351. }
  352. // table name not found in actual tablenames. It may happen if we have edited the connection.
  353. if (!found)
  354. {
  355. table.Dispose();
  356. i--;
  357. }
  358. }
  359. int tableNumber = 0;
  360. // now create tables that are not created yet.
  361. foreach (string tableName in tableNames)
  362. {
  363. bool found = false;
  364. foreach (TableDataSource table in Tables)
  365. {
  366. if (String.Compare(table.TableName, tableName, true) == 0)
  367. {
  368. found = true;
  369. break;
  370. }
  371. }
  372. if (!found)
  373. {
  374. var table = new TableDataSource();
  375. // Enabled = false is used to ensure that when creating a connection,
  376. // none of the tables in the list of tables will be selected by default
  377. table.Enabled = false;
  378. string fixedTableName = tableName.Replace(".", "_").Replace("[", "").Replace("]", "").Replace("\"", "");
  379. if (Report != null)
  380. {
  381. table.Name = Report.Dictionary.CreateUniqueName(fixedTableName);
  382. table.Alias = Report.Dictionary.CreateUniqueAlias(table.Alias);
  383. }
  384. else
  385. table.Name = fixedTableName;
  386. table.TableName = tableName;
  387. table.Connection = this;
  388. if (TablesStructure != null)
  389. {
  390. table.Enabled = TablesStructure[tableNumber].Properties.Where(prop => prop.Key == "Enabled").Select(res => res.Value).First() == "true";
  391. }
  392. Tables.Add(table);
  393. tableNumber++;
  394. }
  395. }
  396. // init table schema
  397. if (initSchema)
  398. {
  399. foreach (TableDataSource table in Tables)
  400. {
  401. table.InitSchema();
  402. }
  403. }
  404. }
  405. /// <summary>
  406. /// Fills the <see cref="Tables"/> collection with all procedures available in the database.
  407. /// </summary>
  408. public virtual void CreateAllProcedures()
  409. {
  410. List<string> procedureNames = new List<string>();
  411. procedureNames.AddRange(GetProcedureNames());
  412. FilterTables(procedureNames);
  413. // remove procedures that does not exist in the connection.
  414. for (int i = 0; i < Tables.Count; i++)
  415. {
  416. var proc = Tables[i] as ProcedureDataSource;
  417. if (proc == null)
  418. continue;
  419. bool found = false;
  420. foreach (string procName in procedureNames)
  421. {
  422. if (String.Compare(proc.TableName, procName, true) == 0)
  423. {
  424. found = true;
  425. break;
  426. }
  427. }
  428. // proc name not found in actual proc names. It may happen if we have edited the connection.
  429. if (!found)
  430. {
  431. proc.Dispose();
  432. i--;
  433. }
  434. }
  435. // now create procedures that are not created yet.
  436. foreach (string procName in procedureNames)
  437. {
  438. bool found = false;
  439. foreach (TableDataSource table in Tables)
  440. {
  441. if (String.Compare(table.TableName, procName, true) == 0)
  442. {
  443. found = true;
  444. break;
  445. }
  446. }
  447. if (!found)
  448. {
  449. var proc = CreateProcedure(procName);
  450. proc.Enabled = false;
  451. string fixedName = procName.Replace(".", "_").Replace("[", "").Replace("]", "").Replace("\"", "");
  452. if (Report != null)
  453. {
  454. proc.Name = Report.Dictionary.CreateUniqueName(fixedName);
  455. proc.Alias = Report.Dictionary.CreateUniqueAlias(proc.Alias);
  456. }
  457. else
  458. proc.Name = fixedName;
  459. proc.TableName = procName;
  460. proc.Connection = this;
  461. Tables.Add(proc);
  462. }
  463. }
  464. }
  465. /// <summary>
  466. /// Create the stored procedure.
  467. /// </summary>
  468. public virtual TableDataSource CreateProcedure(string tableName)
  469. {
  470. ProcedureDataSource table = new ProcedureDataSource();
  471. table.Enabled = true;
  472. table.SelectCommand = tableName;
  473. DbConnection conn = GetConnection();
  474. try
  475. {
  476. OpenConnection(conn);
  477. var schemaParameters = conn.GetSchema("PROCEDURE_PARAMETRS", new string[] { null, null, tableName });
  478. foreach (DataRow row in schemaParameters.Rows)
  479. {
  480. ParameterDirection direction = ParameterDirection.Input;
  481. switch (row["PARAMETER_MODE"].ToString())
  482. {
  483. case "IN":
  484. direction = ParameterDirection.Input;
  485. table.Enabled = false;
  486. break;
  487. case "INOUT":
  488. direction = ParameterDirection.InputOutput;
  489. table.Enabled = false;
  490. break;
  491. case "OUT":
  492. direction = ParameterDirection.Output;
  493. break;
  494. }
  495. table.Parameters.Add(new ProcedureParameter()
  496. {
  497. Name = row["PARAMETER_NAME"].ToString(),
  498. DataType = Convert.ToInt32(row["DATA_TYPE"].ToString()),
  499. Direction = direction
  500. });
  501. }
  502. }
  503. finally
  504. {
  505. DisposeConnection(conn);
  506. }
  507. return table;
  508. }
  509. /// <summary>
  510. /// Creates the relations between tables. Applies to XmlDataConnection only.
  511. /// </summary>
  512. public virtual void CreateRelations()
  513. {
  514. if (Report != null)
  515. {
  516. foreach (DataRelation relation in DataSet.Relations)
  517. {
  518. Relation rel = new Relation();
  519. rel.Name = relation.RelationName;
  520. rel.ParentDataSource = FindTableDataSource(relation.ParentTable);
  521. rel.ChildDataSource = FindTableDataSource(relation.ChildTable);
  522. string[] parentColumns = new string[relation.ParentColumns.Length];
  523. string[] childColumns = new string[relation.ChildColumns.Length];
  524. for (int i = 0; i < relation.ParentColumns.Length; i++)
  525. {
  526. parentColumns[i] = relation.ParentColumns[i].Caption;
  527. }
  528. for (int i = 0; i < relation.ChildColumns.Length; i++)
  529. {
  530. childColumns[i] = relation.ChildColumns[i].Caption;
  531. }
  532. rel.ParentColumns = parentColumns;
  533. rel.ChildColumns = childColumns;
  534. if (Report.Dictionary.Relations.FindEqual(rel) == null)
  535. Report.Dictionary.Relations.Add(rel);
  536. else
  537. rel.Dispose();
  538. }
  539. }
  540. }
  541. /// <summary>
  542. /// Gets an array of table names available in the database.
  543. /// </summary>
  544. /// <returns>An array of strings.</returns>
  545. public virtual string[] GetTableNames()
  546. {
  547. List<string> list = new List<string>();
  548. GetDBObjectNames("TABLE", list);
  549. GetDBObjectNames("VIEW", list);
  550. return list.ToArray();
  551. }
  552. /// <summary>
  553. /// Gets an array of table names available in the database.
  554. /// </summary>
  555. /// <returns>An array of strings.</returns>
  556. public virtual string[] GetProcedureNames()
  557. {
  558. List<string> list = new List<string>();
  559. DataTable schema = null;
  560. DbConnection conn = GetConnection();
  561. if (conn != null)
  562. {
  563. try
  564. {
  565. OpenConnection(conn);
  566. schema = conn.GetSchema("PROCEDURE");
  567. foreach (DataRow row in schema.Rows)
  568. {
  569. list.Add(row["PROCEDURE_NAME"].ToString());
  570. }
  571. }
  572. finally
  573. {
  574. DisposeConnection(conn);
  575. }
  576. }
  577. return list.ToArray();
  578. }
  579. /// <summary>
  580. /// Returns a type of connection.
  581. /// </summary>
  582. /// <returns><b>Type</b> instance.</returns>
  583. /// <remarks>
  584. /// You should override this method if you developing a new connection component.
  585. /// <para/>If your connection component does not use data connection, you need to override
  586. /// the <see cref="FillTableSchema"/> and <see cref="FillTableData"/> methods instead.
  587. /// </remarks>
  588. /// <example>Here is the example of this method implementation:
  589. /// <code>
  590. /// public override Type GetConnectionType()
  591. /// {
  592. /// return typeof(OleDbConnection);
  593. /// }
  594. /// </code>
  595. /// </example>
  596. public virtual Type GetConnectionType()
  597. {
  598. return null;
  599. }
  600. /// <summary>
  601. /// Returns a connection object.
  602. /// </summary>
  603. /// <returns>The <b>DbConnection</b> instance.</returns>
  604. /// <remarks>Either creates a new <b>DbConnection</b> instance of type provided by the
  605. /// <see cref="GetConnectionType"/> method, or returns the application connection if set
  606. /// in the Config.DesignerSettings.ApplicationConnection.</remarks>
  607. public DbConnection GetConnection()
  608. {
  609. Type connectionType = GetConnectionType();
  610. if (connectionType != null)
  611. {
  612. DbConnection connection = GetDefaultConnection();
  613. if (connection != null)
  614. return connection;
  615. // create a new connection object
  616. connection = Activator.CreateInstance(connectionType) as DbConnection;
  617. connection.ConnectionString = ConnectionString;
  618. return connection;
  619. }
  620. return null;
  621. }
  622. /// <summary>
  623. /// Opens a specified connection object.
  624. /// </summary>
  625. /// <param name="connection">Connection to open.</param>
  626. /// <remarks>
  627. /// Use this method to open a connection returned by the <see cref="GetConnection"/> method.
  628. /// <para/>This method displays a login dialog if your connection has the <see cref="LoginPrompt"/>
  629. /// property set to <b>true</b>. Once you have entered an user name and password in
  630. /// this dialog, it will remeber the entered values and will not used anymore in this report session.
  631. /// </remarks>
  632. public void OpenConnection(DbConnection connection)
  633. {
  634. if (connection.State == ConnectionState.Open)
  635. return;
  636. if (!String.IsNullOrEmpty(lastConnectionString))
  637. {
  638. // connection string is already provided, use it and skip other logic.
  639. connection.ConnectionString = lastConnectionString;
  640. }
  641. else
  642. {
  643. // try the global DatabaseLogin event
  644. string oldConnectionString = ConnectionString;
  645. DatabaseLoginEventArgs e = new DatabaseLoginEventArgs(ConnectionString);
  646. Config.ReportSettings.OnDatabaseLogin(this, e);
  647. // that event may do the following:
  648. // - modify the ConnectionString
  649. // - modify the username/password
  650. // - there is no event handler attached to the event, so it does nothing.
  651. if (oldConnectionString != e.ConnectionString)
  652. {
  653. // the connection string was modified. Set the FLastConnectionString to use it next time silently.
  654. lastConnectionString = e.ConnectionString;
  655. }
  656. else
  657. {
  658. if (!String.IsNullOrEmpty(e.UserName) || !String.IsNullOrEmpty(e.Password))
  659. {
  660. // the username/password was modified. Get new connection string
  661. lastConnectionString = GetConnectionStringWithLoginInfo(e.UserName, e.Password);
  662. }
  663. else if (LoginPrompt)
  664. {
  665. ShowLoginForm(lastConnectionString);
  666. }
  667. }
  668. // update the connection if it's not done yet
  669. if (!String.IsNullOrEmpty(lastConnectionString))
  670. connection.ConnectionString = lastConnectionString;
  671. }
  672. connection.Open();
  673. Config.ReportSettings.OnAfterDatabaseLogin(this, new AfterDatabaseLoginEventArgs(connection));
  674. }
  675. /// <summary>
  676. /// Disposes a connection.
  677. /// </summary>
  678. /// <param name="connection">The connection to dispose.</param>
  679. public void DisposeConnection(DbConnection connection)
  680. {
  681. if (ShouldNotDispose(connection))
  682. return;
  683. if (connection != null)
  684. connection.Dispose();
  685. }
  686. /// <summary>
  687. /// Returns a <see cref="DbDataAdapter"/> object that is specific to this connection.
  688. /// </summary>
  689. /// <param name="selectCommand">The SQL command used to fetch a table data rows.</param>
  690. /// <param name="connection">The connection object.</param>
  691. /// <param name="parameters">The select command parameters.</param>
  692. /// <returns>The <b>DbDataAdapter</b> object.</returns>
  693. /// <remarks>
  694. /// You should override this method if you are developing a new connection component. In this method,
  695. /// you need to create the adapter and set its <b>SelectCommand</b>'s parameters.
  696. /// <para/>If your connection does not use data adapter, you need to override
  697. /// the <see cref="FillTableSchema"/> and <see cref="FillTableData"/> methods instead.
  698. /// </remarks>
  699. /// <example>Here is the example of this method implementation:
  700. /// <code>
  701. /// public override DbDataAdapter GetAdapter(string selectCommand, DbConnection connection,
  702. /// CommandParameterCollection parameters)
  703. /// {
  704. /// OleDbDataAdapter adapter = new OleDbDataAdapter(selectCommand, connection as OleDbConnection);
  705. /// foreach (CommandParameter p in parameters)
  706. /// {
  707. /// OleDbParameter parameter = adapter.SelectCommand.Parameters.Add(p.Name, (OleDbType)p.DataType, p.Size);
  708. /// parameter.Value = p.Value;
  709. /// }
  710. /// return adapter;
  711. /// }
  712. /// </code>
  713. /// </example>
  714. public virtual DbDataAdapter GetAdapter(string selectCommand, DbConnection connection,
  715. CommandParameterCollection parameters)
  716. {
  717. return null;
  718. }
  719. /// <summary>
  720. /// Gets the type of parameter that is specific to this connection.
  721. /// </summary>
  722. /// <returns>The parameter's type.</returns>
  723. /// <remarks>
  724. /// This property is used in the report designer to display available data types when you edit the
  725. /// connection parameters. For example, the type of OleDbConnection parameter is a <b>OleDbType</b>.
  726. /// </remarks>
  727. public virtual Type GetParameterType()
  728. {
  729. return null;
  730. }
  731. /// <summary>
  732. /// Quotes the specified DB identifier such as table name or column name.
  733. /// </summary>
  734. /// <param name="value">Identifier to quote.</param>
  735. /// <param name="connection">The opened DB connection.</param>
  736. /// <returns>The quoted identifier.</returns>
  737. public abstract string QuoteIdentifier(string value, DbConnection connection);
  738. /// <summary>
  739. /// Fills the table schema.
  740. /// </summary>
  741. /// <param name="table">DataTable to fill.</param>
  742. /// <param name="selectCommand">The SQL select command.</param>
  743. /// <param name="parameters">SQL parameters.</param>
  744. /// <remarks>
  745. /// Usually you don't need to use this method. Internally it uses the <see cref="GetConnection"/> and
  746. /// <see cref="GetAdapter"/> methods to fill the table schema. If you create own connection component
  747. /// that does not use nor connection or adapter, then you need to override this method.
  748. /// </remarks>
  749. public virtual void FillTableSchema(DataTable table, string selectCommand,
  750. CommandParameterCollection parameters)
  751. {
  752. DbConnection conn = GetConnection();
  753. try
  754. {
  755. OpenConnection(conn);
  756. TableDataSource dataSource = FindTableDataSource(table);
  757. // prepare select command
  758. if (!(dataSource is ProcedureDataSource))
  759. selectCommand = PrepareSelectCommand(selectCommand, table.TableName, conn);
  760. // read the table schema
  761. using (DbDataAdapter adapter = GetAdapter(selectCommand, conn, parameters))
  762. {
  763. adapter.SelectCommand.CommandType = dataSource is ProcedureDataSource ? CommandType.StoredProcedure : CommandType.Text;
  764. adapter.SelectCommand.CommandTimeout = CommandTimeout;
  765. adapter.FillSchema(table, SchemaType.Source);
  766. }
  767. foreach (Column column in dataSource.Columns)
  768. {
  769. if (!column.Enabled)
  770. table.Columns.Remove(column.Name);
  771. }
  772. }
  773. finally
  774. {
  775. DisposeConnection(conn);
  776. }
  777. }
  778. /// <summary>
  779. /// Fills the table data.
  780. /// </summary>
  781. /// <param name="table">DataTable to fill.</param>
  782. /// <param name="selectCommand">The SQL select command.</param>
  783. /// <param name="parameters">SQL parameters.</param>
  784. /// <remarks>
  785. /// Usually you don't need to use this method. Internally it uses the <see cref="GetConnection"/> and
  786. /// <see cref="GetAdapter"/> methods to fill the table data. If you create own connection component
  787. /// that does not use nor connection or adapter, then you need to override this method.
  788. /// </remarks>
  789. public virtual void FillTableData(DataTable table, string selectCommand,
  790. CommandParameterCollection parameters)
  791. {
  792. DbConnection conn = GetConnection();
  793. try
  794. {
  795. OpenConnection(conn);
  796. TableDataSource dataSource = FindTableDataSource(table);
  797. // prepare select command
  798. if (!(dataSource is ProcedureDataSource))
  799. selectCommand = PrepareSelectCommand(selectCommand, table.TableName, conn);
  800. // read the table
  801. using (DbDataAdapter adapter = GetAdapter(selectCommand, conn, parameters))
  802. {
  803. adapter.SelectCommand.CommandType = dataSource is ProcedureDataSource ? CommandType.StoredProcedure : CommandType.Text;
  804. adapter.SelectCommand.CommandTimeout = CommandTimeout;
  805. table.Clear();
  806. adapter.Fill(table);
  807. // copy output parameter values
  808. foreach (DbParameter dp in adapter.SelectCommand.Parameters)
  809. {
  810. if (dp.Direction != ParameterDirection.Input)
  811. {
  812. foreach (CommandParameter cp in parameters)
  813. {
  814. if (cp.Name == dp.ParameterName)
  815. {
  816. cp.Value = dp.Value;
  817. break;
  818. }
  819. }
  820. }
  821. }
  822. }
  823. }
  824. finally
  825. {
  826. DisposeConnection(conn);
  827. }
  828. }
  829. /// <summary>
  830. /// Creates table.
  831. /// For internal use only.
  832. /// </summary>
  833. public virtual void CreateTable(TableDataSource source)
  834. {
  835. if (source.Table == null)
  836. {
  837. source.Table = new DataTable(source.TableName);
  838. DataSet.Tables.Add(source.Table);
  839. }
  840. }
  841. internal virtual void FillTable(TableDataSource source)
  842. {
  843. if (source.Table != null)
  844. {
  845. bool parametersChanged = false;
  846. foreach (CommandParameter par in source.Parameters)
  847. {
  848. object value = par.Value;
  849. if (!Object.Equals(value, par.LastValue))
  850. {
  851. par.LastValue = value;
  852. parametersChanged = true;
  853. }
  854. }
  855. if (source.ForceLoadData || source.Table.Rows.Count == 0 || parametersChanged)
  856. FillTableData(source.Table, source.SelectCommand, source.Parameters);
  857. }
  858. }
  859. /// <summary>
  860. /// Deletes table.
  861. /// For internal use only.
  862. /// </summary>
  863. public virtual void DeleteTable(TableDataSource source)
  864. {
  865. if (source.Table != null)
  866. {
  867. if (dataSet != null)
  868. dataSet.Tables.Remove(source.Table);
  869. source.Table.Dispose();
  870. source.Table = null;
  871. }
  872. }
  873. /// <summary>
  874. /// Clone table.
  875. /// For internal use only.
  876. /// </summary>
  877. public virtual void Clone()
  878. {
  879. XmlItem item = new XmlItem();
  880. using (FRWriter writer = new FRWriter(item))
  881. {
  882. writer.SerializeTo = SerializeTo.Clipboard;
  883. writer.BlobStore = new BlobStore(false);
  884. writer.Write(this);
  885. }
  886. using (FRReader reader = new FRReader(Report, item))
  887. {
  888. reader.DeserializeFrom = SerializeTo.Clipboard;
  889. reader.BlobStore = new BlobStore(false);
  890. var connection = Activator.CreateInstance(this.GetType()) as DataConnectionBase;
  891. connection.Parent = this.Parent;
  892. connection.SetReport(Report);
  893. reader.Read(connection);
  894. connection.CreateUniqueName();
  895. foreach (TableDataSource table in connection.Tables)
  896. table.CreateUniqueName();
  897. Report.Dictionary.AddChild(connection);
  898. }
  899. }
  900. protected void CreateUniqueNames(DataConnectionBase copyTo)
  901. {
  902. int i = 1;
  903. string s;
  904. do
  905. {
  906. s = this.Alias + i.ToString();
  907. i++;
  908. }
  909. while (Report.Dictionary.FindByAlias(s) != null);
  910. copyTo.Alias = s;
  911. copyTo.Name = s;
  912. }
  913. /// <inheritdoc/>
  914. public override void Serialize(FRWriter writer)
  915. {
  916. writer.ItemName = ClassName;
  917. if (Name != "")
  918. writer.WriteStr("Name", Name);
  919. if (Restrictions != Restrictions.None)
  920. writer.WriteValue("Restrictions", Restrictions);
  921. if (!String.IsNullOrEmpty(ConnectionString))
  922. writer.WriteStr("ConnectionString", Crypter.EncryptString(ConnectionString));
  923. if (!String.IsNullOrEmpty(ConnectionStringExpression))
  924. writer.WriteStr("ConnectionStringExpression", ConnectionStringExpression);
  925. if (LoginPrompt)
  926. writer.WriteBool("LoginPrompt", true);
  927. if (CommandTimeout != 30)
  928. writer.WriteInt("CommandTimeout", CommandTimeout);
  929. SerializeDesignExt(writer);
  930. if (writer.SaveChildren)
  931. {
  932. foreach (TableDataSource c in Tables)
  933. {
  934. if (c.Enabled)
  935. writer.Write(c);
  936. }
  937. }
  938. }
  939. /// <inheritdoc/>
  940. public override string[] GetExpressions()
  941. {
  942. return new string[] { ConnectionStringExpression };
  943. }
  944. partial void SetConnectionStringBrowsableAttribute();
  945. #endregion
  946. /// <summary>
  947. /// Initializes a new instance of the <see cref="DataConnectionBase"/> class with default settings.
  948. /// </summary>
  949. public DataConnectionBase()
  950. {
  951. tables = new TableCollection(this);
  952. connectionString = "";
  953. connectionStringExpression = "";
  954. IsSqlBased = true;
  955. canContainProcedures = false;
  956. commandTimeout = 30;
  957. SetFlags(Flags.CanEdit, true);
  958. SetConnectionStringBrowsableAttribute();
  959. }
  960. partial void SerializeDesignExt(FRWriter writer);
  961. }
  962. }