30 | Swapping to the Development environment displays detailed information about the error that occurred.
31 |
32 |
33 | The Development environment shouldn't be enabled for deployed applications.
34 | It can result in displaying sensitive information from exceptions to end users.
35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
36 | and restarting the app.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Pages/Error.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 |
5 | namespace Foundatio.SampleApp.Server.Pages;
6 |
7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
8 | [IgnoreAntiforgeryToken]
9 | public class ErrorModel : PageModel
10 | {
11 | public string? RequestId { get; set; }
12 |
13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
14 |
15 | private readonly ILogger _logger;
16 |
17 | public ErrorModel(ILogger logger)
18 | {
19 | _logger = logger;
20 | }
21 |
22 | public void OnGet()
23 | {
24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Program.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Extensions.Hosting.Startup;
2 | using Foundatio.Repositories;
3 | using Foundatio.SampleApp.Server.Repositories;
4 | using Foundatio.SampleApp.Server.Repositories.Configuration;
5 | using Foundatio.SampleApp.Shared;
6 |
7 | var builder = WebApplication.CreateBuilder(args);
8 |
9 | // Add services to the container.
10 |
11 | builder.Services.AddControllersWithViews();
12 | builder.Services.AddRazorPages();
13 |
14 | // add elastic configuration and repository to DI
15 | builder.Services.AddSingleton();
16 | builder.Services.AddSingleton();
17 |
18 | // configure the elasticsearch indexes
19 | builder.Services.AddConfigureIndexesStartupAction();
20 |
21 | // add sample data if there is none
22 | builder.Services.AddSampleDataStartupAction();
23 |
24 | var app = builder.Build();
25 |
26 | // Configure the HTTP request pipeline.
27 | if (app.Environment.IsDevelopment())
28 | {
29 | app.UseWebAssemblyDebugging();
30 | }
31 | else
32 | {
33 | app.UseExceptionHandler("/Error");
34 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
35 | app.UseHsts();
36 | }
37 |
38 | app.UseHttpsRedirection();
39 |
40 | app.UseBlazorFrameworkFiles();
41 | app.UseStaticFiles();
42 |
43 | app.UseRouting();
44 |
45 | app.MapRazorPages();
46 |
47 | app.UseWaitForStartupActionsBeforeServingRequests();
48 |
49 | // add endpoint to get game reviews
50 | app.MapGet("GameReviews", async (IGameReviewRepository gameReviewRepository, string? search, string? filter, string? sort, int? page, int? limit, string? fields, string? aggs) =>
51 | {
52 | var reviews = await gameReviewRepository.FindAsync(q => q
53 | .FilterExpression(filter)
54 | .SortExpression(sort)
55 | .SearchExpression(search)
56 | .IncludeMask(fields)
57 | .AggregationsExpression(aggs ?? "terms:category terms:tags"),
58 | o => o.PageNumber(page).PageLimit(limit).QueryLogLevel(LogLevel.Warning));
59 |
60 | return GameReviewSearchResult.From(reviews);
61 | });
62 |
63 | app.MapFallbackToFile("index.html");
64 |
65 | app.Run();
66 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:22157",
7 | "sslPort": 44393
8 | }
9 | },
10 | "profiles": {
11 | "http": {
12 | "commandName": "Project",
13 | "dotnetRunMessages": true,
14 | "launchBrowser": true,
15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
16 | "applicationUrl": "http://localhost:5154",
17 | "environmentVariables": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | }
20 | },
21 | "https": {
22 | "commandName": "Project",
23 | "dotnetRunMessages": true,
24 | "launchBrowser": true,
25 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
26 | "applicationUrl": "https://localhost:7188;http://localhost:5154",
27 | "environmentVariables": {
28 | "ASPNETCORE_ENVIRONMENT": "Development"
29 | }
30 | },
31 | "IIS Express": {
32 | "commandName": "IISExpress",
33 | "launchBrowser": true,
34 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
35 | "environmentVariables": {
36 | "ASPNETCORE_ENVIRONMENT": "Development"
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Repositories/Configuration/ElasticExtensions.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Extensions.Hosting.Startup;
2 | using Foundatio.SampleApp.Shared;
3 |
4 | namespace Foundatio.SampleApp.Server.Repositories.Configuration;
5 |
6 | public static class ElasticExtensions
7 | {
8 | public static IServiceCollection AddConfigureIndexesStartupAction(this IServiceCollection services)
9 | {
10 | // configure the elasticsearch indexes
11 | services.AddStartupAction("ConfigureIndexes", async sp =>
12 | {
13 | var configuration = sp.GetRequiredService();
14 | await configuration.ConfigureIndexesAsync(beginReindexingOutdated: false);
15 | });
16 |
17 | return services;
18 | }
19 |
20 | public static IServiceCollection AddSampleDataStartupAction(this IServiceCollection services)
21 | {
22 | services.AddStartupAction("ConfigureIndexes", async sp =>
23 | {
24 | // add some sample data if there is none
25 | var repository = sp.GetRequiredService();
26 | if (await repository.CountAsync() is { Total: 0 })
27 | {
28 | await repository.AddAsync(new GameReview
29 | {
30 | Name = "Super Mario Bros",
31 | Description = "Super Mario Bros is a platform video game developed and published by Nintendo.",
32 | Category = "Adventure",
33 | Tags = new[] { "Highly Rated", "Single Player" }
34 | });
35 |
36 | var categories = new[] { "Action", "Adventure", "Sports", "Racing", "RPG", "Strategy", "Simulation", "Puzzle", "Shooter" };
37 | var tags = new[] { "Highly Rated", "New", "Multiplayer", "Single Player", "Co-op", "Online", "Local", "Multi-Platform", "VR", "Free to Play" };
38 |
39 | for (int i = 0; i < 100; i++)
40 | {
41 | var category = categories[Random.Shared.Next(0, categories.Length)];
42 |
43 | var selectedTags = new List();
44 | for (int x = 0; x < 5; x++)
45 | selectedTags.Add(tags[Random.Shared.Next(0, tags.Length)]);
46 |
47 | await repository.AddAsync(new GameReview
48 | {
49 | Name = $"Test Game {i}",
50 | Description = $"This is a test game {i} review.",
51 | Category = category,
52 | Tags = selectedTags
53 | });
54 | }
55 | }
56 | });
57 |
58 | return services;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Repositories/Configuration/Indexes/GameReviewIndex.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Repositories.Elasticsearch.Configuration;
2 | using Foundatio.Repositories.Elasticsearch.Extensions;
3 | using Foundatio.SampleApp.Shared;
4 | using Nest;
5 |
6 | namespace Foundatio.SampleApp.Server.Repositories.Indexes;
7 |
8 | public sealed class GameReviewIndex : VersionedIndex
9 | {
10 | public GameReviewIndex(IElasticConfiguration configuration) : base(configuration, "gamereview", version: 1) { }
11 |
12 | public override CreateIndexDescriptor ConfigureIndex(CreateIndexDescriptor idx)
13 | {
14 | return base.ConfigureIndex(idx.Settings(s => s
15 | .Analysis(a => a.AddSortNormalizer())
16 | .NumberOfReplicas(0)
17 | .NumberOfShards(1)));
18 | }
19 |
20 | public override TypeMappingDescriptor ConfigureIndexMapping(TypeMappingDescriptor map)
21 | {
22 | // adding new fields will automatically update the index mapping
23 | // changing existing fields requires a new index version and a reindex
24 | return map
25 | .Dynamic(false)
26 | .Properties(p => p
27 | .SetupDefaults()
28 | .Text(f => f.Name(e => e.Name).AddKeywordAndSortFields())
29 | .Text(f => f.Name(e => e.Description))
30 | .Text(f => f.Name(e => e.Category).AddKeywordAndSortFields())
31 | .Text(f => f.Name(e => e.Tags).AddKeywordAndSortFields())
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Repositories/Configuration/SampleAppElasticConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Elasticsearch.Net;
2 | using Foundatio.Repositories.Elasticsearch.Configuration;
3 | using Foundatio.SampleApp.Server.Repositories.Indexes;
4 | using Nest;
5 |
6 | namespace Foundatio.SampleApp.Server.Repositories;
7 |
8 | public class SampleAppElasticConfiguration : ElasticConfiguration
9 | {
10 | private string _connectionString;
11 | private IWebHostEnvironment _env;
12 |
13 | public SampleAppElasticConfiguration(IConfiguration config, IWebHostEnvironment env, ILoggerFactory loggerFactory) : base(loggerFactory: loggerFactory)
14 | {
15 | _connectionString = config.GetConnectionString("ElasticsearchConnectionString") ?? "http://localhost:9200";
16 | _env = env;
17 | AddIndex(GameReviews = new GameReviewIndex(this));
18 | }
19 |
20 | protected override IConnectionPool CreateConnectionPool()
21 | {
22 | return new SingleNodeConnectionPool(new Uri(_connectionString));
23 | }
24 |
25 | protected override void ConfigureSettings(ConnectionSettings settings)
26 | {
27 | // only do this in test and dev mode to enable better debug logging
28 | if (_env.IsDevelopment())
29 | settings.DisableDirectStreaming().PrettyJson();
30 |
31 | base.ConfigureSettings(settings);
32 | }
33 |
34 | public GameReviewIndex GameReviews { get; }
35 | }
36 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/Repositories/GameReviewRepository.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Repositories;
2 | using Foundatio.Repositories.Elasticsearch;
3 | using Foundatio.SampleApp.Shared;
4 |
5 | namespace Foundatio.SampleApp.Server.Repositories;
6 |
7 | public interface IGameReviewRepository : ISearchableRepository { }
8 |
9 | public class GameReviewRepository : ElasticRepositoryBase, IGameReviewRepository
10 | {
11 | public GameReviewRepository(SampleAppElasticConfiguration configuration) : base(configuration.GameReviews) { }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Server/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "ElasticsearchConnectionString": "http://localhost:9200/"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft.AspNetCore": "Warning"
9 | }
10 | },
11 | "AllowedHosts": "*"
12 | }
13 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Shared/Foundatio.SampleApp.Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/Foundatio.SampleApp/Shared/GameReview.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Repositories.Models;
2 |
3 | namespace Foundatio.SampleApp.Shared;
4 |
5 | public class GameReview : IIdentity, IHaveDates
6 | {
7 | public string Id { get; set; } = String.Empty;
8 | public string Name { get; set; } = String.Empty;
9 | public string? Description { get; set; }
10 | public string Category { get; set; } = "General";
11 | public ICollection Tags { get; set; } = new List();
12 | public DateTime UpdatedUtc { get; set; }
13 | public DateTime CreatedUtc { get; set; }
14 | }
15 |
16 | public class GameReviewSearchResult
17 | {
18 | public IReadOnlyCollection Reviews { get; set; } = new List();
19 | public long Total { get; set; }
20 | public ICollection CategoryCounts { get; set; } = new List();
21 | public ICollection TagCounts { get; set; } = new List();
22 |
23 | public static GameReviewSearchResult From(FindResults results)
24 | {
25 | var categoryCounts = results.Aggregations.Terms("terms_category")?.Buckets.Select(t => new AggCount { Name = t.Key, Total = t.Total }).ToList() ?? new List();
26 | var tagCounts = results.Aggregations.Terms("terms_tags")?.Buckets.Select(t => new AggCount { Name = t.Key, Total = t.Total }).ToList() ?? new List();
27 |
28 | return new GameReviewSearchResult
29 | {
30 | Reviews = results.Documents,
31 | Total = results.Total,
32 | CategoryCounts = categoryCounts,
33 | TagCounts = tagCounts
34 | };
35 | }
36 | }
37 |
38 | public class AggCount
39 | {
40 | public string Name { get; set; } = String.Empty;
41 | public long? Total { get; set; }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Configuration/DynamicIndex.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Parsers.ElasticQueries;
2 | using Foundatio.Repositories.Elasticsearch.Extensions;
3 | using Nest;
4 |
5 | namespace Foundatio.Repositories.Elasticsearch.Configuration;
6 |
7 | public class DynamicIndex : Index where T : class
8 | {
9 | public DynamicIndex(IElasticConfiguration configuration, string name = null) : base(configuration, name) { }
10 |
11 | protected override ElasticMappingResolver CreateMappingResolver()
12 | {
13 | return ElasticMappingResolver.Create(ConfigureIndexMapping, Configuration.Client, Name, _logger);
14 | }
15 |
16 | public override TypeMappingDescriptor ConfigureIndexMapping(TypeMappingDescriptor map)
17 | {
18 | return map.Dynamic().AutoMap().Properties(p => p.SetupDefaults());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Configuration/IElasticConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Foundatio.Caching;
5 | using Foundatio.Messaging;
6 | using Foundatio.Parsers.ElasticQueries;
7 | using Foundatio.Repositories.Elasticsearch.CustomFields;
8 | using Foundatio.Repositories.Elasticsearch.Queries.Builders;
9 | using Microsoft.Extensions.Logging;
10 | using Nest;
11 |
12 | namespace Foundatio.Repositories.Elasticsearch.Configuration;
13 |
14 | public interface IElasticConfiguration : IDisposable
15 | {
16 | IElasticClient Client { get; }
17 | ICacheClient Cache { get; }
18 | IMessageBus MessageBus { get; }
19 | ILoggerFactory LoggerFactory { get; }
20 | TimeProvider TimeProvider { get; set; }
21 | IReadOnlyCollection Indexes { get; }
22 | ICustomFieldDefinitionRepository CustomFieldDefinitionRepository { get; }
23 |
24 | IIndex GetIndex(string name);
25 | void ConfigureGlobalQueryBuilders(ElasticQueryBuilder builder);
26 | void ConfigureGlobalQueryParsers(ElasticQueryParserConfiguration config);
27 | Task ConfigureIndexesAsync(IEnumerable indexes = null, bool beginReindexingOutdated = true);
28 | Task MaintainIndexesAsync(IEnumerable indexes = null);
29 | Task DeleteIndexesAsync(IEnumerable indexes = null);
30 | Task ReindexAsync(IEnumerable indexes = null, Func progressCallbackAsync = null);
31 | }
32 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Configuration/IIndex.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 | using System.Threading.Tasks;
5 | using Foundatio.Parsers.ElasticQueries;
6 | using Foundatio.Repositories.Elasticsearch.CustomFields;
7 | using Foundatio.Repositories.Elasticsearch.Queries.Builders;
8 | using Nest;
9 |
10 | namespace Foundatio.Repositories.Elasticsearch.Configuration;
11 |
12 | public interface IIndex : IDisposable
13 | {
14 | string Name { get; }
15 | bool HasMultipleIndexes { get; }
16 | IElasticQueryBuilder QueryBuilder { get; }
17 | ElasticMappingResolver MappingResolver { get; }
18 | ElasticQueryParser QueryParser { get; }
19 | IElasticConfiguration Configuration { get; }
20 | IDictionary CustomFieldTypes { get; }
21 |
22 | void ConfigureSettings(ConnectionSettings settings);
23 | Task ConfigureAsync();
24 | Task EnsureIndexAsync(object target);
25 | Task MaintainAsync(bool includeOptionalTasks = true);
26 | Task DeleteAsync();
27 | Task ReindexAsync(Func progressCallbackAsync = null);
28 | string CreateDocumentId(object document);
29 | string[] GetIndexesByQuery(IRepositoryQuery query);
30 | string GetIndex(object target);
31 | }
32 |
33 | public interface IIndex : IIndex where T : class
34 | {
35 | TypeMappingDescriptor ConfigureIndexMapping(TypeMappingDescriptor map);
36 | Inferrer Infer { get; }
37 | string InferField(Expression> objectPath);
38 | string InferPropertyName(Expression> objectPath);
39 | }
40 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/ICustomFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public interface ICustomFieldType
7 | {
8 | string Type { get; }
9 | Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class;
10 | IProperty ConfigureMapping(SingleMappingSelector map) where T : class;
11 | }
12 |
13 | public class ProcessFieldValueResult
14 | {
15 | public object Value { get; set; }
16 | public object Idx { get; set; }
17 | public bool IsCustomFieldDefinitionModified { get; set; }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/BooleanFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class BooleanFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "bool";
9 | public string Type => "bool";
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Boolean(mp => mp);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/DateFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class DateFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "date";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Date(mp => mp);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/DoubleFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class DoubleFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "double";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Number(mp => mp.Type(NumberType.Double));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/FloatFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class FloatFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "float";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Number(mp => mp.Type(NumberType.Float));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/IntegerFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class IntegerFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "int";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Number(mp => mp.Type(NumberType.Integer));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/KeywordFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class KeywordFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "keyword";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Keyword(mp => mp);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/LongFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class LongFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "long";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Number(mp => mp.Type(NumberType.Long));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/CustomFields/StandardFieldTypes/StringFieldType.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.CustomFields;
5 |
6 | public class StringFieldType : ICustomFieldType
7 | {
8 | public static string IndexType = "string";
9 | public string Type => IndexType;
10 |
11 | public virtual Task ProcessValueAsync(T document, object value, CustomFieldDefinition fieldDefinition) where T : class
12 | {
13 | return Task.FromResult(new ProcessFieldValueResult { Value = value });
14 | }
15 |
16 | public virtual IProperty ConfigureMapping(SingleMappingSelector map) where T : class
17 | {
18 | return map.Text(mp => mp.AddKeywordField());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Extensions/CollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Foundatio.Repositories.Elasticsearch.Extensions;
6 |
7 | #if NETSTANDARD
8 | internal static class CollectionExtensions
9 | {
10 | public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector)
11 | {
12 | var knownKeys = new HashSet();
13 | foreach (TSource element in source)
14 | {
15 | if (knownKeys.Add(keySelector(element)))
16 | {
17 | yield return element;
18 | }
19 | }
20 | }
21 |
22 | public static IEnumerable> Chunk(this IEnumerable source, int size)
23 | {
24 | T[] bucket = null;
25 | int count = 0;
26 |
27 | foreach (var item in source)
28 | {
29 | if (bucket == null)
30 | bucket = new T[size];
31 |
32 | bucket[count++] = item;
33 |
34 | if (count != size)
35 | continue;
36 |
37 | yield return bucket.Select(x => x);
38 |
39 | bucket = null;
40 | count = 0;
41 | }
42 |
43 | // Return the last bucket with all remaining elements
44 | if (bucket != null && count > 0)
45 | {
46 | Array.Resize(ref bucket, count);
47 | yield return bucket.Select(x => x);
48 | }
49 | }
50 | }
51 | #endif
52 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Extensions/ElasticLazyDocument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using Elasticsearch.Net;
5 | using Nest;
6 | using ILazyDocument = Foundatio.Repositories.Models.ILazyDocument;
7 |
8 | namespace Foundatio.Repositories.Elasticsearch.Extensions;
9 |
10 | public class ElasticLazyDocument : ILazyDocument
11 | {
12 | private readonly Nest.ILazyDocument _inner;
13 | private IElasticsearchSerializer _requestResponseSerializer;
14 |
15 | public ElasticLazyDocument(Nest.ILazyDocument inner)
16 | {
17 | _inner = inner;
18 | }
19 |
20 | private static readonly Lazy> _getSerializer =
21 | new(() =>
22 | {
23 | var serializerField = typeof(Nest.LazyDocument).GetField("_requestResponseSerializer", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
24 | return lazyDocument =>
25 | {
26 | var d = lazyDocument as Nest.LazyDocument;
27 | if (d == null)
28 | return null;
29 |
30 | var serializer = serializerField?.GetValue(d) as IElasticsearchSerializer;
31 | return serializer;
32 | };
33 | });
34 |
35 | private static readonly Lazy> _getBytes =
36 | new(() =>
37 | {
38 | var bytesProperty = typeof(Nest.LazyDocument).GetProperty("Bytes", BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);
39 | return lazyDocument =>
40 | {
41 | var d = lazyDocument as Nest.LazyDocument;
42 | if (d == null)
43 | return null;
44 |
45 | var bytes = bytesProperty?.GetValue(d) as byte[];
46 | return bytes;
47 | };
48 | });
49 |
50 | public T As() where T : class
51 | {
52 | if (_requestResponseSerializer == null)
53 | _requestResponseSerializer = _getSerializer.Value(_inner);
54 |
55 | var bytes = _getBytes.Value(_inner);
56 | var hit = _requestResponseSerializer.Deserialize>(new MemoryStream(bytes));
57 | return hit?.Source;
58 | }
59 |
60 | public object As(Type objectType)
61 | {
62 | var hitType = typeof(IHit<>).MakeGenericType(objectType);
63 | return _inner.As(hitType);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Extensions/FindResultsExtensions.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Utility;
2 |
3 | namespace Foundatio.Repositories.Elasticsearch.Extensions;
4 |
5 | public static class FindResultsExtensions
6 | {
7 | public static string GetScrollId(this IHaveData results)
8 | {
9 | return results.Data.GetString(ElasticDataKeys.ScrollId, null);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Extensions/IBodyWithApiCallDetailsExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Text.Json;
3 | using Nest;
4 |
5 | namespace Foundatio.Repositories.Elasticsearch.Extensions;
6 |
7 | internal static class IBodyWithApiCallDetailsExtensions
8 | {
9 | private static readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true, };
10 |
11 | public static T DeserializeRaw(this IResponse call) where T : class, new()
12 | {
13 | if (call?.ApiCall?.ResponseBodyInBytes == null)
14 | return default;
15 |
16 | string rawResponse = Encoding.UTF8.GetString(call.ApiCall.ResponseBodyInBytes);
17 | return JsonSerializer.Deserialize(rawResponse, _options);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Extensions/ResolverExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Foundatio.Parsers.ElasticQueries;
4 | using Nest;
5 |
6 | namespace Foundatio.Repositories.Elasticsearch.Extensions;
7 |
8 | public static class ResolverExtensions
9 | {
10 | public static ICollection GetResolvedFields(this ElasticMappingResolver resolver, ICollection fields)
11 | {
12 | if (fields.Count == 0)
13 | return fields;
14 |
15 | return fields.Select(field => ResolveFieldName(resolver, field)).ToList();
16 | }
17 |
18 | public static ICollection GetResolvedFields(this ElasticMappingResolver resolver, ICollection sorts)
19 | {
20 | if (sorts.Count == 0)
21 | return sorts;
22 |
23 | return sorts.Select(sort => ResolveFieldSort(resolver, sort)).ToList();
24 | }
25 |
26 | public static Field ResolveFieldName(this ElasticMappingResolver resolver, Field field)
27 | {
28 | if (field?.Name == null)
29 | return field;
30 |
31 | return new Field(resolver.GetResolvedField(field.Name), field.Boost, field.Format);
32 | }
33 |
34 | public static IFieldSort ResolveFieldSort(this ElasticMappingResolver resolver, IFieldSort sort)
35 | {
36 | return new FieldSort
37 | {
38 | Field = resolver.GetSortFieldName(sort.SortKey),
39 | IgnoreUnmappedFields = sort.IgnoreUnmappedFields,
40 | Missing = sort.Missing,
41 | Mode = sort.Mode,
42 | Nested = sort.Nested,
43 | NumericType = sort.NumericType,
44 | Order = sort.Order,
45 | UnmappedType = sort.UnmappedType
46 | };
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Foundatio.Repositories.Elasticsearch.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Jobs/ElasticMigrationJob.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using Foundatio.Jobs;
5 | using Foundatio.Repositories.Elasticsearch.Configuration;
6 | using Foundatio.Repositories.Extensions;
7 | using Foundatio.Repositories.Migrations;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace Foundatio.Repositories.Elasticsearch.Jobs;
11 |
12 | [Job(Description = "Runs any pending system migrations and reindexing tasks.", IsContinuous = false)]
13 | public abstract class ElasticMigrationJobBase : JobBase
14 | {
15 | protected readonly IElasticConfiguration _configuration;
16 | protected readonly Lazy _migrationManager;
17 |
18 | public ElasticMigrationJobBase(MigrationManager migrationManager, IElasticConfiguration configuration, ILoggerFactory loggerFactory = null)
19 | : base(loggerFactory)
20 | {
21 | _migrationManager = new Lazy(() =>
22 | {
23 | Configure(migrationManager);
24 | return migrationManager;
25 | });
26 | _configuration = configuration;
27 | }
28 |
29 | protected virtual void Configure(MigrationManager manager) { }
30 |
31 | public MigrationManager MigrationManager => _migrationManager.Value;
32 |
33 | protected override async Task RunInternalAsync(JobContext context)
34 | {
35 | await _configuration.ConfigureIndexesAsync(null, false).AnyContext();
36 |
37 | await _migrationManager.Value.RunMigrationsAsync().AnyContext();
38 |
39 | var tasks = _configuration.Indexes.OfType().Select(ReindexIfNecessary);
40 | await Task.WhenAll(tasks).AnyContext();
41 |
42 | return JobResult.Success;
43 | }
44 |
45 | private async Task ReindexIfNecessary(IVersionedIndex index)
46 | {
47 | if (index.Version != await index.GetCurrentVersionAsync().AnyContext())
48 | await index.ReindexAsync().AnyContext();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Jobs/MaintainIndexesJob.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Foundatio.Jobs;
6 | using Foundatio.Lock;
7 | using Foundatio.Repositories.Elasticsearch.Configuration;
8 | using Foundatio.Repositories.Extensions;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Extensions.Logging.Abstractions;
11 |
12 | namespace Foundatio.Repositories.Elasticsearch.Jobs;
13 |
14 | public class MaintainIndexesJob : IJob
15 | {
16 | private readonly IElasticConfiguration _configuration;
17 | private readonly ILogger _logger;
18 | private readonly ILockProvider _lockProvider;
19 |
20 | public MaintainIndexesJob(IElasticConfiguration configuration, ILockProvider lockProvider, ILoggerFactory loggerFactory)
21 | {
22 | _configuration = configuration;
23 | _lockProvider = lockProvider;
24 | _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;
25 | }
26 |
27 | public virtual async Task RunAsync(CancellationToken cancellationToken = default)
28 | {
29 | _logger.LogInformation("Starting index maintenance...");
30 |
31 | var sw = Stopwatch.StartNew();
32 | try
33 | {
34 | bool success = await _lockProvider.TryUsingAsync("es-maintain-indexes", async t =>
35 | {
36 | _logger.LogInformation("Got lock to maintain indexes");
37 | await _configuration.MaintainIndexesAsync().AnyContext();
38 | sw.Stop();
39 |
40 | await OnCompleted(sw.Elapsed).AnyContext();
41 | }, TimeSpan.FromMinutes(30), cancellationToken).AnyContext();
42 |
43 | if (!success)
44 | _logger.LogInformation("Unable to acquire index maintenance lock.");
45 |
46 | }
47 | catch (Exception ex)
48 | {
49 | sw.Stop();
50 | await OnFailure(sw.Elapsed, ex).AnyContext();
51 | throw;
52 | }
53 |
54 | return JobResult.Success;
55 | }
56 |
57 | public virtual Task OnFailure(TimeSpan duration, Exception ex)
58 | {
59 | _logger.LogError(ex, "Failed to maintain indexes after {Duration:g}: {Message}", duration, ex?.Message);
60 | return Task.FromResult(true);
61 | }
62 |
63 | public virtual Task OnCompleted(TimeSpan duration)
64 | {
65 | _logger.LogInformation("Finished index maintenance in {Duration:g}.", duration);
66 | return Task.CompletedTask;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Jobs/ReindexWorkItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foundatio.Repositories.Elasticsearch.Jobs;
4 |
5 | public class ReindexWorkItem
6 | {
7 | public string OldIndex { get; set; }
8 | public string NewIndex { get; set; }
9 | public string Alias { get; set; }
10 | public string Script { get; set; }
11 | public bool DeleteOld { get; set; }
12 | public string TimestampField { get; set; }
13 | public DateTime? StartUtc { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Jobs/ReindexWorkItemHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Foundatio.Jobs;
5 | using Foundatio.Lock;
6 | using Microsoft.Extensions.Logging;
7 | using Nest;
8 |
9 | namespace Foundatio.Repositories.Elasticsearch.Jobs;
10 |
11 | public class ReindexWorkItemHandler : WorkItemHandlerBase
12 | {
13 | private readonly ElasticReindexer _reindexer;
14 | private readonly ILockProvider _lockProvider;
15 |
16 | public ReindexWorkItemHandler(IElasticClient client, ILockProvider lockProvider, ILoggerFactory loggerFactory = null)
17 | {
18 | _reindexer = new ElasticReindexer(client, loggerFactory.CreateLogger());
19 | _lockProvider = lockProvider;
20 | AutoRenewLockOnProgress = true;
21 | }
22 |
23 | public override Task GetWorkItemLockAsync(object workItem, CancellationToken cancellationToken = default)
24 | {
25 | if (workItem is not ReindexWorkItem reindexWorkItem)
26 | return null;
27 |
28 | return _lockProvider.AcquireAsync(String.Join(":", "reindex", reindexWorkItem.Alias, reindexWorkItem.OldIndex, reindexWorkItem.NewIndex), TimeSpan.FromMinutes(20), cancellationToken);
29 | }
30 |
31 | public override Task HandleItemAsync(WorkItemContext context)
32 | {
33 | var workItem = context.GetData();
34 | return _reindexer.ReindexAsync(workItem, context.ReportProgressAsync);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/AggregationsQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Foundatio.Parsers.ElasticQueries.Extensions;
4 | using Foundatio.Repositories.Extensions;
5 | using Foundatio.Repositories.Options;
6 |
7 | namespace Foundatio.Repositories
8 | {
9 | public static class AggregationQueryExtensions
10 | {
11 | internal const string AggregationsKey = "@AggregationsExpressionKey";
12 |
13 | public static T AggregationsExpression(this T options, string aggregations) where T : IRepositoryQuery
14 | {
15 | return options.BuildOption(AggregationsKey, aggregations);
16 | }
17 | }
18 | }
19 |
20 | namespace Foundatio.Repositories.Options
21 | {
22 | public static class ReadAggregationQueryExtensions
23 | {
24 | public static string GetAggregationsExpression(this IRepositoryQuery query)
25 | {
26 | return query.SafeGetOption(AggregationQueryExtensions.AggregationsKey, null);
27 | }
28 | }
29 | }
30 |
31 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders
32 | {
33 | public class AggregationsQueryBuilder : IElasticQueryBuilder
34 | {
35 | public async Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
36 | {
37 | var elasticIndex = ctx.Options.GetElasticIndex();
38 | if (elasticIndex?.QueryParser == null)
39 | return;
40 |
41 | string aggregations = ctx.Source.GetAggregationsExpression();
42 | if (String.IsNullOrEmpty(aggregations))
43 | return;
44 |
45 | var result = await elasticIndex.QueryParser.BuildAggregationsAsync(aggregations, ctx).AnyContext();
46 | ctx.Search.Aggregations(result);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/ElasticFilterQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using Foundatio.Repositories.Options;
4 | using Nest;
5 |
6 | namespace Foundatio.Repositories
7 | {
8 | public static class ElasticFilterQueryExtensions
9 | {
10 | internal const string ElasticFiltersKey = "@ElasticFilters";
11 |
12 | public static T ElasticFilter(this T query, QueryContainer filter) where T : IRepositoryQuery
13 | {
14 | return query.AddCollectionOptionValue(ElasticFiltersKey, filter);
15 | }
16 | }
17 | }
18 |
19 | namespace Foundatio.Repositories.Options
20 | {
21 | public static class ReadElasticFilterQueryExtensions
22 | {
23 | public static ICollection GetElasticFilters(this IRepositoryQuery query)
24 | {
25 | return query.SafeGetCollection(ElasticFilterQueryExtensions.ElasticFiltersKey);
26 | }
27 | }
28 | }
29 |
30 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders
31 | {
32 | public class ElasticFilterQueryBuilder : IElasticQueryBuilder
33 | {
34 | public Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
35 | {
36 | var elasticFilters = ctx.Source.GetElasticFilters();
37 | if (elasticFilters.Count == 0)
38 | return Task.CompletedTask;
39 |
40 | foreach (var filter in elasticFilters)
41 | ctx.Filter &= filter;
42 |
43 | return Task.CompletedTask;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/IdentityQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Foundatio.Repositories.Queries;
4 | using Nest;
5 |
6 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders;
7 |
8 | public class IdentityQueryBuilder : IElasticQueryBuilder
9 | {
10 | public Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
11 | {
12 | var ids = ctx.Source.GetIds();
13 | if (ids.Count > 0)
14 | ctx.Filter &= new IdsQuery { Values = ids.Select(id => new Nest.Id(id)) };
15 |
16 | var excludesIds = ctx.Source.GetExcludedIds();
17 | if (excludesIds.Count > 0)
18 | ctx.Filter &= !new IdsQuery { Values = excludesIds.Select(id => new Nest.Id(id)) };
19 |
20 | return Task.CompletedTask;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/PageableQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Foundatio.Repositories.Options;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders;
5 |
6 | public class PageableQueryBuilder : IElasticQueryBuilder
7 | {
8 | public Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
9 | {
10 | int limit = ctx.Options.GetLimit();
11 | if (limit >= ctx.Options.GetMaxLimit() || ctx.Options.ShouldUseSnapshotPaging())
12 | {
13 | ctx.Search.Size(limit);
14 | }
15 | else
16 | {
17 | // add 1 to limit if not snapshot paging so we can know if we have more results
18 | ctx.Search.Size(limit + 1);
19 | }
20 |
21 | // can only use search_after or skip
22 | if (ctx.Options.HasSearchAfter())
23 | ctx.Search.SearchAfter(ctx.Options.GetSearchAfter());
24 | else if (ctx.Options.HasSearchBefore())
25 | ctx.Search.SearchAfter(ctx.Options.GetSearchBefore());
26 | else if (ctx.Options.ShouldUseSkip())
27 | ctx.Search.Skip(ctx.Options.GetSkip());
28 |
29 | return Task.CompletedTask;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/SoftDeletesQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Foundatio.Repositories.Models;
3 | using Foundatio.Repositories.Options;
4 | using Nest;
5 |
6 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders;
7 |
8 | public class SoftDeletesQueryBuilder : IElasticQueryBuilder
9 | {
10 | public Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
11 | {
12 | // TODO: Figure out how to automatically add parent filter for soft deletes on queries that have a parent document type
13 |
14 | // dont add filter to child query system filters
15 | if (ctx.Parent != null)
16 | return Task.CompletedTask;
17 |
18 | // get soft delete mode, use parent query as default if it exists
19 | var mode = ctx.Options.GetSoftDeleteMode(ctx.Parent?.Options?.GetSoftDeleteMode() ?? SoftDeleteQueryMode.ActiveOnly);
20 |
21 | // no filter needed if we want all
22 | if (mode == SoftDeleteQueryMode.All)
23 | return Task.CompletedTask;
24 |
25 | // check to see if the model supports soft deletes
26 | if (!ctx.Options.SupportsSoftDeletes())
27 | return Task.CompletedTask;
28 |
29 | var documentType = ctx.Options.DocumentType();
30 | var property = documentType.GetProperty(nameof(ISupportSoftDeletes.IsDeleted));
31 | var index = ctx.Options.GetElasticIndex();
32 |
33 | string fieldName = property != null ? index?.Configuration.Client.Infer.Field(new Field(property)) ?? "isDeleted" : "isDeleted";
34 | if (mode == SoftDeleteQueryMode.ActiveOnly)
35 | ctx.Filter &= new TermQuery { Field = fieldName, Value = false };
36 | else if (mode == SoftDeleteQueryMode.DeletedOnly)
37 | ctx.Filter &= new TermQuery { Field = fieldName, Value = true };
38 |
39 | var parentType = ctx.Options.ParentDocumentType();
40 | if (parentType != null && parentType != typeof(object))
41 | ctx.Filter &= new HasParentQuery
42 | {
43 | ParentType = parentType,
44 | Query = new BoolQuery
45 | {
46 | Filter = new[] { new QueryContainer(new TermQuery { Field = fieldName, Value = false }) }
47 | }
48 | };
49 |
50 | return Task.CompletedTask;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/Builders/SortQueryBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq.Expressions;
4 | using System.Threading.Tasks;
5 | using Foundatio.Parsers.ElasticQueries.Extensions;
6 | using Foundatio.Repositories.Elasticsearch.Extensions;
7 | using Foundatio.Repositories.Options;
8 | using Nest;
9 |
10 | namespace Foundatio.Repositories
11 | {
12 | public static class SortQueryExtensions
13 | {
14 | internal const string SortsKey = "@SortsKey";
15 |
16 | public static T Sort(this T query, Field field, SortOrder? order = null) where T : IRepositoryQuery
17 | {
18 | return query.AddCollectionOptionValue(SortsKey, new FieldSort { Field = field, Order = order });
19 | }
20 |
21 | public static T SortDescending(this T query, Field field) where T : IRepositoryQuery
22 | {
23 | return query.Sort(field, SortOrder.Descending);
24 | }
25 |
26 | public static T SortAscending(this T query, Field field) where T : IRepositoryQuery
27 | {
28 | return query.Sort(field, SortOrder.Ascending);
29 | }
30 |
31 | public static IRepositoryQuery Sort(this IRepositoryQuery query, Expression> objectPath, SortOrder? order = null) where T : class
32 | {
33 | return query.AddCollectionOptionValue, IFieldSort>(SortsKey, new FieldSort { Field = objectPath, Order = order });
34 | }
35 |
36 | public static IRepositoryQuery SortDescending(this IRepositoryQuery query, Expression> objectPath) where T : class
37 | {
38 | return query.Sort(objectPath, SortOrder.Descending);
39 | }
40 |
41 | public static IRepositoryQuery SortAscending(this IRepositoryQuery query, Expression> objectPath) where T : class
42 | {
43 | return query.Sort(objectPath, SortOrder.Ascending);
44 | }
45 | }
46 | }
47 |
48 | namespace Foundatio.Repositories.Options
49 | {
50 | public static class ReadSortQueryExtensions
51 | {
52 | public static ICollection GetSorts(this IRepositoryQuery query)
53 | {
54 | return query.SafeGetCollection(SortQueryExtensions.SortsKey);
55 | }
56 | }
57 | }
58 |
59 | namespace Foundatio.Repositories.Elasticsearch.Queries.Builders
60 | {
61 | public class SortQueryBuilder : IElasticQueryBuilder
62 | {
63 | public Task BuildAsync(QueryBuilderContext ctx) where T : class, new()
64 | {
65 | var sortFields = ctx.Source.GetSorts();
66 | if (sortFields.Count <= 0)
67 | return Task.CompletedTask;
68 |
69 | var resolver = ctx.GetMappingResolver();
70 | sortFields = resolver.GetResolvedFields(sortFields);
71 |
72 | ctx.Search.Sort(sortFields);
73 | return Task.CompletedTask;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Queries/QueryParts/ElasticIndexesQuery.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Foundatio.Repositories.Options;
4 |
5 | namespace Foundatio.Repositories
6 | {
7 | public static class ElasticIndexesQueryExtensions
8 | {
9 | internal const string ElasticIndexesKey = "@ElasticIndexes";
10 | public static T Index(this T query, string field) where T : IRepositoryQuery
11 | {
12 | return query.AddCollectionOptionValue(ElasticIndexesKey, field);
13 | }
14 |
15 | public static T Index(this T query, IEnumerable fields) where T : IRepositoryQuery
16 | {
17 | return query.AddCollectionOptionValue(ElasticIndexesKey, fields);
18 | }
19 |
20 | public static T Index(this T query, params string[] fields) where T : IRepositoryQuery
21 | {
22 | return query.AddCollectionOptionValue(ElasticIndexesKey, fields);
23 | }
24 |
25 | internal const string ElasticIndexesStartUtcKey = "@ElasticIndexesStartUtc";
26 | internal const string ElasticIndexesEndUtcKey = "@ElasticIndexesEndUtc";
27 | public static T Index(this T query, DateTime? utcStart, DateTime? utcEnd) where T : IRepositoryQuery
28 | {
29 | if (utcStart.HasValue)
30 | query.Values.Set(ElasticIndexesStartUtcKey, utcStart);
31 |
32 | if (utcEnd.HasValue)
33 | query.Values.Set(ElasticIndexesEndUtcKey, utcEnd);
34 |
35 | return query;
36 | }
37 | }
38 | }
39 |
40 | namespace Foundatio.Repositories.Options
41 | {
42 | public static class ReadElasticIndexesQueryExtensions
43 | {
44 | public static ICollection GetElasticIndexes(this IRepositoryQuery options)
45 | {
46 | return options.SafeGetCollection(ElasticIndexesQueryExtensions.ElasticIndexesKey);
47 | }
48 |
49 | public static DateTime? GetElasticIndexesStartUtc(this IRepositoryQuery options)
50 | {
51 | return options.SafeGetOption(ElasticIndexesQueryExtensions.ElasticIndexesStartUtcKey);
52 | }
53 |
54 | public static DateTime? GetElasticIndexesEndUtc(this IRepositoryQuery options)
55 | {
56 | return options.SafeGetOption(ElasticIndexesQueryExtensions.ElasticIndexesEndUtcKey);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Repositories/IParentChildDocument.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Repositories.Models;
2 | using Nest;
3 |
4 | namespace Foundatio.Repositories.Elasticsearch;
5 |
6 | public interface IParentChildDocument : IIdentity
7 | {
8 | string ParentId { get; set; }
9 | JoinField Discriminator { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories.Elasticsearch/Repositories/MigrationStateRepository.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Repositories.Elasticsearch.Configuration;
2 | using Foundatio.Repositories.Migrations;
3 | using Nest;
4 |
5 | namespace Foundatio.Repositories.Elasticsearch;
6 |
7 | public class MigrationStateRepository : ElasticRepositoryBase, IMigrationStateRepository
8 | {
9 | public MigrationStateRepository(MigrationIndex index) : base(index)
10 | {
11 | DisableCache();
12 | DefaultConsistency = Consistency.Immediate;
13 | }
14 | }
15 |
16 | public class MigrationIndex : Index
17 | {
18 | private readonly int _replicas;
19 |
20 | public MigrationIndex(IElasticConfiguration configuration, string name = "migration", int replicas = 1) : base(configuration, name)
21 | {
22 | _replicas = replicas;
23 | }
24 |
25 | public override TypeMappingDescriptor ConfigureIndexMapping(TypeMappingDescriptor map)
26 | {
27 | return map
28 | .Dynamic(false)
29 | .Properties(p => p
30 | .Keyword(f => f.Name(e => e.Id))
31 | .Number(f => f.Name(e => e.Version).Type(NumberType.Integer))
32 | .Date(f => f.Name(e => e.StartedUtc))
33 | .Date(f => f.Name(e => e.CompletedUtc))
34 | );
35 | }
36 |
37 | public override CreateIndexDescriptor ConfigureIndex(CreateIndexDescriptor idx)
38 | {
39 | return base.ConfigureIndex(idx).Settings(s => s
40 | .NumberOfShards(1)
41 | .NumberOfReplicas(_replicas));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/AsyncQueryNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Exceptions;
2 |
3 | public class AsyncQueryNotFoundException : RepositoryException
4 | {
5 | public AsyncQueryNotFoundException() { }
6 |
7 | public AsyncQueryNotFoundException(string id) : base($"Async query \"{id}\" could not be found")
8 | {
9 | Id = id;
10 | }
11 |
12 | public string Id { get; private set; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/DocumentException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foundatio.Repositories.Exceptions;
4 |
5 | public class DocumentException : RepositoryException
6 | {
7 | public DocumentException() : base() { }
8 | public DocumentException(string message) : base(message) { }
9 | public DocumentException(string message, Exception innerException) : base(message, innerException) { }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/DocumentNotFoundException.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Exceptions;
2 |
3 | public class DocumentNotFoundException : DocumentException
4 | {
5 | public DocumentNotFoundException() { }
6 |
7 | public DocumentNotFoundException(string id) : base($"Document \"{id}\" could not be found")
8 | {
9 | Id = id;
10 | }
11 |
12 | public string Id { get; private set; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/DocumentValidationException.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Exceptions;
2 |
3 | public class DocumentValidationException : DocumentException
4 | {
5 | public DocumentValidationException() { }
6 |
7 | public DocumentValidationException(string message) : base(message) { }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/DuplicateDocumentException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foundatio.Repositories.Exceptions;
4 |
5 | public class DuplicateDocumentException : DocumentException
6 | {
7 | public DuplicateDocumentException() : base() { }
8 | public DuplicateDocumentException(string message) : base(message) { }
9 | public DuplicateDocumentException(string message, Exception innerException) : base(message, innerException) { }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/RepositoryException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foundatio.Repositories.Exceptions;
4 |
5 | public class RepositoryException : Exception
6 | {
7 | public RepositoryException() : base() { }
8 | public RepositoryException(string message) : base(message) { }
9 | public RepositoryException(string message, Exception innerException) : base(message, innerException) { }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Exceptions/VersionConflictDocumentException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Foundatio.Repositories.Exceptions;
4 |
5 | public class VersionConflictDocumentException : DocumentException
6 | {
7 | public VersionConflictDocumentException() : base() { }
8 | public VersionConflictDocumentException(string message) : base(message) { }
9 | public VersionConflictDocumentException(string message, Exception innerException) : base(message, innerException) { }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Extensions/FindResultsExtensions.cs:
--------------------------------------------------------------------------------
1 | using Foundatio.Utility;
2 |
3 | namespace Foundatio.Repositories.Extensions;
4 |
5 | public static class FindResultsExtensions
6 | {
7 | public static string GetAsyncQueryId(this IHaveData results)
8 | {
9 | return results.Data.GetString(AsyncQueryDataKeys.AsyncQueryId, null);
10 | }
11 |
12 | ///
13 | /// Whether the query completed successfully or was interrupted (will also be true while the query is still running)
14 | ///
15 | public static bool IsAsyncQueryPartial(this IHaveData results)
16 | {
17 | return results.Data.GetBoolean(AsyncQueryDataKeys.IsPartial, false);
18 | }
19 |
20 | ///
21 | /// Whether the query is still running
22 | ///
23 | public static bool IsAsyncQueryRunning(this IHaveData results)
24 | {
25 | return results.Data.GetBoolean(AsyncQueryDataKeys.IsRunning, false);
26 | }
27 | }
28 |
29 | public static class AsyncQueryDataKeys
30 | {
31 | public const string AsyncQueryId = "@AsyncQueryId";
32 | public const string IsRunning = "@IsRunning";
33 | public const string IsPartial = "@IsPending";
34 | }
35 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Extensions/NumberExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Extensions;
2 |
3 | public static class NumericExtensions
4 | {
5 | public static string ToOrdinal(this int num)
6 | {
7 | switch (num % 100)
8 | {
9 | case 11:
10 | case 12:
11 | case 13:
12 | return num.ToString("#,###0") + "th";
13 | }
14 |
15 | switch (num % 10)
16 | {
17 | case 1:
18 | return num.ToString("#,###0") + "st";
19 | case 2:
20 | return num.ToString("#,###0") + "nd";
21 | case 3:
22 | return num.ToString("#,###0") + "rd";
23 | default:
24 | return num.ToString("#,###0") + "th";
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace Foundatio.Repositories.Extensions;
5 |
6 | public static class StringExtensions
7 | {
8 | public static string ToJTokenPath(this string path)
9 | {
10 | if (path.StartsWith("$"))
11 | return path;
12 |
13 | var sb = new StringBuilder();
14 | string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
15 | for (int i = 0; i < parts.Length; i++)
16 | {
17 | if (parts[i].IsNumeric())
18 | sb.Append("[").Append(parts[i]).Append("]");
19 | else
20 | sb.Append(parts[i]);
21 |
22 | if (i < parts.Length - 1 && !parts[i + 1].IsNumeric())
23 | sb.Append(".");
24 | }
25 |
26 | return sb.ToString();
27 | }
28 |
29 | public static bool IsNumeric(this string value)
30 | {
31 | if (String.IsNullOrEmpty(value))
32 | return false;
33 |
34 | for (int i = 0; i < value.Length; i++)
35 | {
36 | if (Char.IsNumber(value[i]))
37 | continue;
38 |
39 | if (i == 0 && value[i] == '-')
40 | continue;
41 |
42 | return false;
43 | }
44 |
45 | return true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Extensions/TaskExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading.Tasks;
4 |
5 | namespace Foundatio.Repositories.Extensions;
6 |
7 | internal static class TaskHelper
8 | {
9 | [DebuggerStepThrough]
10 | public static ConfiguredTaskAwaitable AnyContext(this Task task)
11 | {
12 | return task.ConfigureAwait(continueOnCapturedContext: false);
13 | }
14 |
15 | [DebuggerStepThrough]
16 | public static ConfiguredTaskAwaitable AnyContext(this Task task)
17 | {
18 | return task.ConfigureAwait(continueOnCapturedContext: false);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Foundatio.Repositories.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/IReadOnlyRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using Foundatio.Repositories.Models;
4 | using Foundatio.Utility;
5 |
6 | namespace Foundatio.Repositories;
7 |
8 | public interface IReadOnlyRepository where T : class, new()
9 | {
10 | Task GetByIdAsync(Id id, CommandOptionsDescriptor options);
11 | Task GetByIdAsync(Id id, ICommandOptions options = null);
12 |
13 | Task> GetByIdsAsync(Ids ids, CommandOptionsDescriptor options);
14 | Task> GetByIdsAsync(Ids ids, ICommandOptions options = null);
15 |
16 | Task> GetAllAsync(CommandOptionsDescriptor options);
17 | Task> GetAllAsync(ICommandOptions options = null);
18 |
19 | Task ExistsAsync(Id id, CommandOptionsDescriptor options);
20 | Task ExistsAsync(Id id, ICommandOptions options = null);
21 |
22 | Task CountAsync(CommandOptionsDescriptor options);
23 | Task CountAsync(ICommandOptions options = null);
24 |
25 | Task InvalidateCacheAsync(T document);
26 | Task InvalidateCacheAsync(IEnumerable documents);
27 |
28 | Task InvalidateCacheAsync(string cacheKey);
29 | Task InvalidateCacheAsync(IEnumerable cacheKeys);
30 |
31 | AsyncEvent> BeforeQuery { get; }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/IRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using Foundatio.Repositories.Models;
4 | using Foundatio.Utility;
5 |
6 | namespace Foundatio.Repositories;
7 |
8 | public interface IRepository : IReadOnlyRepository where T : class, IIdentity, new()
9 | {
10 | Task AddAsync(T document, CommandOptionsDescriptor options);
11 | Task AddAsync(T document, ICommandOptions options = null);
12 |
13 | Task AddAsync(IEnumerable documents, CommandOptionsDescriptor options);
14 | Task AddAsync(IEnumerable documents, ICommandOptions options = null);
15 |
16 | Task SaveAsync(T document, CommandOptionsDescriptor options);
17 | Task SaveAsync(T document, ICommandOptions options = null);
18 |
19 | Task SaveAsync(IEnumerable documents, CommandOptionsDescriptor options);
20 | Task SaveAsync(IEnumerable documents, ICommandOptions options = null);
21 |
22 | Task PatchAsync(Id id, IPatchOperation operation, CommandOptionsDescriptor options);
23 | Task PatchAsync(Id id, IPatchOperation operation, ICommandOptions options = null);
24 |
25 | Task PatchAsync(Ids ids, IPatchOperation operation, CommandOptionsDescriptor options);
26 | Task PatchAsync(Ids ids, IPatchOperation operation, ICommandOptions options = null);
27 |
28 | Task RemoveAsync(Id id, CommandOptionsDescriptor options);
29 | Task RemoveAsync(Id id, ICommandOptions options = null);
30 |
31 | Task RemoveAsync(Ids ids, CommandOptionsDescriptor options);
32 | Task RemoveAsync(Ids ids, ICommandOptions options = null);
33 |
34 | Task RemoveAsync(T document, CommandOptionsDescriptor options);
35 | Task RemoveAsync(T document, ICommandOptions options = null);
36 |
37 | Task RemoveAsync(IEnumerable documents, CommandOptionsDescriptor options);
38 | Task RemoveAsync(IEnumerable documents, ICommandOptions options = null);
39 |
40 | Task RemoveAllAsync(CommandOptionsDescriptor options);
41 | Task RemoveAllAsync(ICommandOptions options = null);
42 |
43 | AsyncEvent> DocumentsAdding { get; }
44 | AsyncEvent> DocumentsAdded { get; }
45 | AsyncEvent> DocumentsSaving { get; }
46 | AsyncEvent> DocumentsSaved { get; }
47 | AsyncEvent> DocumentsRemoving { get; }
48 | AsyncEvent> DocumentsRemoved { get; }
49 | AsyncEvent> DocumentsChanging { get; }
50 | AsyncEvent> DocumentsChanged { get; }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/AbstractPatcher.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Utility;
2 |
3 | public abstract class AbstractPatcher where TDoc : class
4 | {
5 | public virtual void Patch(ref TDoc target, PatchDocument document)
6 | {
7 | foreach (var operation in document.Operations)
8 | {
9 | target = ApplyOperation(operation, target);
10 | }
11 | }
12 |
13 | public virtual TDoc ApplyOperation(Operation operation, TDoc target)
14 | {
15 | if (operation is AddOperation)
16 | Add((AddOperation)operation, target);
17 | else if (operation is CopyOperation)
18 | Copy((CopyOperation)operation, target);
19 | else if (operation is MoveOperation)
20 | Move((MoveOperation)operation, target);
21 | else if (operation is RemoveOperation)
22 | Remove((RemoveOperation)operation, target);
23 | else if (operation is ReplaceOperation)
24 | target = Replace((ReplaceOperation)operation, target) ?? target;
25 | else if (operation is TestOperation)
26 | Test((TestOperation)operation, target);
27 | return target;
28 | }
29 |
30 | protected abstract void Add(AddOperation operation, TDoc target);
31 | protected abstract void Copy(CopyOperation operation, TDoc target);
32 | protected abstract void Move(MoveOperation operation, TDoc target);
33 | protected abstract void Remove(RemoveOperation operation, TDoc target);
34 | protected abstract TDoc Replace(ReplaceOperation operation, TDoc target);
35 | protected abstract void Test(TestOperation operation, TDoc target);
36 | }
37 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/AddOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class AddOperation : Operation
7 | {
8 | public JToken Value { get; set; }
9 |
10 | public override void Write(JsonWriter writer)
11 | {
12 | writer.WriteStartObject();
13 |
14 | WriteOp(writer, "add");
15 | WritePath(writer, Path);
16 | WriteValue(writer, Value);
17 |
18 | writer.WriteEndObject();
19 | }
20 |
21 | public override void Read(JObject jOperation)
22 | {
23 | Path = jOperation.Value("path");
24 | Value = jOperation.GetValue("value");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/CopyOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class CopyOperation : Operation
7 | {
8 | public string FromPath { get; set; }
9 |
10 | public override void Write(JsonWriter writer)
11 | {
12 | writer.WriteStartObject();
13 |
14 | WriteOp(writer, "copy");
15 | WritePath(writer, Path);
16 | WriteFromPath(writer, FromPath);
17 |
18 | writer.WriteEndObject();
19 | }
20 |
21 | public override void Read(JObject jOperation)
22 | {
23 | Path = jOperation.Value("path");
24 | FromPath = jOperation.Value("from");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/MoveOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class MoveOperation : Operation
7 | {
8 | public string FromPath { get; set; }
9 |
10 | public override void Write(JsonWriter writer)
11 | {
12 | writer.WriteStartObject();
13 |
14 | WriteOp(writer, "move");
15 | WritePath(writer, Path);
16 | WriteFromPath(writer, FromPath);
17 |
18 | writer.WriteEndObject();
19 | }
20 |
21 | public override void Read(JObject jOperation)
22 | {
23 | Path = jOperation.Value("path");
24 | FromPath = jOperation.Value("from");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/Operation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public abstract class Operation
7 | {
8 | public string Path { get; set; }
9 |
10 | public abstract void Write(JsonWriter writer);
11 |
12 | protected static void WriteOp(JsonWriter writer, string op)
13 | {
14 | writer.WritePropertyName("op");
15 | writer.WriteValue(op);
16 | }
17 |
18 | protected static void WritePath(JsonWriter writer, string path)
19 | {
20 | writer.WritePropertyName("path");
21 | writer.WriteValue(path);
22 | }
23 |
24 | protected static void WriteFromPath(JsonWriter writer, string path)
25 | {
26 | writer.WritePropertyName("from");
27 | writer.WriteValue(path);
28 | }
29 |
30 | protected static void WriteValue(JsonWriter writer, JToken value)
31 | {
32 | writer.WritePropertyName("value");
33 | value.WriteTo(writer);
34 | }
35 |
36 | public abstract void Read(JObject jOperation);
37 |
38 | public static Operation Parse(string json)
39 | {
40 | return Build(JObject.Parse(json));
41 | }
42 |
43 | public static Operation Build(JObject jOperation)
44 | {
45 | var op = PatchDocument.CreateOperation((string)jOperation["op"]);
46 | op.Read(jOperation);
47 | return op;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/PatchDocumentConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 |
5 | namespace Foundatio.Repositories.Utility;
6 |
7 | public class PatchDocumentConverter : JsonConverter
8 | {
9 | public override bool CanConvert(Type objectType)
10 | {
11 | return true;
12 | }
13 |
14 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
15 | {
16 | if (objectType != typeof(PatchDocument))
17 | throw new ArgumentException("Object must be of type PatchDocument", nameof(objectType));
18 |
19 | try
20 | {
21 | if (reader.TokenType == JsonToken.Null)
22 | return null;
23 |
24 | var patch = JArray.Load(reader);
25 | return PatchDocument.Parse(patch.ToString());
26 | }
27 | catch (Exception ex)
28 | {
29 | throw new ArgumentException("Invalid patch document: " + ex.Message);
30 | }
31 | }
32 |
33 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
34 | {
35 | if (!(value is PatchDocument))
36 | return;
37 |
38 | var jsonPatchDoc = (PatchDocument)value;
39 | writer.WriteStartArray();
40 | foreach (var op in jsonPatchDoc.Operations)
41 | op.Write(writer);
42 | writer.WriteEndArray();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/RemoveOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class RemoveOperation : Operation
7 | {
8 | public override void Write(JsonWriter writer)
9 | {
10 | writer.WriteStartObject();
11 |
12 | WriteOp(writer, "remove");
13 | WritePath(writer, Path);
14 |
15 | writer.WriteEndObject();
16 | }
17 |
18 | public override void Read(JObject jOperation)
19 | {
20 | Path = jOperation.Value("path");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/ReplaceOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class ReplaceOperation : Operation
7 | {
8 | public JToken Value { get; set; }
9 |
10 | public override void Write(JsonWriter writer)
11 | {
12 | writer.WriteStartObject();
13 |
14 | WriteOp(writer, "replace");
15 | WritePath(writer, Path);
16 | WriteValue(writer, Value);
17 |
18 | writer.WriteEndObject();
19 | }
20 |
21 | public override void Read(JObject jOperation)
22 | {
23 | Path = jOperation.Value("path");
24 | Value = jOperation.GetValue("value");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/JsonPatch/TestOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 |
4 | namespace Foundatio.Repositories.Utility;
5 |
6 | public class TestOperation : Operation
7 | {
8 | public JToken Value { get; set; }
9 |
10 | public override void Write(JsonWriter writer)
11 | {
12 | writer.WriteStartObject();
13 |
14 | WriteOp(writer, "test");
15 | WritePath(writer, Path);
16 | WriteValue(writer, Value);
17 |
18 | writer.WriteEndObject();
19 | }
20 |
21 | public override void Read(JObject jOperation)
22 | {
23 | Path = jOperation.Value("path");
24 | Value = jOperation.GetValue("value");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Migration/IMigration.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using Foundatio.Lock;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace Foundatio.Repositories.Migrations;
7 |
8 | public interface IMigration
9 | {
10 | ///
11 | /// The type of migration.
12 | /// Versioned migrations are run once and they are run sequentially.
13 | /// VersionedAndResumable migrations are the
14 | /// same as Versioned migrations except if they fail to complete, they will
15 | /// automatically be retried.
16 | /// Repeatable migrations will be run after all versioned migrations
17 | /// are run and can be repeatedly run without causing issues. Version is not required on
18 | /// Repeatable migrations, if the Version property
19 | /// is populated, it is treated as a revision for that specific migration and if it has a revision
20 | /// that is higher than from when it was last run, then the migration will be run again.
21 | ///
22 | MigrationType MigrationType { get; }
23 |
24 | ///
25 | /// Versioned and VersionedAndResumable
26 | /// migrations require a version number in order to be run. The version of the migration determines the order in which migrations are run.
27 | /// If a version is not set, they will be treated as disabled and will not run automatically.
28 | /// For Repeatable migrations, if the version is populated then it is considered a
29 | /// revision of that specific migration (not an overall version) and if the revision is higher than the last time the migration was
30 | /// run then the migration will be run again.
31 | ///
32 | int? Version { get; }
33 |
34 | ///
35 | /// If set to true, indicates that this migration needs to be run while the application is offline.
36 | ///
37 | bool RequiresOffline { get; }
38 |
39 | Task RunAsync(MigrationContext context);
40 | }
41 |
42 | public class MigrationContext
43 | {
44 | public MigrationContext(ILock migrationLock, ILogger logger, CancellationToken cancellationToken)
45 | {
46 | Lock = migrationLock;
47 | Logger = logger;
48 | CancellationToken = cancellationToken;
49 | }
50 |
51 | public ILock Lock { get; }
52 | public ILogger Logger { get; }
53 | public CancellationToken CancellationToken { get; }
54 | }
55 |
56 | public enum MigrationType
57 | {
58 | Versioned,
59 | VersionedAndResumable,
60 | Repeatable
61 | }
62 |
63 | public static class MigrationExtensions
64 | {
65 | public static string GetId(this IMigration migration)
66 | {
67 | return migration.MigrationType != MigrationType.Repeatable ? migration.Version.ToString() : migration.GetType().FullName;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Migration/IMigrationStateRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Migrations;
2 |
3 | public interface IMigrationStateRepository : IRepository { }
4 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Migration/MigrationBase.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.Extensions.Logging;
3 | using Microsoft.Extensions.Logging.Abstractions;
4 |
5 | namespace Foundatio.Repositories.Migrations;
6 |
7 | public abstract class MigrationBase : IMigration
8 | {
9 | protected ILogger _logger;
10 |
11 | public MigrationBase() { }
12 |
13 | public MigrationBase(ILoggerFactory loggerFactory)
14 | {
15 | loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
16 | _logger = loggerFactory.CreateLogger(GetType());
17 | }
18 |
19 | public MigrationType MigrationType { get; protected set; } = MigrationType.Versioned;
20 |
21 | public virtual int? Version { get; protected set; } = null;
22 |
23 | public bool RequiresOffline { get; protected set; } = false;
24 |
25 | public virtual Task RunAsync()
26 | {
27 | return Task.CompletedTask;
28 | }
29 |
30 | public virtual Task RunAsync(MigrationContext context)
31 | {
32 | return RunAsync();
33 | }
34 |
35 | protected int CalculateProgress(long total, long completed, int startProgress = 0, int endProgress = 100)
36 | {
37 | return startProgress + (int)((100 * (double)completed / total) * (((double)endProgress - startProgress) / 100));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Migration/MigrationState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Foundatio.Repositories.Models;
3 |
4 | namespace Foundatio.Repositories.Migrations;
5 |
6 | public class MigrationState : IIdentity
7 | {
8 | public string Id { get; set; }
9 | public MigrationType MigrationType { get; set; }
10 | public int Version { get; set; }
11 | public DateTime StartedUtc { get; set; }
12 | public DateTime? CompletedUtc { get; set; }
13 | public string ErrorMessage { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/AggregationsHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public class AggregationsHelper
6 | {
7 | public IReadOnlyDictionary Aggregations { get; protected internal set; } = EmptyReadOnly.Dictionary;
8 |
9 | public AggregationsHelper() { }
10 |
11 | protected AggregationsHelper(IDictionary aggregations)
12 | {
13 | Aggregations = aggregations != null ?
14 | new Dictionary(aggregations)
15 | : EmptyReadOnly.Dictionary;
16 | }
17 |
18 | public AggregationsHelper(IReadOnlyDictionary aggregations)
19 | {
20 | Aggregations = aggregations ?? EmptyReadOnly.Dictionary;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/BucketAggregate.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public class BucketAggregate : IAggregate
6 | {
7 | public IReadOnlyCollection Items { get; set; } = EmptyReadOnly.Collection;
8 | public IReadOnlyDictionary Data { get; set; } = EmptyReadOnly.Dictionary;
9 | public long Total { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/BucketAggregateBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public abstract class BucketAggregateBase : AggregationsHelper, IAggregate
6 | {
7 | protected BucketAggregateBase() { }
8 | protected BucketAggregateBase(IReadOnlyDictionary aggregations) : base(aggregations) { }
9 |
10 | public IReadOnlyDictionary Data { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/BucketBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public abstract class BucketBase : AggregationsHelper, IBucket
6 | {
7 | protected BucketBase() { }
8 | protected BucketBase(IReadOnlyDictionary aggregations) : base(aggregations) { }
9 |
10 | public IReadOnlyDictionary Data { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/BucketedAggregation.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public class BucketedAggregation
6 | {
7 | public IReadOnlyCollection Buckets { get; set; } = EmptyReadOnly.Collection;
8 | }
9 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/DateHistogramBucket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 |
5 | namespace Foundatio.Repositories.Models;
6 |
7 | [DebuggerDisplay("Date: {Date}: Ticks: {Key}")]
8 | public class DateHistogramBucket : KeyedBucket
9 | {
10 | public DateHistogramBucket()
11 | {
12 | }
13 |
14 | [System.Text.Json.Serialization.JsonConstructor]
15 | public DateHistogramBucket(DateTime date, IReadOnlyDictionary aggregations) : base(aggregations)
16 | {
17 | Date = date;
18 | }
19 |
20 | public DateTime Date { get; }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/ExtendedStatsAggregate.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | [DebuggerDisplay("Count: {Count} Min: {Min} Max: {Max} Average: {Average} Sum: {Sum} SumOfSquares: {SumOfSquares} Variance: {Variance} StdDeviation: {StdDeviation}")]
6 | public class ExtendedStatsAggregate : StatsAggregate
7 | {
8 | public double? SumOfSquares { get; set; }
9 | public double? Variance { get; set; }
10 | public double? StdDeviation { get; set; }
11 | public StandardDeviationBounds StdDeviationBounds { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/IAggregate.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Foundatio.Repositories.Utility;
3 |
4 | namespace Foundatio.Repositories.Models;
5 |
6 | [Newtonsoft.Json.JsonConverter(typeof(AggregationsNewtonsoftJsonConverter))]
7 | [System.Text.Json.Serialization.JsonConverter(typeof(AggregationsSystemTextJsonConverter))]
8 | public interface IAggregate
9 | {
10 | IReadOnlyDictionary Data { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/IBucket.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Foundatio.Repositories.Utility;
3 |
4 | namespace Foundatio.Repositories.Models;
5 |
6 | [Newtonsoft.Json.JsonConverter(typeof(BucketsNewtonsoftJsonConverter))]
7 | [System.Text.Json.Serialization.JsonConverter(typeof(BucketsSystemTextJsonConverter))]
8 | public interface IBucket
9 | {
10 | IReadOnlyDictionary Data { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/KeyedAggregation.cs:
--------------------------------------------------------------------------------
1 | namespace Foundatio.Repositories.Models;
2 |
3 | public class KeyedAggregation : ValueAggregate
4 | {
5 | public T Key { get; set; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/KeyedBucket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using Foundatio.Repositories.Utility;
5 |
6 | namespace Foundatio.Repositories.Models;
7 |
8 | [DebuggerDisplay("KeyAsString: {KeyAsString} Key: {Key} Total: {Total}")]
9 | public class KeyedBucket : BucketBase
10 | {
11 | public KeyedBucket()
12 | {
13 | }
14 |
15 | [System.Text.Json.Serialization.JsonConstructor]
16 | public KeyedBucket(IReadOnlyDictionary aggregations) : base(aggregations)
17 | {
18 | }
19 |
20 | [System.Text.Json.Serialization.JsonConverter(typeof(ObjectToInferredTypesConverter))]
21 | public T Key { get; set; }
22 | public string KeyAsString { get; set; }
23 | public long? Total { get; set; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/MetricAggregateBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public class MetricAggregateBase : IAggregate
6 | {
7 | public IReadOnlyDictionary Data { get; set; }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/MultiBucketAggregate.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Foundatio.Repositories.Models;
4 |
5 | public class MultiBucketAggregate : BucketAggregateBase
6 | where TBucket : IBucket
7 | {
8 | public MultiBucketAggregate() { }
9 | public MultiBucketAggregate(IReadOnlyDictionary aggregations) : base(aggregations) { }
10 |
11 | public IReadOnlyCollection Buckets { get; set; } = EmptyReadOnly.Collection;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/ObjectValueAggregate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using Foundatio.Serializer;
4 | using Newtonsoft.Json.Linq;
5 |
6 | namespace Foundatio.Repositories.Models;
7 |
8 | [DebuggerDisplay("Value: {Value}")]
9 | public class ObjectValueAggregate : MetricAggregateBase
10 | {
11 | public object Value { get; set; }
12 |
13 | public T ValueAs(ITextSerializer serializer = null)
14 | {
15 | if (serializer != null)
16 | {
17 | if (Value is string stringValue)
18 | return serializer.Deserialize(stringValue);
19 | else if (Value is JToken jTokenValue)
20 | return serializer.Deserialize(jTokenValue.ToString());
21 | }
22 |
23 | return Value is JToken jToken
24 | ? jToken.ToObject()
25 | : (T)Convert.ChangeType(Value, typeof(T));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Foundatio.Repositories/Models/Aggregations/PercentilesAggregate.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 |
4 | namespace Foundatio.Repositories.Models;
5 |
6 | [DebuggerDisplay("Percentile: {Percentile} Value: {Value}")]
7 | public class PercentileItem
8 | {
9 | public double Percentile { get; set; }
10 | public double? Value { get; set; }
11 | }
12 |
13 | public class PercentilesAggregate : MetricAggregateBase
14 | {
15 | public PercentilesAggregate() { }
16 |
17 | public PercentilesAggregate(IEnumerable