1489e3b65132b2eb4e7d2e787db37e486d5e43c8
FAQ/many-to-many-database-join.md
| ... | ... | @@ -0,0 +1,149 @@ |
| 1 | +#Creating a many-to-many database join and hiding the middle table as a datasource |
|
| 2 | + |
|
| 3 | +[[_TOC_]] |
|
| 4 | + |
|
| 5 | +##Question |
|
| 6 | + |
|
| 7 | +How do I join two tables in Izenda that are in a many-to-many relationship with each other? I have a connecting table for the relationship, but I don't want the user to see the connecting table listed as a datasource. |
|
| 8 | + |
|
| 9 | +##Answer |
|
| 10 | + |
|
| 11 | +In order to achieve the results required for this scenario, some understanding about how Izenda handles constraints is required. This tutorial will deal with C# and VB as well as manipulating objects that Izenda normally controls automatically. The solution will vary based on your application's specific needs. However, here is what the process will require in its most basic form. |
|
| 12 | + |
|
| 13 | +For this tutorial, all changes being made will exist in your CustomAdHocConfig class that is normally contained inside the Global.asax. |
|
| 14 | + |
|
| 15 | +First, we need to modify the [[InitializeReporting()|/FAQ/InitializeReporting]] method: |
|
| 16 | + |
|
| 17 | +``` csharp |
|
| 18 | +public static void InitializeReporting() { |
|
| 19 | + //Check to see if we've already initialized. |
|
| 20 | + if (HttpContext.Current.Session == null || HttpContext.Current.Session["ReportingInitialized"] != null) |
|
| 21 | + return; |
|
| 22 | + //Initialize System |
|
| 23 | + AdHocSettings.LicenseKey = "INSERT_YOUR_LICENSE_KEY_HERE"; |
|
| 24 | + AdHocSettings.SqlServerConnectionString = @"INSERT_YOUR_CONNECTION_STRING_HERE"; |
|
| 25 | + {...} |
|
| 26 | + AdHocContext.Driver.AddConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]"); |
|
| 27 | + HttpContext.Current.Session["ReportingInitialized"] = true; |
|
| 28 | +} |
|
| 29 | +``` |
|
| 30 | + |
|
| 31 | +``` visualbasic |
|
| 32 | +Public Shared Sub InitializeReporting() |
|
| 33 | + 'Check to see if we've already initialized. |
|
| 34 | + If (HttpContext.Current.Session Is Nothing OrElse (Not (HttpContext.Current.Session("ReportingInitialized") Is Nothing))) Then |
|
| 35 | + Return |
|
| 36 | + End If |
|
| 37 | + 'Initialize System |
|
| 38 | + AdHocSettings.LicenseKey = "INSERT_LICENSE_KEY_HERE" |
|
| 39 | + AdHocSettings.SqlServerConnectionString = "INSERT_CONNECTION_STRING_HERE" |
|
| 40 | + {...} |
|
| 41 | + AdHocContext.Driver.AddConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]") |
|
| 42 | + HttpContext.Current.Session("ReportingInitialized") = True |
|
| 43 | +End Sub |
|
| 44 | +``` |
|
| 45 | +The statement critical to the [[InitializeReporting()|/FAQ/InitializeReporting]] method is the AddConstraint method call. Here, we set up a relationship between two columns in our two tables with a many-to-many relationship. At this point, the connector table has not entered the equation. This constraint must exist in order to select the two tables with a many-to-many relationship on the datasources tab. |
|
| 46 | + |
|
| 47 | +_*NOTE:* The two columns in the fake constraint MUST have the same datatype._ |
|
| 48 | + |
|
| 49 | +Next, we modify the [[PreExecuteReportSet()|/FAQ/PreExecuteReportSet]] method. |
|
| 50 | + |
|
| 51 | +``` csharp |
|
| 52 | +public override void PreExecuteReportSet(ReportSet reportSet) { |
|
| 53 | + base.PreExecuteReportSet(reportSet); |
|
| 54 | + AdHocContext.Driver.RemoveConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]"); //temporarily remove the constraint |
|
| 55 | + if(reportSet.JoinedTables.Count > 0) //check if we have selected multiple tables |
|
| 56 | + { |
|
| 57 | + IEnumerable<JoinedTable> jts = (from JoinedTable jt in reportSet.JoinedTables |
|
| 58 | + where (jt.DbTable.Name.Contains("Orders") || jt.DbTable.Name.Contains("Suppliers")) || |
|
| 59 | + (jt.RightConditionTable.Contains("Orders") || jt.RightConditionTable.Contains("Suppliers")) |
|
| 60 | + select jt); //use LINQ to check if we selected the tables we want to define the relationship for |
|
| 61 | + JoinedTable middleJT = new JoinedTable(){ |
|
| 62 | + TableName = "[dbo].[testcon]" //This is our connector table |
|
| 63 | + }; |
|
| 64 | + if(jts.Count() == 2) //We have both tables that we want |
|
| 65 | + { |
|
| 66 | + //we know the collection only contains the items we want to remove, so it is safe to refer to them by index. |
|
| 67 | + reportSet.JoinedTables.Remove(jts.ToArray()[1]); |
|
| 68 | + reportSet.JoinedTables.Remove(jts.ToArray()[0]); |
|
| 69 | + //if any other joined tables exist, you can modify them according to your needs. This tutorial will not cover that scenario. |
|
| 70 | + reportSet.JoinedTables.Add(middleJT); //Add the connector table |
|
| 71 | + reportSet.JoinedTables.Add(new JoinedTable() |
|
| 72 | + { |
|
| 73 | + TableName = "[dbo].[Orders]", |
|
| 74 | + RightConditionTable = "[dbo].[testcon]", |
|
| 75 | + RightConditionColumn = "[dbo].[testcon].[ordernum]", |
|
| 76 | + LeftConditionColumn = "[dbo].[Orders].[OrderID]" |
|
| 77 | + }); //Add the first table in the many-to-many relationship and relate it to the connector table |
|
| 78 | + middleJT.RightConditionColumn = "[dbo].[Orders].[OrderID]"; //define the connector table's relationship. If the connector table is in position 0 of the JoinedTables list, this won't matter. |
|
| 79 | + middleJT.LeftConditionColumn = "[dbo].[testcon].[ordernum]"; //If the connector table is not in position 0, then relate it to another table that is not in the many-to-many relationship |
|
| 80 | + middleJT.RightConditionTable = "[dbo].[Orders]"; //This tutorial will assume the connector table is in position 0 of the JoinedTables list. |
|
| 81 | + reportSet.JoinedTables.Add(new JoinedTable() |
|
| 82 | + { |
|
| 83 | + TableName = "[dbo].[Suppliers]", |
|
| 84 | + RightConditionTable = "[dbo].[testcon]", |
|
| 85 | + RightConditionColumn = "[dbo].[testcon].[supplier]", |
|
| 86 | + LeftConditionColumn = "[dbo].[Suppliers].[SupplierID]" |
|
| 87 | + }); //Add the second table in the many-to-many relationship and relate it to the connector table |
|
| 88 | + } |
|
| 89 | + } |
|
| 90 | +} |
|
| 91 | +``` |
|
| 92 | + |
|
| 93 | +``` visualbasic |
|
| 94 | +Public Overrides Sub PreExecuteReportSet(ByVal reportSet As ReportSet) |
|
| 95 | + MyBase.PreExecuteReportSet(reportSet) |
|
| 96 | + AdHocContext.Driver.RemoveConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]") |
|
| 97 | + If (reportSet.JoinedTables.Count > 0) Then |
|
| 98 | + Dim jts As IEnumerable(Of JoinedTable) = (From jt As JoinedTable In reportSet.JoinedTables |
|
| 99 | + Where (jt.DbTable.Name.Contains("Orders") OrElse jt.DbTable.Name.Contains("Suppliers")) OrElse |
|
| 100 | + (jt.RightConditionTable.Contains("Orders") OrElse jt.RightConditionTable.Contains("Suppliers")) |
|
| 101 | + Select jt) |
|
| 102 | + Dim middleJT As New JoinedTable() With { |
|
| 103 | + .TableName = "[dbo].[testcon]" |
|
| 104 | + } |
|
| 105 | + If (jts.Count() = 2) Then |
|
| 106 | + reportSet.JoinedTables.Remove(jts.ToArray()(1)) |
|
| 107 | + reportSet.JoinedTables.Remove(jts.ToArray()(0)) |
|
| 108 | + reportSet.JoinedTables.Add(middleJT) |
|
| 109 | + reportSet.JoinedTables.Add(New JoinedTable() With { |
|
| 110 | + .TableName = "[dbo].[Orders]", |
|
| 111 | + .RightConditionTable = "[dbo].[testcon]", |
|
| 112 | + .RightConditionColumn = "[dbo].[testcon].[ordernum]", |
|
| 113 | + .LeftConditionColumn = "[dbo].[Orders].[OrderID]" |
|
| 114 | + }) |
|
| 115 | + middleJT.RightConditionColumn = "[dbo].[Orders].[OrderID]" |
|
| 116 | + middleJT.LeftConditionColumn = "[dbo].[testcon].[ordernum]" |
|
| 117 | + middleJT.RightConditionTable = "[dbo].[Orders]" |
|
| 118 | + reportSet.JoinedTables.Add(New JoinedTable() With { |
|
| 119 | + .TableName = "[dbo].[Suppliers]", |
|
| 120 | + .RightConditionTable = "[dbo].[testcon]", |
|
| 121 | + .RightConditionColumn = "[dbo].[testcon].[supplier]", |
|
| 122 | + .LeftConditionColumn = "[dbo].[Suppliers].[SupplierID]" |
|
| 123 | + }) |
|
| 124 | + End If |
|
| 125 | + End If |
|
| 126 | +End Sub |
|
| 127 | +``` |
|
| 128 | + |
|
| 129 | +The above code in the [[PreExecuteReportSet()|/FAQ/PreExecuteReportSet]] method will re-define the relationship between the two tables in a many-to-many relationship and insert the connector table silently. This will produce a working join on the two tables while leaving out the pre-defined fake join. The user will be able to freely select the two tables in the many-to-many relationship without seeing the connecting table. |
|
| 130 | + |
|
| 131 | +Finally, we need a statement in the [[PostExecuteReportSet()|/FAQ/PostExecuteReportSet]] method. |
|
| 132 | + |
|
| 133 | +``` csharp |
|
| 134 | +public override void PostExecuteReportSet(ReportSet reportSet) |
|
| 135 | +{ |
|
| 136 | + base.PostExecuteReportSet(reportSet); |
|
| 137 | + AdHocContext.Driver.AddConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]"); |
|
| 138 | +} |
|
| 139 | +``` |
|
| 140 | + |
|
| 141 | +``` visualbasic |
|
| 142 | +Public Overrides Sub PostExecuteReportSet(ByVal reportSet As ReportSet) |
|
| 143 | + MyBase.PostExecuteReportSet(reportSet) |
|
| 144 | + AdHocContext.Driver.AddConstraint("[dbo].[Orders].[ShipName]", "[dbo].[Suppliers].[CompanyName]") |
|
| 145 | +End Sub |
|
| 146 | +``` |
|
| 147 | + |
|
| 148 | + |
|
| 149 | +Re-adding the constraint after report execution will ensure that the tables will be selectable again when the user makes subsequent trips to the Report Designer. Other users in the system without the ability to select the datasources in question will not see any change in their current behavior. Report definitions using the relationship will not reflect the internal process. The fake constraint will appear in the definition. |
|
| ... | ... | \ No newline at end of file |