Skip to content

Commit 628727e

Browse files
committed
Updated comments
1 parent 7bb946b commit 628727e

2 files changed

Lines changed: 216 additions & 24 deletions

File tree

Sqlbi.PbiPushDataset/PbiConnection.cs

Lines changed: 169 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,73 @@ public PbiPushDatasetException( PbiConnection connection, string message ) : bas
2424
}
2525
}
2626

27+
/// <summary>
28+
/// Manage the connection with the Power BI REST API to manage a Push Dataset.
29+
/// The push dataset is created starting from a Tabular Object Model (TOM) structure, removing the
30+
/// unsupported features (like user hierarchies and inactive relationships).
31+
/// The push dataset can be initialized reading data from another Power BI dataset.
32+
/// The class handles service principal authentication and user authentication for the refresh operation.
33+
/// The class also includes a simulator that creates and write rows according to a configuration table.
34+
/// </summary>
2735
public class PbiConnection
2836
{
2937
// Power BI API settings
3038
private const string resource = "https://analysis.windows.net/powerbi/api";
3139
private const string ApiUrl = "https://api.powerbi.com";
3240

41+
/// <summary>
42+
/// The Tenant ID of Azure Active Directory. See https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
43+
/// </summary>
3344
public string TenantId { get; set; }
45+
46+
/// <summary>
47+
/// This is the service principal for the Power BI REST API authentication.
48+
/// Usually, the tool runs in a scheduled unattended batch.
49+
/// The service principal is a string with a global unique identifier.
50+
/// For more details about the service principal, read https://www.sqlbi.com/articles/creating-a-service-principal-account-for-power-bi-api/
51+
/// </summary>
3452
public string PrincipalId { get; set; }
53+
54+
/// <summary>
55+
/// The client secret for the service principal.
56+
/// For more details about the client secret, read https://www.sqlbi.com/articles/creating-a-service-principal-account-for-power-bi-api/
57+
/// </summary>
3558
public string ClientSecret { get; set; }
3659

3760
// Properties for user authentication (required for Refresh in PbiPushDataset)
61+
62+
/// <summary>
63+
/// The Client ID of the application registered in Azure Active Directory
64+
/// that calls the REST API to update the push dataset.
65+
/// You can see how to register an application and obtain a Client ID in https://www.sqlbi.com/articles/creating-a-service-principal-account-for-power-bi-api/
66+
/// </summary>
3867
public string ClientId { get; set; }
68+
69+
/// <summary>
70+
/// The username to retrieve data from the regular dataset to initialize the push dataset after the daily refresh.
71+
/// </summary>
3972
public string Username { get; set; }
73+
74+
/// <summary>
75+
/// The password to retrieve data from the regular dataset to initialize the push dataset after the daily refresh.
76+
/// </summary>
4077
public string Password { get; set; }
4178

4279
public PbiConnection()
4380
{
4481
}
4582

4683
private PowerBIClient _powerBIClient;
84+
85+
/// <summary>
86+
/// Returns true if the connection to Power BI is active and already authenticated.
87+
/// </summary>
4788
public bool IsOpen { get => _powerBIClient != null; }
4889

90+
/// <summary>
91+
/// Returns the access token for the service principal
92+
/// </summary>
93+
/// <returns></returns>
4994
public async Task<string> GetServicePrincipalToken()
5095
{
5196
IConfidentialClientApplication appBuilder = null;
@@ -72,20 +117,25 @@ public async Task<string> GetServicePrincipalToken()
72117
// The application doesn't have sufficient permissions.
73118
// - Did you declare enough app permissions during app creation?
74119
// - Did the tenant admin grant permissions to the application?
75-
120+
76121
throw;
77122
}
78123
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
79124
{
80125
// Invalid scope. The scope has to be in the form "https://resourceurl/.default"
81126
// Mitigation: Change the scope to be as expected.
82-
127+
83128
throw;
84129
}
85130

86131
return result.AccessToken;
87132
}
88133

134+
/// <summary>
135+
/// Returns the access token for the user-based authentication.
136+
/// </summary>
137+
/// <param name="serverResource"></param>
138+
/// <returns></returns>
89139
public async Task<string> GetUserAccessToken(string serverResource = null)
90140
{
91141
IPublicClientApplication appBuilder = null;
@@ -141,6 +191,11 @@ public async Task<string> GetUserAccessToken(string serverResource = null)
141191
return result.AccessToken;
142192
}
143193

194+
195+
/// <summary>
196+
/// Retrieves the access token for the service principal and connects to Power BI service.
197+
/// </summary>
198+
/// <returns></returns>
144199
public async Task Open()
145200
{
146201
if (IsOpen) return;
@@ -161,18 +216,18 @@ public async Task Open()
161216
{
162217
result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
163218
}
164-
catch (MsalUiRequiredException )
219+
catch (MsalUiRequiredException)
165220
{
166221
// The application doesn't have sufficient permissions.
167222
// - Did you declare enough app permissions during app creation?
168223
// - Did the tenant admin grant permissions to the application?
169-
throw;
224+
throw;
170225
}
171226
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
172227
{
173228
// Invalid scope. The scope has to be in the form "https://resourceurl/.default"
174229
// Mitigation: Change the scope to be as expected.
175-
throw;
230+
throw;
176231
}
177232

178233
#endregion
@@ -181,11 +236,22 @@ public async Task Open()
181236
_powerBIClient = new PowerBIClient(new Uri(ApiUrl), tokenCredentials);
182237
}
183238

184-
public async Task<string> CreatePushDataset(
185-
FileInfo model,
186-
Guid groupId,
187-
string datasetName,
188-
bool overwriteExistingDataset,
239+
/// <summary>
240+
/// Creates a push dataset in the specified workspace using the TOM model retrieved from a model.bim file.
241+
/// </summary>
242+
/// <param name="model">Filename containing the TOM model in model.bim forma (JSON).</param>
243+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace where you publish the push dataset.</param>
244+
/// <param name="datasetName">The name of the push dataset to create.</param>
245+
/// <param name="overwriteExistingDataset">TRUE to overwrite an existing dataset, FALSE to raise an exception if the dataset already exists.</param>
246+
/// <param name="fifoPolicy">TRUE to use the BasicFIFO retention policy for the new push dataset, FALSE to use None as a retention policy.</param>
247+
/// <param name="unsupportedMeasureAction">Action to run for unsupported measures found in the model.</param>
248+
/// <param name="unsupportedRelationshipAction">Action to run for unsupported relationships found in the model.</param>
249+
/// <returns>Dataset ID of the push dataset.</returns>
250+
public async Task<string> CreatePushDataset(
251+
FileInfo model,
252+
Guid groupId,
253+
string datasetName,
254+
bool overwriteExistingDataset,
189255
bool fifoPolicy,
190256
Action<TabModel.Measure> unsupportedMeasureAction,
191257
Action<TabModel.Relationship> unsupportedRelationshipAction)
@@ -203,25 +269,32 @@ public async Task<string> CreatePushDataset(
203269
}
204270
else
205271
{
206-
throw new PbiPushDatasetException( this, $"Dataset {datasetName} already existing." );
272+
throw new PbiPushDatasetException(this, $"Dataset {datasetName} already existing.");
207273
}
208274
}
209275

210276
string modelBim = File.ReadAllText(model.FullName);
211277
TabModel.Database database = TabModel.JsonSerializer.DeserializeDatabase(modelBim);
212-
var schema = SchemaBuilder.GetDatasetRequest( datasetName, database.Model, unsupportedMeasureAction, unsupportedRelationshipAction);
278+
var schema = SchemaBuilder.GetDatasetRequest(datasetName, database.Model, unsupportedMeasureAction, unsupportedRelationshipAction);
213279

214-
// Version for single user
215-
// ds = api.Datasets.PostDataset(schema, DefaultRetentionPolicy.BasicFIFO);
216280
ds = await _powerBIClient.Datasets.PostDatasetInGroupAsync(
217-
groupId,
218-
schema,
281+
groupId,
282+
schema,
219283
fifoPolicy ? DefaultRetentionPolicy.BasicFIFO : DefaultRetentionPolicy.None
220284
);
221285

222286
return ds.Id;
223287
}
224288

289+
/// <summary>
290+
/// Updates the structure of a push dataset in the specified workspace using the TOM model retrieved from a model.bim file.
291+
/// </summary>
292+
/// <param name="model">Filename containing the TOM model in model.bim forma (JSON).</param>
293+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
294+
/// <param name="datasetName">The name of the push dataset to update.</param>
295+
/// <param name="unsupportedMeasureAction">Action to run for unsupported measures found in the model.</param>
296+
/// <param name="unsupportedRelationshipAction">Action to run for unsupported relationships found in the model.</param>
297+
/// <returns>List of updated tables in the push dataset.</returns>
225298
public async Task<List<string>> AlterPushDataset(
226299
FileInfo model,
227300
Guid groupId,
@@ -240,6 +313,15 @@ public async Task<List<string>> AlterPushDataset(
240313
return await AlterPushDataset(model, groupId, new Guid(ds.Id), unsupportedMeasureAction, unsupportedRelationshipAction);
241314
}
242315

316+
/// <summary>
317+
/// Updates the structure of a push dataset in the specified workspace using the TOM model retrieved from a model.bim file.
318+
/// </summary>
319+
/// <param name="model">Filename containing the TOM model in model.bim forma (JSON).</param>
320+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
321+
/// <param name="datasetId">The dataset ID of the push dataset to update.</param>
322+
/// <param name="unsupportedMeasureAction">Action to run for unsupported measures found in the model.</param>
323+
/// <param name="unsupportedRelationshipAction">Action to run for unsupported relationships found in the model.</param>
324+
/// <returns>List of updated tables in the push dataset.</returns>
243325
public async Task<List<string>> AlterPushDataset(
244326
FileInfo model,
245327
Guid groupId,
@@ -268,6 +350,13 @@ public async Task<List<string>> AlterPushDataset(
268350
return updatedTables;
269351
}
270352

353+
/// <summary>
354+
/// Remove all the rows from one or all the tables of a push dataset
355+
/// </summary>
356+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
357+
/// <param name="datasetName">The name of the push dataset.</param>
358+
/// <param name="clearingTable">The table to clear. Pass null to clear all the tables.</param>
359+
/// <returns>List of tables cleared in the push dataset.</returns>
271360
public async Task<List<string>> ClearPushDataset(Guid groupId, string datasetName, Action<string> clearingTable = null)
272361
{
273362
await Open();
@@ -281,12 +370,19 @@ public async Task<List<string>> ClearPushDataset(Guid groupId, string datasetNam
281370
return await ClearPushDataset(groupId, new Guid(ds.Id), clearingTable);
282371
}
283372

284-
public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId, Action<string> clearingTable = null)
373+
/// <summary>
374+
/// Remove all the rows from one or all the tables of a push dataset
375+
/// </summary>
376+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
377+
/// <param name="datasetId">The push dataset ID.</param>
378+
/// <param name="clearingTable">The table to clear. Pass null to clear all the tables.</param>
379+
/// <returns>List of tables cleared in the push dataset.</returns>
380+
public async Task<List<string>> ClearPushDataset(Guid groupId, Guid datasetId, Action<string> clearingTable = null)
285381
{
286382
await Open();
287383
var clearedTables = new List<string>();
288384
var tables = await _powerBIClient.Datasets.GetTablesInGroupAsync(groupId, datasetId.ToString());
289-
foreach( var t in tables.Value )
385+
foreach (var t in tables.Value)
290386
{
291387
clearingTable?.Invoke(t.Name);
292388
await _powerBIClient.Datasets.DeleteRowsInGroupAsync(groupId, datasetId.ToString(), t.Name);
@@ -295,6 +391,13 @@ public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId,
295391
return clearedTables;
296392
}
297393

394+
/// <summary>
395+
/// Runs a simulation writing rows in one or more tables of a push dataset at intervals specified by the simulator configuration.
396+
/// </summary>
397+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
398+
/// <param name="datasetName">The name of the push dataset.</param>
399+
/// <param name="simulator">Configuration for the simulation.</param>
400+
/// <returns>List of tables and number of rows written for each table.</returns>
298401
public async Task<List<(string, int)>> PushSimulation(Guid groupId, string datasetName, Simulator simulator)
299402
{
300403
await Open();
@@ -308,12 +411,19 @@ public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId,
308411
return await PushSimulation(groupId, new Guid(ds.Id), simulator);
309412
}
310413

414+
/// <summary>
415+
/// Runs a simulation writing rows in one or more tables of a push dataset at intervals specified by the simulator configuration.
416+
/// </summary>
417+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
418+
/// <param name="datasetId">The push dataset ID.</param>
419+
/// <param name="simulator">Configuration for the simulation.</param>
420+
/// <returns>List of tables and number of rows written for each table.</returns>
311421
public async Task<List<(string, int)>> PushSimulation(Guid groupId, Guid datasetId, Simulator simulator)
312422
{
313423
await Open();
314424

315425
var pushedTables = new List<(string, int)>();
316-
foreach ( var table in simulator.Parameters.Tables )
426+
foreach (var table in simulator.Parameters.Tables)
317427
{
318428
List<object> rows = new List<object>();
319429
for (int rowNumber = 0; rowNumber < table.BatchRows; rowNumber++)
@@ -327,11 +437,23 @@ public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId,
327437
return pushedTables;
328438
}
329439

330-
public async Task<List<(string,int)>> RefreshWithDax(
331-
Guid groupId, string datasetName,
332-
string sourceWorkspace, string sourceDatabase,
333-
string dax,
334-
Action<string, int> refreshingTable = null,
440+
/// <summary>
441+
/// Writes in tables of a push dataset the result obtained by running one or more DAX queries
442+
/// on another dataset published on the same Power BI tenant (also on a different workspace).
443+
/// </summary>
444+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
445+
/// <param name="datasetName">The name of the push dataset to update.</param>
446+
/// <param name="sourceWorkspace">The name of the workspace containing the dataset to use as a "data source".</param>
447+
/// <param name="sourceDatabase">The name of the dataset to use as a "data source".</param>
448+
/// <param name="dax">DAX queries to run over the source dataset.</param>
449+
/// <param name="refreshingTable">Action to execute while updating the tables in the push dataset.</param>
450+
/// <param name="clearTable">TRUE to overwrite destination tables in the push dataset, FALSE to append rows to existing data in the push dataset.</param>
451+
/// <returns>List of tables and number of rows written for each table.</returns>
452+
public async Task<List<(string, int)>> RefreshWithDax(
453+
Guid groupId, string datasetName,
454+
string sourceWorkspace, string sourceDatabase,
455+
string dax,
456+
Action<string, int> refreshingTable = null,
335457
bool clearTable = true)
336458
{
337459
await Open();
@@ -345,6 +467,18 @@ public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId,
345467
return await RefreshWithDax(groupId, new Guid(ds.Id), sourceWorkspace, sourceDatabase, dax, refreshingTable, clearTable);
346468
}
347469

470+
/// <summary>
471+
/// Writes in tables of a push dataset the result obtained by running one or more DAX queries
472+
/// on another dataset published on the same Power BI tenant (also on a different workspace).
473+
/// </summary>
474+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
475+
/// <param name="datasetId">The Dataset ID of the push dataset to update.</param>
476+
/// <param name="sourceWorkspace">The name of the workspace containing the dataset to use as a "data source".</param>
477+
/// <param name="sourceDatabase">The name of the dataset to use as a "data source".</param>
478+
/// <param name="dax">DAX queries to run over the source dataset.</param>
479+
/// <param name="refreshingTable">Action to execute while updating the tables in the push dataset.</param>
480+
/// <param name="clearTable">TRUE to overwrite destination tables in the push dataset, FALSE to append rows to existing data in the push dataset.</param>
481+
/// <returns>List of tables and number of rows written for each table.</returns>
348482
public async Task<List<(string, int)>> RefreshWithDax(
349483
Guid groupId, Guid datasetId,
350484
string sourceWorkspace, string sourceDatabase,
@@ -356,6 +490,17 @@ public async Task<List<string>> ClearPushDataset( Guid groupId, Guid datasetId,
356490
return await RefreshWithQuery(groupId, datasetId, connectionString, dax, refreshingTable, clearTable);
357491
}
358492

493+
/// <summary>
494+
/// Writes in tables of a push dataset the result obtained by running one or more queries
495+
/// on an ADOMD connection. For example, it can be used to run queries over Analysis Services instances.
496+
/// </summary>
497+
/// <param name="groupId">The Group ID corresponding to the Power BI workspace containing the push dataset.</param>
498+
/// <param name="datasetId">The Dataset ID of the push dataset to update.</param>
499+
/// <param name="connectionString">ADOMD connection string for the source database.</param>
500+
/// <param name="query">Queries to run over the source database.</param>
501+
/// <param name="refreshingTable">Action to execute while updating the tables in the push dataset.</param>
502+
/// <param name="clearTable">TRUE to overwrite destination tables in the push dataset, FALSE to append rows to existing data in the push dataset.</param>
503+
/// <returns>List of tables and number of rows written for each table.</returns>
359504
public async Task<List<(string, int)>> RefreshWithQuery(Guid groupId, Guid datasetId, string connectionString, string query, Action<string, int> refreshingTable = null, bool clearTable = true)
360505
{
361506
const int MAX_ROWS_PER_POST = 9000;

0 commit comments

Comments
 (0)