Skip to content

Commit

Permalink
[IngestionClient] Fix database deployment issue - move database deplo…
Browse files Browse the repository at this point in the history
…yment to code (EF) (#1728)

* move DB migration to code

* Update Connector.csproj

* continue with DB migration

* start with db connector update

* continue

* fixes

* fix migration

* fixes

* fix SilenceBetweenCurrentAndPreviousSegmentInMs value

* small fixes

* catch db exceptions

* update templates and GH action

* remove BOMs

* Update guide.md

* Update IngestionClientDbContextExtensions.cs

* Update DesignTimeSpeechServicesDbContextFactory.cs

* Update Program.cs

* remove unused references

* Delete profile.arm.json
  • Loading branch information
HenryvanderVegte authored Nov 7, 2022
1 parent 0c96ba4 commit 54ddf15
Show file tree
Hide file tree
Showing 28 changed files with 1,504 additions and 388 deletions.
11 changes: 0 additions & 11 deletions .github/workflows/ingestion_client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,3 @@ jobs:
asset_path: ./RealtimeTranscription.zip
asset_name: RealtimeTranscription.zip
asset_content_type: application/zip

- name: Upload Bacpac File
id: upload-release-asset-4
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./samples/ingestion/ingestion-client/Setup/IngestionClient.bacpac
asset_name: IngestionClient.bacpac
asset_content_type: application/octet-stream
6 changes: 6 additions & 0 deletions samples/ingestion/ingestion-client/BatchIngestionClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartTranscriptionByTimer",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RealtimeTranscription", "RealtimeTranscription\RealtimeTranscription.csproj", "{5B4B7645-41AD-4951-AA06-44DF94CDEB8D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabaseMigrator", "DatabaseMigrator\DatabaseMigrator.csproj", "{5BD38646-D3F3-481B-909E-353750AC5384}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -44,6 +46,10 @@ Global
{5B4B7645-41AD-4951-AA06-44DF94CDEB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B4B7645-41AD-4951-AA06-44DF94CDEB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B4B7645-41AD-4951-AA06-44DF94CDEB8D}.Release|Any CPU.Build.0 = Release|Any CPU
{5BD38646-D3F3-481B-909E-353750AC5384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BD38646-D3F3-481B-909E-353750AC5384}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BD38646-D3F3-481B-909E-353750AC5384}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BD38646-D3F3-481B-909E-353750AC5384}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.0" />
<PackageReference Include="JsonSubTypes" Version="1.9.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="5.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.10" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.4" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// <copyright file="IngestionClientDbContext.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>

namespace Connector.Database
{
using Connector.Database.Models;
using Microsoft.EntityFrameworkCore;

public class IngestionClientDbContext : DbContext
{
public IngestionClientDbContext(DbContextOptions<IngestionClientDbContext> options)
: base(options)
{
}

public DbSet<Transcription> Transcriptions { get; set; }

public DbSet<CombinedRecognizedPhrase> CombinedRecognizedPhrases { get; set; }

public DbSet<NBest> NBests { get; set; }

public DbSet<RecognizedPhrase> RecognizedPhrases { get; set; }

public DbSet<Word> Words { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// <copyright file="IngestionClientDbContextExtensions.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>

namespace Connector.Database
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

using Connector.Database.Models;

public static class IngestionClientDbContextExtensions
{
private const int MaxNBestsPerRecognizedPhrase = 1;

public static async Task StoreTranscriptionAsync(
this IngestionClientDbContext ingestionClientDbContext,
Guid transcriptionId,
string locale,
string fileName,
float approximateCost,
SpeechTranscript speechTranscript)
{
_ = ingestionClientDbContext ?? throw new ArgumentNullException(nameof(ingestionClientDbContext));
_ = speechTranscript ?? throw new ArgumentNullException(nameof(speechTranscript));

var transcription = new Transcription(
id: transcriptionId,
locale: locale,
name: fileName,
source: speechTranscript.Source,
timestamp: DateTime.Parse(speechTranscript.Timestamp, CultureInfo.InvariantCulture),
duration: speechTranscript.Duration ?? string.Empty,
durationInSeconds: TimeSpan.FromTicks(speechTranscript.DurationInTicks).TotalSeconds,
numberOfChannels: speechTranscript.CombinedRecognizedPhrases.Count(),
approximateCost: approximateCost);
var combinedRecognizedPhrases = new List<CombinedRecognizedPhrase>();

var phrasesByChannel = speechTranscript.RecognizedPhrases.GroupBy(t => t.Channel);

foreach (var phrases in phrasesByChannel)
{
var channel = phrases.Key;
var combinedPhrase = speechTranscript.CombinedRecognizedPhrases.Where(t => t.Channel == channel).FirstOrDefault();
var combinedRecognizedPhraseDb = AddCombinedRecognizedPhrase(combinedPhrase, channel, phrases);
combinedRecognizedPhrases.Add(combinedRecognizedPhraseDb);
}

transcription = transcription.WithCombinedRecognizedPhrases(combinedRecognizedPhrases);

ingestionClientDbContext.Add(transcription);
var entitiesAdded = await ingestionClientDbContext.SaveChangesAsync().ConfigureAwait(false);
}

private static CombinedRecognizedPhrase AddCombinedRecognizedPhrase(Connector.CombinedRecognizedPhrase combinedRecognizedPhrase, int channel, IEnumerable<Connector.RecognizedPhrase> recognizedPhrases)
{
var combinedRecognizedPhraseDb = new CombinedRecognizedPhrase(
id: Guid.NewGuid(),
channel: channel,
lexical: combinedRecognizedPhrase?.Lexical ?? string.Empty,
itn: combinedRecognizedPhrase?.Lexical ?? string.Empty,
maskedItn: combinedRecognizedPhrase?.Lexical ?? string.Empty,
display: combinedRecognizedPhrase?.Lexical ?? string.Empty,
sentimentNegative: combinedRecognizedPhrase?.Sentiment?.Negative ?? 0d,
sentimentNeutral: combinedRecognizedPhrase?.Sentiment?.Neutral ?? 0d,
sentimentPositive: combinedRecognizedPhrase?.Sentiment?.Positive ?? 0d);

var recognizedPhrasesDb = new List<RecognizedPhrase>();

var orderedPhrases = recognizedPhrases.OrderBy(p => p.OffsetInTicks);
var previousEndInMs = 0.0;
foreach (var phrase in orderedPhrases)
{
var silenceBetweenCurrentAndPreviousSegmentInMs = Convert.ToInt32(Math.Max(0, TimeSpan.FromTicks(phrase.OffsetInTicks).TotalMilliseconds - previousEndInMs));

var recognizedPhraseDb = AddRecognizedPhrase(phrase, silenceBetweenCurrentAndPreviousSegmentInMs);
previousEndInMs = (TimeSpan.FromTicks(phrase.OffsetInTicks) + TimeSpan.FromTicks(phrase.DurationInTicks)).TotalMilliseconds;

recognizedPhrasesDb.Add(recognizedPhraseDb);
}

combinedRecognizedPhraseDb = combinedRecognizedPhraseDb.WithRecognizedPhrases(recognizedPhrasesDb);
return combinedRecognizedPhraseDb;
}

private static RecognizedPhrase AddRecognizedPhrase(Connector.RecognizedPhrase recognizedPhrase, int silenceBetweenCurrentAndPreviousSegmentInMs)
{
var recognizedPhraseDb = new RecognizedPhrase(
id: Guid.NewGuid(),
recognitionStatus: recognizedPhrase.RecognitionStatus,
speaker: recognizedPhrase.Speaker,
channel: recognizedPhrase.Channel,
offset: recognizedPhrase.Offset,
duration: recognizedPhrase.Duration,
silenceBetweenCurrentAndPreviousSegmentInMs: silenceBetweenCurrentAndPreviousSegmentInMs);

var nbestsDb = new List<NBest>();

foreach (var nbestResult in recognizedPhrase.NBest.Take(MaxNBestsPerRecognizedPhrase))
{
var nbestDb = AddNBestResult(nbestResult);
nbestsDb.Add(nbestDb);
}

recognizedPhraseDb = recognizedPhraseDb.WithNBests(nbestsDb);
return recognizedPhraseDb;
}

private static NBest AddNBestResult(Connector.NBest nbest)
{
var nbestDb = new NBest(
id: Guid.NewGuid(),
confidence: nbest.Confidence,
lexical: nbest.Lexical,
itn: nbest.ITN,
maskedItn: nbest.MaskedITN,
display: nbest.Display,
sentimentNegative: nbest.Sentiment?.Negative ?? 0d,
sentimentNeutral: nbest.Sentiment?.Neutral ?? 0d,
sentimentPositive: nbest.Sentiment?.Positive ?? 0d);

if (nbest.Words != null)
{
var wordsDb = new List<Word>();

foreach (var word in nbest.Words)
{
var wordDb = CreateWord(word);
wordsDb.Add(wordDb);
}

nbestDb = nbestDb.WithWords(wordsDb);
}

return nbestDb;
}

private static Word CreateWord(Connector.Words word)
{
var wordDb = new Word(
id: Guid.NewGuid(),
wordText: word.Word,
offset: word.Offset,
duration: word.Duration,
confidence: word.Confidence);

return wordDb;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// <copyright file="CombinedRecognizedPhrase.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>

namespace Connector.Database.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by Entity Framework")]
public class CombinedRecognizedPhrase : DbModelBase
{
public CombinedRecognizedPhrase(Guid id, int channel, string lexical, string itn, string maskedItn, string display, double sentimentNegative, double sentimentNeutral, double sentimentPositive)
{
this.Id = id;
this.Channel = channel;
this.Lexical = lexical;
this.Itn = itn;
this.MaskedItn = maskedItn;
this.Display = display;
this.SentimentNegative = sentimentNegative;
this.SentimentNeutral = sentimentNeutral;
this.SentimentPositive = sentimentPositive;
}

[Column("ID")]
[Key]
public Guid Id { get; set; }

public int Channel { get; private set; }

public string Lexical { get; private set; }

public string Itn { get; private set; }

public string MaskedItn { get; private set; }

public string Display { get; private set; }

public double SentimentNegative { get; private set; }

public double SentimentNeutral { get; private set; }

public double SentimentPositive { get; private set; }

[ForeignKey("CombinedRecognizedPhraseID")]
public ICollection<RecognizedPhrase> RecognizedPhrases { get; set; }

public CombinedRecognizedPhrase WithRecognizedPhrases(ICollection<RecognizedPhrase> recognizedPhrases)
{
this.RecognizedPhrases = recognizedPhrases;
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// <copyright file="DbModelBase.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>

namespace Connector.Database.Models
{
public abstract class DbModelBase
{
public const int MaxTimeSpanColumnLength = 255;

public const int MaxLocaleLength = 255;

public const int MaxDefaultStringLength = 500;

public const int MaxWordLength = 511;

public const int MaxStateLength = 32;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// <copyright file="NBest.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>

namespace Connector.Database.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by Entity Framework")]
public class NBest : DbModelBase
{
public NBest(Guid id, double confidence, string lexical, string itn, string maskedItn, string display, double sentimentNegative, double sentimentNeutral, double sentimentPositive)
{
this.Id = id;
this.Confidence = confidence;
this.Lexical = lexical;
this.Itn = itn;
this.MaskedItn = maskedItn;
this.Display = display;
this.SentimentNegative = sentimentNegative;
this.SentimentNeutral = sentimentNeutral;
this.SentimentPositive = sentimentPositive;
}

[Column("ID")]
[Key]
public Guid Id { get; set; }

public double Confidence { get; private set; }

public string Lexical { get; private set; }

public string Itn { get; private set; }

public string MaskedItn { get; private set; }

public string Display { get; private set; }

public double SentimentNegative { get; private set; }

public double SentimentNeutral { get; private set; }

public double SentimentPositive { get; private set; }

[ForeignKey("NBestID")]
public ICollection<Word> Words { get; set; }

public NBest WithWords(ICollection<Word> words)
{
this.Words = words;
return this;
}
}
}
Loading

0 comments on commit 54ddf15

Please sign in to comment.