diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/Bot.Builder.Community.Storage.EntityFramework.csproj b/libraries/Bot.Builder.Community.Storage.EntityFramework/Bot.Builder.Community.Storage.EntityFramework.csproj
index 07dd6cb3..011d07f7 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/Bot.Builder.Community.Storage.EntityFramework.csproj
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/Bot.Builder.Community.Storage.EntityFramework.csproj
@@ -1,24 +1,16 @@
-
-
- netstandard2.0
- Entity Framework based storage for bots created using Microsoft Bot Builder SDK.
- Bot Builder Community
- Bot Builder Community
- https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/blob/master/LICENSE
- https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/master/libraries/Bot.Builder.Community.Storage.EntityFramework
- 1.0.0
- bot framework, bot builder, azure bot service, storage
- 1.0.0
- 1.0.0
-
+
+ net6.0
+ enable
+ enable
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataContext.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataContext.cs
index 2ab19059..9cd622d9 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataContext.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataContext.cs
@@ -1,62 +1,64 @@
-using System;
+using Bot.Builder.Community.Storage.EntityFramework;
using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace Bot.Builder.Community.Storage.EntityFramework
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+public class BotDataContext : DbContext
{
+ private string? _connectionString;
+
///
- /// DbContext for BotDataEntitys
+ /// Constructor for BotDataContext receiving connectionString
///
- public class BotDataContext : DbContext
+ /// Connection string to use when configuring the options during
+ public BotDataContext(string connectionString)
+ : base()
{
- private string _connectionString;
-
- ///
- /// Constructor for BotDataContext receiving connectionString
- ///
- /// Connection string to use when configuring the options during
- public BotDataContext(string connectionString)
- : base()
+ if (string.IsNullOrEmpty(connectionString))
{
- if (string.IsNullOrEmpty(connectionString))
- {
- throw new ArgumentNullException(nameof(connectionString));
- }
-
- _connectionString = connectionString;
+ throw new ArgumentNullException(nameof(connectionString));
}
- ///
- /// Constructor for BotDataContext receiving DBContextOptions
- ///
- /// Options to use for configuration.
- public BotDataContext(DbContextOptions options)
- : base(options)
- { }
-
- ///
- /// BotDataEntity records
- ///
- public virtual DbSet BotDataEntity { get; set; }
+ _connectionString = connectionString;
+ }
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- if (!optionsBuilder.IsConfigured)
- {
- optionsBuilder.UseSqlServer(_connectionString);
- }
+ ///
+ /// Constructor for BotDataContext receiving DBContextOptions
+ ///
+ /// Options to use for configuration.
+ public BotDataContext(DbContextOptions options)
+ : base(options)
+ { }
- base.OnConfiguring(optionsBuilder);
- }
+ ///
+ /// BotDataEntity records
+ ///
+ public DbSet? BotDataEntity { get; set; }
- protected override void OnModelCreating(ModelBuilder builder)
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
{
- builder.Entity(entity =>
- {
- entity.ToTable(nameof(BotDataEntity));
- entity.HasIndex(e => e.RealId);
- entity.HasKey(e => e.Id);
- });
+ optionsBuilder.UseSqlServer(_connectionString!);
}
+ base.OnConfiguring(optionsBuilder);
}
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.Entity(entity =>
+ {
+ entity.ToTable(nameof(BotDataEntity));
+ entity.HasIndex(e => e.RealId);
+ entity.HasKey(e => e.Id);
+ });
+ }
+
+
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataEntity.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataEntity.cs
index 57119afa..c1eb2532 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataEntity.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/BotDataEntity.cs
@@ -1,62 +1,62 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace Bot.Builder.Community.Storage.EntityFramework
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+[Table("BotDataEntity")]
+public class BotDataEntity
{
///
- /// BotDataEntity representing one bot data record.
+ /// Constructor for BotDataEntity
///
- [Table("BotDataEntity")]
- public class BotDataEntity
+ ///
+ /// Sets Timestamp to DateTimeOffset.UtfcNow
+ ///
+ public BotDataEntity()
{
- ///
- /// Constructor for BotDataEntity
- ///
- ///
- /// Sets Timestamp to DateTimeOffset.UtfcNow
- ///
- public BotDataEntity()
- {
- Timestamp = DateTimeOffset.UtcNow;
- }
-
- ///
- /// Gets or sets the auto-generated Id/Key.
- ///
- ///
- /// The database generated Id/Key.
- ///
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; set; }
+ Timestamp = DateTimeOffset.UtcNow;
+ }
- ///
- /// Gets or sets the un-sanitized Id/Key.
- ///
- ///
- /// The un-sanitized Id/Key.
- ///
- [MaxLength(1024)]
- public string RealId { get; set; }
+ ///
+ /// Gets or sets the auto-generated Id/Key.
+ ///
+ ///
+ /// The database generated Id/Key.
+ ///
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; set; }
- ///
- /// Gets or sets the persisted object's state.
- ///
- ///
- /// The persisted object's state.
- ///
- [Column(TypeName = "nvarchar(MAX)")]
- public string Document { get; set; }
+ ///
+ /// Gets or sets the un-sanitized Id/Key.
+ ///
+ ///
+ /// The un-sanitized Id/Key.
+ ///
+ [MaxLength(1024)]
+ public string? RealId { get; set; }
- ///
- /// Gets or sets the current timestamp.
- ///
- ///
- /// The current timestamp.
- ///
- [Required]
- [Timestamp]
- public DateTimeOffset Timestamp { get; set; }
- }
+ ///
+ /// Gets or sets the persisted object's state.
+ ///
+ ///
+ /// The persisted object's state.
+ ///
+ [Column(TypeName = "nvarchar(MAX)")]
+ public string? Document { get; set; }
+ ///
+ /// Gets or sets the current timestamp.
+ ///
+ ///
+ /// The current timestamp.
+ ///
+ [Required]
+ [Timestamp]
+ public DateTimeOffset? Timestamp { get; set; }
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorage.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorage.cs
index 3642a978..53ef33d2 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorage.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorage.cs
@@ -1,214 +1,217 @@
-using System;
+using Microsoft.Bot.Builder;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
+using System.Text;
using System.Threading.Tasks;
-using Microsoft.Bot.Builder;
-using Microsoft.EntityFrameworkCore;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using System.Transactions;
+using Bot.Builder.Community.Storage.EntityFramework;
+
+namespace Bot.Builder.Community.Storage.EntityFramework;
-namespace Bot.Builder.Community.Storage.EntityFramework
+public class EntityFrameworkStorage : IStorage
{
+ private static readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
+
+ private bool _checkedConnection;
+ private readonly EntityFrameworkStorageOptions? _storageOptions;
+
///
- /// Implements an EntityFramework based storage provider for a bot.
+ /// Initializes a new instance of the class.
+ /// using the provided connection string.
///
- public class EntityFrameworkStorage : IStorage
+ /// Entity Framework database connection string.
+ public EntityFrameworkStorage(string connectionString)
+ : this(new EntityFrameworkStorageOptions() { ConnectionString = connectionString })
{
- private static readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
-
- private bool _checkedConnection;
- private readonly EntityFrameworkStorageOptions _storageOptions;
-
- ///
- /// Initializes a new instance of the class.
- /// using the provided connection string.
- ///
- /// Entity Framework database connection string.
- public EntityFrameworkStorage(string connectionString)
- : this(new EntityFrameworkStorageOptions() { ConnectionString = connectionString })
+ if (string.IsNullOrEmpty(connectionString))
{
- if (string.IsNullOrEmpty(connectionString))
- {
- throw new ArgumentNullException(nameof(connectionString));
- }
+ throw new ArgumentNullException(nameof(connectionString));
+ }
+ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Entity Framework options class.
+ public EntityFrameworkStorage(EntityFrameworkStorageOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Entity Framework options class.
- public EntityFrameworkStorage(EntityFrameworkStorageOptions options)
+
+ if (string.IsNullOrEmpty(options.ConnectionString))
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ throw new ArgumentNullException(nameof(options.ConnectionString));
+ }
- if (string.IsNullOrEmpty(options.ConnectionString))
- {
- throw new ArgumentNullException(nameof(options.ConnectionString));
- }
+ _storageOptions = options;
+ }
- _storageOptions = options;
+ ///
+ /// Get a BotDataContext will by default use the connection string provided during EntityFrameworkStorage construction.
+ ///
+ public BotDataContext GetBotDataContext => new(_storageOptions!.ConnectionString!);
+
+ ///
+ /// Deletes storage items from storage.
+ ///
+ /// keys of the objects to remove from the store.
+ /// A cancellation token that can be used by other objects
+ /// or threads to receive notice of cancellation.
+ /// A task that represents the work queued to execute.
+ ///
+ ///
+ public async Task DeleteAsync(string[] keys, CancellationToken cancellationToken)
+ {
+ if (keys == null)
+ {
+ throw new ArgumentNullException(nameof(keys));
}
- ///
- /// Get a BotDataContext will by default use the connection string provided during EntityFrameworkStorage construction.
- ///
- public virtual BotDataContext GetBotDataContext => new BotDataContext(_storageOptions.ConnectionString);
-
- ///
- /// Deletes storage items from storage.
- ///
- /// keys of the objects to remove from the store.
- /// A cancellation token that can be used by other objects
- /// or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- ///
- public async Task DeleteAsync(string[] keys, CancellationToken cancellationToken)
+ if (keys.Length == 0)
{
- if (keys == null)
- {
- throw new ArgumentNullException(nameof(keys));
- }
+ return;
+ }
- if (keys.Length == 0)
- {
- return;
- }
+ // Ensure Initialization has been run
+ await EnsureConnection().ConfigureAwait(false);
- // Ensure Initialization has been run
- await EnsureConnection().ConfigureAwait(false);
+ using (var context = GetBotDataContext)
+ {
+ context.RemoveRange(context.BotDataEntity!.Where(item => keys.Contains(item.RealId)));
+ await context.SaveChangesAsync();
+ }
+ }
- using (var context = GetBotDataContext)
- {
- context.RemoveRange(context.BotDataEntity.Where(item => keys.Contains(item.RealId)));
- await context.SaveChangesAsync();
- }
+ ///
+ /// Reads storage items from storage.
+ ///
+ /// keys of the objects to read from the store.
+ /// A cancellation token that can be used by other objects
+ /// or threads to receive notice of cancellation.
+ /// A task that represents the work queued to execute.
+ /// If the activities are successfully sent, the task result contains
+ /// the items read, indexed by key.
+ ///
+ ///
+ public async Task> ReadAsync(string[] keys, CancellationToken cancellationToken)
+ {
+ if (keys == null)
+ {
+ throw new ArgumentNullException(nameof(keys));
}
- ///
- /// Reads storage items from storage.
- ///
- /// keys of the objects to read from the store.
- /// A cancellation token that can be used by other objects
- /// or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- /// If the activities are successfully sent, the task result contains
- /// the items read, indexed by key.
- ///
- ///
- public async Task> ReadAsync(string[] keys, CancellationToken cancellationToken)
+ if (keys.Length == 0)
{
- if (keys == null)
- {
- throw new ArgumentNullException(nameof(keys));
- }
+ // No keys passed in, no result to return.
+ return new Dictionary();
+ }
- if (keys.Length == 0)
- {
- // No keys passed in, no result to return.
- return new Dictionary();
- }
+ // Ensure we have checked for possible connection issues
+ await EnsureConnection().ConfigureAwait(false);
- // Ensure we have checked for possible connection issues
- await EnsureConnection().ConfigureAwait(false);
+ var storeItems = new Dictionary(keys.Length);
- var storeItems = new Dictionary(keys.Length);
+ using (var database = GetBotDataContext)
+ {
+ var query = (from item in database.BotDataEntity
+ where keys.Any(k => k == item.RealId)
+ select new { item.RealId, item.Document });
- using (var database = GetBotDataContext)
+ foreach (var item in query)
{
- var query = (from item in database.BotDataEntity
- where keys.Any(k => k == item.RealId)
- select new { item.RealId, item.Document });
-
- foreach (var item in query)
- {
- var jObject = JObject.Parse(item.Document).ToObject(typeof(object), _jsonSerializer);
- storeItems.Add(item.RealId, jObject);
- }
-
- return storeItems;
+ var jObject = JObject.Parse(item.Document).ToObject(typeof(object), _jsonSerializer);
+ storeItems.Add(item.RealId, jObject!);
}
+
+ return storeItems;
}
+ }
- ///
- /// Writes storage items to storage.
- ///
- /// The items to write to storage, indexed by key.
- /// A cancellation token that can be used by other objects
- /// or threads to receive notice of cancellation.
- /// A task that represents the work queued to execute.
- ///
- ///
- public async Task WriteAsync(IDictionary changes, CancellationToken cancellationToken)
+ ///
+ /// Writes storage items to storage.
+ ///
+ /// The items to write to storage, indexed by key.
+ /// A cancellation token that can be used by other objects
+ /// or threads to receive notice of cancellation.
+ /// A task that represents the work queued to execute.
+ ///
+ ///
+ public async Task WriteAsync(IDictionary changes, CancellationToken cancellationToken)
+ {
+ if (changes == null)
{
- if (changes == null)
- {
- throw new ArgumentNullException(nameof(changes));
- }
+ throw new ArgumentNullException(nameof(changes));
+ }
- if (changes.Count == 0)
- {
- return;
- }
+ if (changes.Count == 0)
+ {
+ return;
+ }
- // Ensure Initialization has been run
- await EnsureConnection().ConfigureAwait(false);
-
- using (var context = GetBotDataContext)
+ // Ensure Initialization has been run
+ await EnsureConnection().ConfigureAwait(false);
+
+ using (var context = GetBotDataContext)
+ {
+ // Begin a transaction using the isolation level provided in Storage Options
+ var transaction = context.Database.BeginTransaction();
+
+
+#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+ var existingItems = context.BotDataEntity!.Where(item => changes.Keys.Contains(item.RealId)).ToDictionary(d => (d as BotDataEntity).RealId);
+#pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
+
+ foreach (var change in changes)
{
- // Begin a transaction using the isolation level provided in Storage Options
- var transaction = context.Database.BeginTransaction(_storageOptions.TransactionIsolationLevel);
-
- var existingItems = context.BotDataEntity.Where(item => changes.Keys.Contains(item.RealId)).ToDictionary(d => (d as BotDataEntity).RealId);
+ var json = JObject.FromObject(change.Value, _jsonSerializer);
+ var existingItem = existingItems.FirstOrDefault(i => i.Key == change.Key).Value;
- foreach (var change in changes)
+ if (existingItem != null)
{
- var json = JObject.FromObject(change.Value, _jsonSerializer);
- var existingItem = existingItems.FirstOrDefault(i => i.Key == change.Key).Value;
-
- if (existingItem != null)
- {
- existingItem.Document = json.ToString(Formatting.None);
- existingItem.Timestamp = DateTimeOffset.UtcNow;
- }
- else
+ existingItem.Document = json.ToString(Formatting.None);
+ existingItem.Timestamp = DateTimeOffset.UtcNow;
+ }
+ else
+ {
+ var newItem = new BotDataEntity
{
- var newItem = new BotDataEntity
- {
- RealId = change.Key,
- Document = json.ToString(Formatting.None),
- Timestamp = DateTimeOffset.UtcNow,
- };
- await context.BotDataEntity.AddAsync(newItem);
- }
+ RealId = change.Key,
+ Document = json.ToString(Formatting.None),
+ Timestamp = DateTimeOffset.UtcNow,
+ };
+ await context.BotDataEntity!.AddAsync(newItem);
}
-
- context.SaveChanges();
- transaction.Commit();
}
+
+ context.SaveChanges();
+ transaction.Commit();
}
+ }
- ///
- /// Ensures a database connection is possible.
- ///
- ///
- /// Will throw ArgumentException if the database cannot be created to.
- ///
- private async Task EnsureConnection()
+ ///
+ /// Ensures a database connection is possible.
+ ///
+ ///
+ /// Will throw ArgumentException if the database cannot be created to.
+ ///
+ private async Task EnsureConnection()
+ {
+ // In the steady-state case, we'll already have verified the database is setup.
+ if (!_checkedConnection)
{
- // In the steady-state case, we'll already have verified the database is setup.
- if (!_checkedConnection)
+ using (var context = GetBotDataContext)
{
- using (var context = GetBotDataContext)
- {
- if (!await context.Database.CanConnectAsync())
- throw new ArgumentException("The sql database defined in the connection has not been created. See https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/master/libraries/Bot.Builder.Community.Storage.EntityFramework");
- }
- _checkedConnection = true;
+ if (!await context.Database.CanConnectAsync())
+ throw new ArgumentException("The sql database defined in the connection has not been created. See https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/master/libraries/Bot.Builder.Community.Storage.EntityFramework");
}
+ _checkedConnection = true;
}
}
+
+
+
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorageOptions.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorageOptions.cs
index bd0d4215..18839d51 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorageOptions.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkStorageOptions.cs
@@ -1,23 +1,25 @@
-using System.Data;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace Bot.Builder.Community.Storage.EntityFramework
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+public class EntityFrameworkStorageOptions
{
///
- /// Entity Framework Storage Options.
+ /// Gets or sets the connection string to use while creating BotDataContext.
+ ///
+ public string? ConnectionString { get; set; }
+
+ ///
+ /// Gets or sets the transaction isolation level to use during write operations.
///
- public class EntityFrameworkStorageOptions
- {
- ///
- /// Gets or sets the connection string to use while creating BotDataContext.
- ///
- public string ConnectionString { get; set; }
+ ///
+ /// Default IsolationLevel.ReadCommitted
+ ///
+ public IsolationLevel TransactionIsolationLevel => IsolationLevel.ReadCommitted;
- ///
- /// Gets or sets the transaction isolation level to use during write operations.
- ///
- ///
- /// Default IsolationLevel.ReadCommitted
- ///
- public IsolationLevel TransactionIsolationLevel => IsolationLevel.ReadCommitted;
- }
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkTranscriptStore.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkTranscriptStore.cs
index e422f62e..25a7d50e 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkTranscriptStore.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/EntityFrameworkTranscriptStore.cs
@@ -1,226 +1,226 @@
-using System;
-using System.Linq;
-using System.Threading.Tasks;
+using Bot.Builder.Community.Storage.EntityFramework;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Bot.Builder.Community.Storage.EntityFramework;
-namespace Bot.Builder.Community.Storage.EntityFramework
+public class EntityFrameworkTranscriptStore : ITranscriptStore
{
+ private static readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()
+ {
+ NullValueHandling = NullValueHandling.Ignore,
+ Formatting = Formatting.Indented,
+ });
+
+ private TranscriptStoreOptions _options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Connection string to connect to Sql Server Storage.
+ public EntityFrameworkTranscriptStore(string connectionString)
+ : this(new TranscriptStoreOptions() { ConnectionString = connectionString })
+ {
+ if (string.IsNullOrEmpty(connectionString))
+ {
+ throw new ArgumentNullException(nameof(connectionString));
+ }
+ }
+
///
- /// The Entity Framework transcript store stores transcripts in Sql Server.
+ /// Initializes a new instance of the class.
///
- ///
- /// Each activity is stored as json in the Activity field.
- ///
- public class EntityFrameworkTranscriptStore : ITranscriptStore
+ /// Options to use for the Transcript Store
+ public EntityFrameworkTranscriptStore(TranscriptStoreOptions options)
{
- private static readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()
+ if (options == null)
{
- NullValueHandling = NullValueHandling.Ignore,
- Formatting = Formatting.Indented,
- });
-
- private TranscriptStoreOptions _options;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Connection string to connect to Sql Server Storage.
- public EntityFrameworkTranscriptStore(string connectionString)
- :this(new TranscriptStoreOptions() { ConnectionString = connectionString})
+ throw new ArgumentNullException(nameof(options));
+ }
+ if (string.IsNullOrEmpty(options.ConnectionString))
{
- if (string.IsNullOrEmpty(connectionString))
- {
- throw new ArgumentNullException(nameof(connectionString));
- }
+ throw new ArgumentNullException(nameof(options.ConnectionString) + " cannot be empty.");
}
+ _options = options;
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- /// Options to use for the Transcript Store
- public EntityFrameworkTranscriptStore(TranscriptStoreOptions options)
+ ///
+ /// Get a TranscriptContext will by default use the connection string provided during EntityFrameworkTranscriptStore construction.
+ ///
+ public virtual TranscriptContext GetTranscriptContext => new TranscriptContext(_options.ConnectionString!);
+
+ ///
+ /// Log an activity to the transcript.
+ ///
+ /// Activity being logged.
+ /// A A task that represents the work queued to execute.
+ public async Task LogActivityAsync(IActivity activity)
+ {
+ BotAssert.ActivityNotNull(activity);
+
+ using (var context = GetTranscriptContext)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
- if (string.IsNullOrEmpty(options.ConnectionString))
+ var transcript = new TranscriptEntity()
{
- throw new ArgumentNullException(nameof(options.ConnectionString) + " cannot be empty.");
- }
- _options = options;
+ Channel = activity.ChannelId,
+ Conversation = activity.Conversation.Id,
+ Activity = JsonConvert.SerializeObject(activity)
+ };
+ await context.Transcript!.AddAsync(transcript);
+ await context.SaveChangesAsync();
}
+ }
- ///
- /// Get a TranscriptContext will by default use the connection string provided during EntityFrameworkTranscriptStore construction.
- ///
- public virtual TranscriptContext GetTranscriptContext => new TranscriptContext(_options.ConnectionString);
-
- ///
- /// Log an activity to the transcript.
- ///
- /// Activity being logged.
- /// A A task that represents the work queued to execute.
- public async Task LogActivityAsync(IActivity activity)
+ ///
+ /// Get activities for a conversation (Aka the transcript).
+ ///
+ /// Channel Id.
+ /// Conversation Id.
+ /// Continuatuation token to page through results. (Id of last record returned)
+ /// Earliest time to include.
+ /// PagedResult of activities.
+ public Task> GetTranscriptActivitiesAsync(string channelId, string conversationId, string? continuationToken = null, DateTimeOffset startDate = default(DateTimeOffset))
+ {
+ if (string.IsNullOrEmpty(channelId))
{
- BotAssert.ActivityNotNull(activity);
+ throw new ArgumentNullException($"missing {nameof(channelId)}");
+ }
- using (var context = GetTranscriptContext)
- {
- var transcript = new TranscriptEntity()
- {
- Channel = activity.ChannelId,
- Conversation = activity.Conversation.Id,
- Activity = JsonConvert.SerializeObject(activity)
- };
- await context.Transcript.AddAsync(transcript);
- await context.SaveChangesAsync();
- }
+ if (string.IsNullOrEmpty(conversationId))
+ {
+ throw new ArgumentNullException($"missing {nameof(conversationId)}");
}
- ///
- /// Get activities for a conversation (Aka the transcript).
- ///
- /// Channel Id.
- /// Conversation Id.
- /// Continuatuation token to page through results. (Id of last record returned)
- /// Earliest time to include.
- /// PagedResult of activities.
- public Task> GetTranscriptActivitiesAsync(string channelId, string conversationId, string continuationToken = null, DateTimeOffset startDate = default(DateTimeOffset))
+ int continuationId = 0;
+ if (!string.IsNullOrEmpty(continuationToken))
{
- if (string.IsNullOrEmpty(channelId))
+ if (!int.TryParse(continuationToken, out continuationId))
{
- throw new ArgumentNullException($"missing {nameof(channelId)}");
+ throw new ArgumentException(nameof(continuationToken) + " must be an integer");
}
+ }
+
+ var pagedResult = new PagedResult();
- if (string.IsNullOrEmpty(conversationId))
+ using (var context = GetTranscriptContext)
+ {
+ var query = context.Transcript!.Where(t => t.Channel == channelId && t.Conversation == conversationId);
+ // Filter on startDate, if present
+ if (startDate != default(DateTimeOffset))
{
- throw new ArgumentNullException($"missing {nameof(conversationId)}");
+ query = query.Where(t => t.Timestamp >= startDate);
}
- int continuationId = 0;
+ // Filter on continuationToken if present
if (!string.IsNullOrEmpty(continuationToken))
{
- if (!int.TryParse(continuationToken, out continuationId))
- {
- throw new ArgumentException(nameof(continuationToken) + " must be an integer");
- }
+ query = query.Where(t => t.Id > continuationId);
}
- var pagedResult = new PagedResult();
+ var finalItems = query.OrderBy(i => i.Id).Take(_options.PageSize).Select(i => new { i.Id, i.Activity }).ToArray();
+ // Take only PageSize, and convert to Activities
+#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
+ pagedResult.Items = finalItems.Select(i => JsonConvert.DeserializeObject(i.Activity!)).ToArray();
+#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type.
- using (var context = GetTranscriptContext)
+ if (pagedResult.Items.Length == _options.PageSize)
{
- var query = context.Transcript.Where(t => t.Channel == channelId && t.Conversation == conversationId);
- // Filter on startDate, if present
- if (startDate != default(DateTimeOffset))
- {
- query = query.Where(t => t.Timestamp >= startDate);
- }
-
- // Filter on continuationToken if present
- if (!string.IsNullOrEmpty(continuationToken))
- {
- query = query.Where(t => t.Id > continuationId);
- }
-
- var finalItems = query.OrderBy(i => i.Id).Take(_options.PageSize).Select(i => new { i.Id, i.Activity }).ToArray();
- // Take only PageSize, and convert to Activities
- pagedResult.Items = finalItems.Select(i => JsonConvert.DeserializeObject(i.Activity)).ToArray();
-
- if (pagedResult.Items.Length == _options.PageSize)
- {
- pagedResult.ContinuationToken = finalItems.Last().Id.ToString();
- }
+ pagedResult.ContinuationToken = finalItems.Last().Id.ToString();
}
+ }
- return Task.FromResult(pagedResult);
+ return Task.FromResult(pagedResult);
+ }
+
+ ///
+ /// List conversations in the channelId.
+ ///
+ /// Channel Id.
+ /// Continuatuation token to page through results.
+ /// A A task that represents the work queued to execute.
+ public Task> ListTranscriptsAsync(string channelId, string? continuationToken = default)
+ {
+ if (string.IsNullOrEmpty(channelId))
+ {
+ throw new ArgumentNullException($"missing {nameof(channelId)}");
}
- ///
- /// List conversations in the channelId.
- ///
- /// Channel Id.
- /// Continuatuation token to page through results.
- /// A A task that represents the work queued to execute.
- public Task> ListTranscriptsAsync(string channelId, string continuationToken = null)
+ DateTimeOffset continuationDate = default(DateTimeOffset);
+ if (!string.IsNullOrEmpty(continuationToken))
{
- if (string.IsNullOrEmpty(channelId))
+ if (!DateTimeOffset.TryParse(continuationToken, out continuationDate))
{
- throw new ArgumentNullException($"missing {nameof(channelId)}");
+ throw new ArgumentException(nameof(continuationToken) + " must be an DateTimeOffset");
}
+ }
- DateTimeOffset continuationDate = default(DateTimeOffset);
+ var pagedResult = new PagedResult();
+
+ using (var context = GetTranscriptContext)
+ {
+ var query = context.Transcript!.Where(t => t.Channel == channelId);
+
+ // Get all conversation.ids with thier min Timestamps
+ var items = (from p in query
+ group p by p.Conversation into grp
+ let timestamp = grp.Min(p => p.Timestamp)
+ let conversationId = grp.Key
+
+ from p in grp
+ where p.Conversation == conversationId && p.Timestamp == timestamp
+ select new { p.Conversation, p.Timestamp });
+
+ // Filter on continuationToken if present
if (!string.IsNullOrEmpty(continuationToken))
{
- if (!DateTimeOffset.TryParse(continuationToken, out continuationDate))
- {
- throw new ArgumentException(nameof(continuationToken) + " must be an DateTimeOffset");
- }
+ // TODO: what if two activities have the same timestamp??? is that possible???
+ items = items.Where(i => i.Timestamp > continuationDate);
}
-
- var pagedResult = new PagedResult();
- using (var context = GetTranscriptContext)
+ // Take only PageSize, and convert to Transcript Info
+ var finalItems = items.OrderBy(i => i.Timestamp).Take(_options.PageSize);
+ pagedResult.Items = finalItems.Select(i => new TranscriptInfo() { ChannelId = channelId, Id = i.Conversation, Created = (DateTimeOffset)i.Timestamp! }).ToArray();
+
+ // Set ContinuationToken to last date
+ if (pagedResult.Items.Length == _options.PageSize)
{
- var query = context.Transcript.Where(t => t.Channel == channelId);
-
- // Get all conversation.ids with thier min Timestamps
- var items = (from p in query
- group p by p.Conversation into grp
- let timestamp = grp.Min(p => p.Timestamp)
- let conversationId = grp.Key
-
- from p in grp
- where p.Conversation == conversationId && p.Timestamp == timestamp
- select new { p.Conversation, p.Timestamp });
-
- // Filter on continuationToken if present
- if (!string.IsNullOrEmpty(continuationToken))
- {
- // TODO: what if two activities have the same timestamp??? is that possible???
- items = items.Where(i => i.Timestamp > continuationDate);
- }
-
- // Take only PageSize, and convert to Transcript Info
- var finalItems = items.OrderBy(i => i.Timestamp).Take(_options.PageSize);
- pagedResult.Items = finalItems.Select(i => new TranscriptInfo() { ChannelId = channelId, Id = i.Conversation, Created = i.Timestamp }).ToArray();
-
- // Set ContinuationToken to last date
- if (pagedResult.Items.Length == _options.PageSize)
- {
- pagedResult.ContinuationToken = finalItems.OrderByDescending(i => i.Timestamp).First().Timestamp.ToString();
- }
+ pagedResult.ContinuationToken = finalItems.OrderByDescending(i => i.Timestamp).First().Timestamp.ToString();
}
-
- return Task.FromResult(pagedResult);
}
- ///
- /// Delete a specific conversation and all of it's activities.
- ///
- /// Channel Id where conversation took place.
- /// Id of the conversation to delete.
- /// A A task that represents the work queued to execute.
- public async Task DeleteTranscriptAsync(string channelId, string conversationId)
+ return Task.FromResult(pagedResult);
+ }
+
+ ///
+ /// Delete a specific conversation and all of it's activities.
+ ///
+ /// Channel Id where conversation took place.
+ /// Id of the conversation to delete.
+ /// A A task that represents the work queued to execute.
+ public async Task DeleteTranscriptAsync(string channelId, string conversationId)
+ {
+ if (string.IsNullOrEmpty(channelId))
{
- if (string.IsNullOrEmpty(channelId))
- {
- throw new ArgumentNullException($"{nameof(channelId)} should not be null");
- }
+ throw new ArgumentNullException($"{nameof(channelId)} should not be null");
+ }
- if (string.IsNullOrEmpty(conversationId))
- {
- throw new ArgumentNullException($"{nameof(conversationId)} should not be null");
- }
+ if (string.IsNullOrEmpty(conversationId))
+ {
+ throw new ArgumentNullException($"{nameof(conversationId)} should not be null");
+ }
- using (var context = GetTranscriptContext)
- {
- context.RemoveRange(context.Transcript.Where(item => item.Conversation == conversationId && item.Channel == channelId));
- await context.SaveChangesAsync();
- }
+ using (var context = GetTranscriptContext)
+ {
+ context.RemoveRange(context.Transcript!.Where(item => item.Conversation == conversationId && item.Channel == channelId));
+ await context.SaveChangesAsync();
}
}
+
+
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptContext.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptContext.cs
index 6635b181..500a62f0 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptContext.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptContext.cs
@@ -1,56 +1,59 @@
-using System;
+using Bot.Builder.Community.Storage.EntityFramework;
using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace Bot.Builder.Community.Storage.EntityFramework
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+public class TranscriptContext : DbContext
{
+ private string _connectionString;
+
///
- /// DbContext for TranscriptEntitys
+ /// Constructor for TranscriptContext receiving connectionString
///
- public class TranscriptContext : DbContext
+ /// Connection string to use when configuring the options during
+ public TranscriptContext(string connectionString)
+ : base()
{
- private string _connectionString;
-
- ///
- /// Constructor for TranscriptContext receiving connectionString
- ///
- /// Connection string to use when configuring the options during
- public TranscriptContext(string connectionString)
- : base()
+ if (string.IsNullOrEmpty(connectionString))
{
- if (string.IsNullOrEmpty(connectionString))
- {
- throw new ArgumentNullException(nameof(connectionString));
- }
-
- _connectionString = connectionString;
+ throw new ArgumentNullException(nameof(connectionString));
}
- ///
- /// TranscriptEntity records
- ///
- public virtual DbSet Transcript { get; set; }
-
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- if (!optionsBuilder.IsConfigured)
- {
- optionsBuilder.UseSqlServer(_connectionString);
- }
+ _connectionString = connectionString;
+ }
- base.OnConfiguring(optionsBuilder);
- }
+ ///
+ /// TranscriptEntity records
+ ///
+ public DbSet? Transcript { get; set; }
- protected override void OnModelCreating(ModelBuilder builder)
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
{
- builder.Entity(entity =>
- {
- entity.ToTable(nameof(TranscriptEntity));
- entity.HasIndex(e => e.Conversation);
- entity.HasIndex(e => e.Channel);
- entity.HasIndex(e => new { e.Channel, e.Conversation });
- entity.HasKey(e => e.Id);
- });
+ optionsBuilder.UseSqlServer(_connectionString);
}
+ base.OnConfiguring(optionsBuilder);
+ }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ builder.Entity(entity =>
+ {
+ entity.ToTable(nameof(TranscriptEntity));
+ entity.HasIndex(e => e.Conversation);
+ entity.HasIndex(e => e.Channel);
+ entity.HasIndex(e => new { e.Channel, e.Conversation });
+ entity.HasKey(e => e.Id);
+ });
}
+
+
+
}
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptEntity.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptEntity.cs
index 80382384..92e5e50c 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptEntity.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptEntity.cs
@@ -1,55 +1,56 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
-namespace Bot.Builder.Community.Storage.EntityFramework
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+[Table("TranscriptEntity")]
+public class TranscriptEntity
{
///
- /// TranscriptEntity representing one Bot Activity record.
+ /// Constructor for TranscriptEntity
///
- [Table("TranscriptEntity")]
- public class TranscriptEntity
+ ///
+ /// Sets Timestamp to DateTimeOffset.UtfcNow
+ ///
+ public TranscriptEntity()
{
- ///
- /// Constructor for TranscriptEntity
- ///
- ///
- /// Sets Timestamp to DateTimeOffset.UtfcNow
- ///
- public TranscriptEntity()
- {
- Timestamp = DateTimeOffset.UtcNow;
- }
+ Timestamp = DateTimeOffset.UtcNow;
+ }
- ///
- /// Gets or sets the auto-generated Id/Key.
- ///
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; set; }
+ ///
+ /// Gets or sets the auto-generated Id/Key.
+ ///
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; set; }
- ///
- /// Gets or sets the Channel this activity occurred on.
- ///
- [MaxLength(256)]
- public string Channel { get; set; }
+ ///
+ /// Gets or sets the Channel this activity occurred on.
+ ///
+ [MaxLength(256)]
+ public string? Channel { get; set; }
- ///
- /// Gets or sets the Conversation id this activity occurred on.
- ///
- [MaxLength(1024)]
- public string Conversation { get; set; }
+ ///
+ /// Gets or sets the Conversation id this activity occurred on.
+ ///
+ [MaxLength(1024)]
+ public string? Conversation { get; set; }
- ///
- /// Gets or sets the persisted Activity as a string.
- ///
- [Column(TypeName = "nvarchar(MAX)")]
- public string Activity { get; set; }
+ ///
+ /// Gets or sets the persisted Activity as a string.
+ ///
+ [Column(TypeName = "nvarchar(MAX)")]
+ public string? Activity { get; set; }
- ///
- /// Gets or sets the current timestamp.
- ///
- [Required]
- [Timestamp]
- public DateTimeOffset Timestamp { get; set; }
- }
-}
+ ///
+ /// Gets or sets the current timestamp.
+ ///
+ [Required]
+ [Timestamp]
+ public DateTimeOffset? Timestamp { get; set; }
+}
\ No newline at end of file
diff --git a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptStoreOptions.cs b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptStoreOptions.cs
index 33c7df79..80e4279a 100644
--- a/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptStoreOptions.cs
+++ b/libraries/Bot.Builder.Community.Storage.EntityFramework/TranscriptStoreOptions.cs
@@ -1,21 +1,26 @@
-namespace Bot.Builder.Community.Storage.EntityFramework
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Bot.Builder.Community.Storage.EntityFramework;
+
+
+public class TranscriptStoreOptions
{
+
+ ///
+ /// Gets or sets the connection string to use while creating TranscriptContext.
+ ///
+ public string? ConnectionString { get; set; }
+
///
- /// Entity Framework Transcript Options.
+ /// Gets or sets the total records to return from the store per page.
///
- public class TranscriptStoreOptions
- {
- ///
- /// Gets or sets the connection string to use while creating TranscriptContext.
- ///
- public string ConnectionString { get; set; }
+ ///
+ /// Default 20
+ ///
+ public int PageSize => 20;
- ///
- /// Gets or sets the total records to return from the store per page.
- ///
- ///
- /// Default 20
- ///
- public int PageSize => 20;
- }
}