{"id":22106254,"url":"https://github.com/tomasfabian/joker","last_synced_at":"2025-04-07T15:07:58.627Z","repository":{"id":40796273,"uuid":"222001751","full_name":"tomasfabian/Joker","owner":"tomasfabian","description":"Reactive data changes from SQL server to .NET clients. SqlTableDependency extensions, Joker.OData, Joker.Redis, Joker.MVVM and ksqlDB LINQ provider","archived":false,"fork":false,"pushed_at":"2024-12-18T18:14:01.000Z","size":3671,"stargazers_count":68,"open_issues_count":2,"forks_count":23,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-07T15:07:51.004Z","etag":null,"topics":["cross-platform","csharp","dotnet","mvvm","odata","pubsub","redis","sqlserver"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomasfabian.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-15T20:55:27.000Z","updated_at":"2025-02-23T00:01:17.000Z","dependencies_parsed_at":"2023-01-21T14:04:12.338Z","dependency_job_id":"48d85645-3455-4944-95c2-2baad34a2685","html_url":"https://github.com/tomasfabian/Joker","commit_stats":{"total_commits":903,"total_committers":4,"mean_commits":225.75,"dds":0.1472868217054264,"last_synced_commit":"cb1fd620f0b399142226d030be1b5f6e2908e90d"},"previous_names":["tomasfabian/sqltabledependency.extensions"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasfabian%2FJoker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasfabian%2FJoker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasfabian%2FJoker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasfabian%2FJoker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomasfabian","download_url":"https://codeload.github.com/tomasfabian/Joker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247675597,"owners_count":20977376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cross-platform","csharp","dotnet","mvvm","odata","pubsub","redis","sqlserver"],"created_at":"2024-12-01T07:09:14.726Z","updated_at":"2025-04-07T15:07:58.609Z","avatar_url":"https://github.com/tomasfabian.png","language":"C#","funding_links":["https://www.buymeacoffee.com/tomasfabian"],"categories":[],"sub_categories":[],"readme":"Data change notifications from SQL Server via [SqlTableDependency](https://github.com/christiandelbianco/monitor-table-change-with-sqltabledependency), [OData](https://docs.microsoft.com/en-us/odata/overview) and [Redis](https://github.com/StackExchange/StackExchange.Redis) to different [.NET](https://dotnet.microsoft.com/) clients ([WinUI3 - UWP and Win32 apps](https://microsoft.github.io/microsoft-ui-xaml/about.html#what-is-it), [WPF](https://github.com/dotnet/wpf), [Blazor Wasm](https://docs.microsoft.com/sk-sk/aspnet/core/blazor/?view=aspnetcore-5.0#blazor-webassembly), etc). Blazor Wasm notifications are redirected with [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr).\n\n\u003cimg src=\"jokerinaction.gif\" alt=\"Joker in action\" width=\"1024\"/\u003e\n\nSet docker-compose.csproj as startup project in Joker.sln\n\n# Joker Model-View-ViewModel:\nReactive view models for data changes\n\n* [Joker.MVVM wiki](https://github.com/tomasfabian/SqlTableDependency.Extensions/wiki/Joker.MVVM)\n\nInstall-Package Joker.MVVM\n\n# Joker OData:\nPlumbing code for OData web services. Support for batching and end points. Please help out the community by sharing your suggestions and code improvements:\n* [Joker.OData wiki](https://github.com/tomasfabian/SqlTableDependency.Extensions/wiki/Joker.OData)\n\n# Preview:\n[Redis TableDependency status notifier](https://github.com/tomasfabian/SqlTableDependency.Extensions/wiki/Redis-TableDependency-status-notifier---preview)\nSQL server data changes refresher via Redis with End to end reconnections\n\n# SqlTableDependency.Extensions\nThe `SqlTableDependency.Extensions` .NET package is a library that provides convenient and efficient ways to monitor and receive real-time notifications for changes in SQL Server database tables. It is built on top of the [SqlTableDependency](https://github.com/christiandelbianco/monitor-table-change-with-sqltabledependency) library and extends its functionality.\n\nThe main purpose of `SqlTableDependency.Extensions` is to simplify the process of setting up and handling database table change notifications in .NET applications.\n\nWith this package, you can easily subscribe to table changes and receive notifications in your application whenever a row is inserted, updated, or deleted in a specified SQL Server table. \n\n## Install:\nhttps://www.nuget.org/packages/SqlTableDependency.Extensions/\n\nInstall-Package SqlTableDependency.Extensions\n\nor\n\ndotnet add package SqlTableDependency.Extensions --version 3.0.0\n\n## See:\nFollowing package is based on christiandelbianco's `SqlTableDependency`:\nhttps://github.com/christiandelbianco/monitor-table-change-with-sqltabledependency\n\n`SqlTableDependency.Extension.SqlTableDependencyProvider` provides periodic reconnections in case of any error, like lost connection etc.\n\nCurrently there are 3 LifetimeScopes:\n\n## UniqueScope:\nIf the connection is lost, database objects will only be deleted after a timeout period. Upon reconnection, the database objects are recreated, but only if the conversation handle no longer exists.Otherwise, the database objects are preserved and reused.\nIf the application was closed without cleaning the conversation, it will be reused upon app restart. This ensures that data changes within the timeout period are not lost, and messages will be delivered after the reconnection.\n\n## ApplicationScope:\nIn case that the connection is lost, database objects will be deleted only after timeout period. After reconnection the database objects are recreated in case that the conversation handle does not exist anymore. Otherwise the database objects are preserved and reused. If the application was closed the conversation will not continue after app restart. You shouldn't lost data changes within the timeout period. The messages will be delivered after the reconnection.\n\n## ConnectionScope:\nIf the connection is lost, the database objects will be deleted either after a timeout period or during disposal. Upon each reconnection, the database objects are recreated.\n\n[Wiki Samples](https://github.com/tomasfabian/SqlTableDependency.Extensions/wiki/Samples)\n\n## Docker for external dependencies:\nMS SQL Server 2017:\n```\ndocker run --name sql -e \"ACCEPT_EULA=Y\" -e \"SA_PASSWORD=\u003cYourNewStrong@Passw0rd\u003e\" -p 1401:1433 -d mcr.microsoft.com/mssql/server:2017-latest\n```\nRedis latest:\n```\ndocker run --name redis-server -p 6379:6379 -d redis\n```\n\n## Examples Entity Framework migrations:\nPackage Manager Console (Default project =\u003e Examples\\Samples.Data):\n```\nUpdate-Database -ConnectionString \"Server=127.0.0.1,1401;User Id = SA;Password=\u003cYourNewStrong@Passw0rd\u003e;Initial Catalog = Test;\" -ConnectionProviderName \"System.Data.SqlClient\" -ProjectName Sample.Data -verbose\n```\n## Basic usage:\n\nEnable Service Broker in MS SQL SERVER\n\n```SQL\nALTER DATABASE [DatabaseName]\n    SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;\n```\n// C#\n```C#\n  public class Product\n  {\n      public int Id { get; set; }\n      public string Name { get; set; }\n  } \n  \n  using SqlTableDependency.Extensions;\n  using SqlTableDependency.Extensions.Enums;\n  \n  internal class ProductsSqlTableDependencyProvider : SqlTableDependencyProvider\u003cProduct\u003e\n  {\n    private readonly ILogger logger;\n\n    internal ProductsSqlTableDependencyProvider(ConnectionStringSettings connectionStringSettings, IScheduler scheduler, ILogger logger)\n      : base(connectionStringSettings, scheduler, LifetimeScope.UniqueScope)\n    {\n      this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n    }\n\n    internal ProductsSqlTableDependencyProvider(string connectionString, IScheduler scheduler, ILogger logger)\n      : base(connectionString, scheduler, LifetimeScope.UniqueScope)\n    {\n      this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n    }\n\n    protected override ModelToTableMapper\u003cProduct\u003e OnInitializeMapper(ModelToTableMapper\u003cProduct\u003e modelToTableMapper)\n    {\n      modelToTableMapper.AddMapping(c =\u003e c.Id, nameof(Product.Id));\n\n      return modelToTableMapper;\n    }\n\n    protected override void OnInserted(Product product)\n    {\n      base.OnInserted(product);\n\n      logger.Trace(\"Entity was added\");\n\n      LogChangeInfo(product);\n    }\n\n    protected override void OnUpdated(Product product, Product oldValues)\n    {\n      base.OnUpdated(entity, oldValues);\n\n      logger.Trace(\"Entity was modified\");\n\n      LogChangeInfo(product);\n    }\n\n    protected override void OnDeleted(Product product)\n    {\n      base.OnDeleted(product);\n\n      logger.Trace(\"Entity was deleted\");\n\n      LogChangeInfo(product);\n    }\n\n    private void LogChangeInfo(Product product)\n    {\n      Console.WriteLine(Environment.NewLine);\n\n      Console.WriteLine(\"Id: \" + product.Id);\n      Console.WriteLine(\"Name: \" + product.Name);\n\n      Console.WriteLine(\"#####\");\n      Console.WriteLine(Environment.NewLine);\n    }\n    \n    protected override void OnError(Exception exception)\n    {\n      logSource.Error(exception);\n    }\n  }\n```\n//Program.cs\n```C#\nusing System.Configuration;\nusing System.Reactive.Concurrency;\n\nnamespace SqlTableDependency.Extensions.Sample\n{\n  class Program\n  {\n    static void Main(string[] args)\n    {\n      var connectionString = ConfigurationManager.ConnectionStrings[\"FargoEntities\"].ConnectionString;\n      \n      using var productsProvider = new ProductsSqlTableDependencyProvider(connectionString, ThreadPoolScheduler.Instance, new ConsoleLogger());\n      productsProvider.SubscribeToEntityChanges();\n      \n      Console.ReadKey();\n    }\n  }\n}\n\n```\n\n# Joker.Redis\nSqlServer PubSub notifications via Redis and SqlTableDependencyProvider extension\n\nInstall-Package Joker.Redis\n\nDownload and run redis-server (https://redis.io/download) or use Docker (see above).\n\nServer side:\n```C#\npublic class ProductSqlTableDependencyRedisProvider : SqlTableDependencyRedisProvider\u003cProduct\u003e\n{\n  public ProductSqlTableDependencyRedisProvider(ISqlTableDependencyProvider\u003cProduct\u003e sqlTableDependencyProvider, IRedisPublisher redisPublisher) \n    : base(sqlTableDependencyProvider, redisPublisher)\n  {\n  }\n}\n\n```\n\n```C#\nstring redisUrl = ConfigurationManager.AppSettings[\"RedisUrl\"]; //localhost\n\nvar redisPublisher = new RedisPublisher(redisUrl);\nawait redisPublisher.PublishAsync(\"messages\", \"hello\");\n\nusing var productsProvider = new ProductsSqlTableDependencyProvider(connectionString, ThreadPoolScheduler.Instance, new ConsoleLogger());\n\nusing var productSqlTableDependencyRedisProvider = new ProductSqlTableDependencyRedisProvider(productsProvider, redisPublisher, ThreadPoolScheduler.Instance)\n  .StartPublishing();\n```\n\nClient side:\n```C#\nprivate static async Task\u003cRedisSubscriber\u003e CreateRedisSubscriber(string redisUrl)\n{\n  var redisSubscriber = new RedisSubscriber(redisUrl);\n\n  await redisSubscriber.Subscribe(channelMessage =\u003e { Console.WriteLine($\"OnNext({channelMessage.Message})\"); },\n  \"messages\");\n\n  await redisSubscriber.Subscribe(channelMessage =\u003e\n  {\n    var recordChange = JsonConvert.DeserializeObject\u003cRecordChangedNotification\u003cProduct\u003e\u003e(channelMessage.Message);\n    Console.WriteLine($\"OnNext Product changed({recordChange.ChangeType})\");\n    Console.WriteLine($\"OnNext Product changed({recordChange.Entity.Id})\");\n  }, nameof(Product) + \"-Changes\");\n\n  await redisSubscriber.Subscribe(channelMessage =\u003e\n  {\n    var tableDependencyStatus = JsonConvert.DeserializeObject\u003cTableDependencyStatus\u003e(channelMessage.Message);\n    Console.WriteLine($\"OnNext tableDependencyStatus changed({tableDependencyStatus})\");\n  }, nameof(Product) + \"-Status\");\n\n  redisSubscriber.WhenIsConnectedChanges.Subscribe(c =\u003e Console.WriteLine($\"REDIS is connected: {c}\"));\n\n  return redisSubscriber;\n}\n```\n\n# How to put it all together\n[Try out samples](https://github.com/tomasfabian/Joker/wiki/Samples)\n\n```C#\n    private static void CreateReactiveProductsViewModel()\n    {\n      var reactiveData = new ReactiveData\u003cProduct\u003e();\n      var redisUrl = ConfigurationManager.AppSettings[\"RedisUrl\"];\n      using var entitiesSubscriber = new DomainEntitiesSubscriber\u003cProduct\u003e(new RedisSubscriber(redisUrl), reactiveData);\n\n      string connectionString = ConfigurationManager.ConnectionStrings[\"FargoEntities\"].ConnectionString;\n\n      var reactiveProductsViewModel = new ReactiveProductsViewModel(new SampleDbContext(connectionString),\n        reactiveData, new WpfSchedulersFactory());\n\n      reactiveProductsViewModel.SubscribeToDataChanges();\n\n      reactiveProductsViewModel.Dispose();\n    }\n```\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/tomasfabian)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasfabian%2Fjoker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomasfabian%2Fjoker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasfabian%2Fjoker/lists"}