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