Skip to content

Commit 7869956

Browse files
#106 - Implement discriminator in CouchContext
1 parent 9d04d79 commit 7869956

12 files changed

+219
-64
lines changed

README.md

+30-10
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ The produced Mango JSON:
104104
* [Indexing](#indexing)
105105
* [Index Options](#index-options)
106106
* [Partial Indexes](#partial-indexes)
107+
* [Database Splitting](#database-splitting)
107108
* [Local (non-replicating) Documents](#local-(non-replicating)-documents)
108109
* [Bookmark and Execution stats](#bookmark-and-execution-stats)
109110
* [Users](#users)
@@ -487,16 +488,7 @@ public class MyDeathStarContext : CouchContext
487488
{
488489
public CouchDatabase<Rebel> Rebels { get; set; }
489490

490-
protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
491-
{
492-
optionsBuilder
493-
.UseEndpoint("http://localhost:5984/")
494-
.UseBasicAuthentication("admin", "admin")
495-
// If it finds a index with the same name and ddoc (or null)
496-
// but with different fields and/or sort order,
497-
// it will override the index
498-
.OverrideExistingIndexes();
499-
}
491+
// OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }
500492
501493
protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
502494
{
@@ -506,6 +498,34 @@ public class MyDeathStarContext : CouchContext
506498
}
507499
```
508500

501+
## Database Splitting
502+
503+
It is possible to use the same database for multiple types:
504+
```csharp
505+
public class MyDeathStarContext : CouchContext
506+
{
507+
public CouchDatabase<Rebel> Rebels { get; set; }
508+
public CouchDatabase<Vehicle> Vehicles { get; set; }
509+
510+
// OnConfiguring(CouchOptionsBuilder optionsBuilder) { ... }
511+
512+
protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
513+
{
514+
databaseBuilder.Document<Rebel>().ToDatabase("troups");
515+
databaseBuilder.Document<Vehicle>().ToDatabase("troups");
516+
}
517+
}
518+
```
519+
> When multiple `CouchDatabase` point to the same **database**, a `_discriminator` field is added on documents creation.
520+
>
521+
> When querying, a filter by `discriminator` is added automatically.
522+
523+
If you are not using `CouchContext`, you can still use the database slit feature:
524+
```csharp
525+
var rebels = client.GetDatabase<Rebel>("troups", nameof(Rebel));
526+
var vehicles = client.GetDatabase<Vehicle>("troups", nameof(Vehicle));
527+
```
528+
509529
## Local (non-replicating) Documents
510530

511531
The Local (non-replicating) document interface allows you to create local documents that are not replicated to other databases.

src/CouchDB.Driver/CouchClient.cs

+6
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,12 @@ protected virtual async Task DisposeAsync(bool disposing)
361361
private string GetClassName<TSource>()
362362
{
363363
Type type = typeof(TSource);
364+
return GetClassName(type);
365+
}
366+
367+
public string GetClassName(Type type)
368+
{
369+
Check.NotNull(type, nameof(type));
364370
return type.GetName(_options);
365371
}
366372
}

src/CouchDB.Driver/CouchContext.cs

+61-22
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ protected virtual void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
1818

1919
private static readonly MethodInfo InitDatabasesGenericMethod
2020
= typeof(CouchContext).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
21-
.Single(mi => mi.Name == nameof(InitDatabasesAsync));
21+
.Single(mi => mi.Name == nameof(InitDatabaseAsync));
2222

2323
private static readonly MethodInfo ApplyDatabaseChangesGenericMethod
2424
= typeof(CouchContext).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
@@ -39,20 +39,9 @@ protected CouchContext(CouchOptions options)
3939
#pragma warning restore CA2214 // Do not call overridable methods in constructors
4040

4141
Client = new CouchClient(options);
42-
IEnumerable<PropertyInfo> databasePropertyInfos = GetDatabaseProperties();
4342

44-
foreach (PropertyInfo dbProperty in databasePropertyInfos)
45-
{
46-
Type documentType = dbProperty.PropertyType.GetGenericArguments()[0];
47-
48-
var initDatabasesTask = (Task)InitDatabasesGenericMethod.MakeGenericMethod(documentType)
49-
.Invoke(this, new object[] {dbProperty, options});
50-
initDatabasesTask.ConfigureAwait(false).GetAwaiter().GetResult();
51-
52-
var applyDatabaseChangesTask = (Task)ApplyDatabaseChangesGenericMethod.MakeGenericMethod(documentType)
53-
.Invoke(this, new object[] { dbProperty, options, databaseBuilder });
54-
applyDatabaseChangesTask.ConfigureAwait(false).GetAwaiter().GetResult();
55-
}
43+
SetupDiscriminators(databaseBuilder);
44+
InitializeDatabases(options, databaseBuilder);
5645
}
5746

5847
public async ValueTask DisposeAsync()
@@ -69,26 +58,76 @@ protected virtual async Task DisposeAsync(bool disposing)
6958
}
7059
}
7160

72-
private async Task InitDatabasesAsync<TSource>(PropertyInfo propertyInfo, CouchOptions options)
61+
private static void SetupDiscriminators(CouchDatabaseBuilder databaseBuilder)
62+
{
63+
// Get all options that share the database with another one
64+
IEnumerable<KeyValuePair<Type, CouchDocumentBuilder>>? sharedDatabase = databaseBuilder.DocumentBuilders
65+
.Where(opt => opt.Value.Database != null)
66+
.GroupBy(v => v.Value.Database)
67+
.Where(g => g.Count() > 1)
68+
.SelectMany(g => g);
69+
foreach (KeyValuePair<Type, CouchDocumentBuilder> option in sharedDatabase)
70+
{
71+
option.Value.Discriminator = option.Key.Name;
72+
}
73+
}
74+
75+
private void InitializeDatabases(CouchOptions options, CouchDatabaseBuilder databaseBuilder)
76+
{
77+
foreach (PropertyInfo dbProperty in GetDatabaseProperties())
78+
{
79+
Type documentType = dbProperty.PropertyType.GetGenericArguments()[0];
80+
81+
var initDatabasesTask = (Task)InitDatabasesGenericMethod.MakeGenericMethod(documentType)
82+
.Invoke(this, new object[] { dbProperty, options, databaseBuilder });
83+
initDatabasesTask.ConfigureAwait(false).GetAwaiter().GetResult();
84+
85+
var applyDatabaseChangesTask = (Task)ApplyDatabaseChangesGenericMethod.MakeGenericMethod(documentType)
86+
.Invoke(this, new object[] { dbProperty, options, databaseBuilder });
87+
applyDatabaseChangesTask.ConfigureAwait(false).GetAwaiter().GetResult();
88+
}
89+
}
90+
91+
private async Task InitDatabaseAsync<TSource>(PropertyInfo propertyInfo, CouchOptions options, CouchDatabaseBuilder databaseBuilder)
7392
where TSource : CouchDocument
7493
{
75-
ICouchDatabase<TSource> database = options.CheckDatabaseExists
76-
? await Client.GetOrCreateDatabaseAsync<TSource>().ConfigureAwait(false)
77-
: Client.GetDatabase<TSource>();
94+
ICouchDatabase<TSource> database;
95+
Type documentType = typeof(TSource);
96+
97+
if (databaseBuilder.DocumentBuilders.ContainsKey(documentType))
98+
{
99+
var documentBuilder = (CouchDocumentBuilder<TSource>)databaseBuilder.DocumentBuilders[documentType];
100+
var databaseName = documentBuilder.Database ?? Client.GetClassName(documentType);
101+
database = options.CheckDatabaseExists
102+
? await Client.GetOrCreateDatabaseAsync<TSource>(databaseName, documentBuilder.Shards, documentBuilder.Replicas, documentBuilder.Discriminator).ConfigureAwait(false)
103+
: Client.GetDatabase<TSource>(databaseName, documentBuilder.Discriminator);
104+
}
105+
else
106+
{
107+
database = options.CheckDatabaseExists
108+
? await Client.GetOrCreateDatabaseAsync<TSource>().ConfigureAwait(false)
109+
: Client.GetDatabase<TSource>();
110+
}
78111

79112
propertyInfo.SetValue(this, database);
80113
}
81114

82115
private async Task ApplyDatabaseChangesAsync<TSource>(PropertyInfo propertyInfo, CouchOptions options, CouchDatabaseBuilder databaseBuilder)
83-
where TSource: CouchDocument
116+
where TSource : CouchDocument
84117
{
85-
if (!databaseBuilder.DocumentBuilders.ContainsKey(typeof(TSource)))
118+
Type documentType = typeof(TSource);
119+
if (!databaseBuilder.DocumentBuilders.ContainsKey(documentType))
86120
{
87121
return;
88122
}
89123

90124
var database = (CouchDatabase<TSource>)propertyInfo.GetValue(this);
91-
var documentBuilder = (CouchDocumentBuilder<TSource>)databaseBuilder.DocumentBuilders[typeof(TSource)];
125+
var documentBuilder = (CouchDocumentBuilder<TSource>)databaseBuilder.DocumentBuilders[documentType];
126+
127+
if (!documentBuilder.IndexDefinitions.Any())
128+
{
129+
return;
130+
}
92131

93132
List<IndexInfo> indexes = await database.GetIndexesAsync().ConfigureAwait(false);
94133

@@ -125,7 +164,7 @@ await database.CreateIndexAsync(
125164
{
126165
return;
127166
}
128-
167+
129168
IndexDefinition indexDefinition = database.NewIndexBuilder(indexSetup.IndexBuilderAction).Build();
130169
if (!AreFieldsEqual(currentIndex.Fields, indexDefinition.Fields))
131170
{

src/CouchDB.Driver/CouchDB.Driver.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939

4040
<ItemGroup>
4141
<Folder Include="License\" />
42-
<Folder Include="Attributes\" />
4342
</ItemGroup>
4443

4544
</Project>

src/CouchDB.Driver/ICouchClient.cs

+7
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ Task<ICouchDatabase<TSource>> GetOrCreateDatabaseAsync<TSource>(int? shards = nu
160160
/// <returns>A task that represents the asynchronous operation. The task result contains the sequence of all active tasks.</returns>
161161
Task<IEnumerable<CouchActiveTask>> GetActiveTasksAsync(CancellationToken cancellationToken = default);
162162

163+
/// <summary>
164+
/// Get the database name for the given type.
165+
/// </summary>
166+
/// <param name="type">The type of database documents.</param>
167+
/// <returns></returns>
168+
string GetClassName(Type type);
169+
163170
/// <summary>
164171
/// URI of the CouchDB endpoint.
165172
/// </summary>

src/CouchDB.Driver/Options/CouchDatabaseBuilder.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ namespace CouchDB.Driver.Options
66
{
77
public class CouchDatabaseBuilder
88
{
9-
internal readonly Dictionary<Type, object> DocumentBuilders;
9+
internal readonly Dictionary<Type, CouchDocumentBuilder> DocumentBuilders;
1010

1111
internal CouchDatabaseBuilder()
1212
{
13-
DocumentBuilders = new Dictionary<Type, object>();
13+
DocumentBuilders = new Dictionary<Type, CouchDocumentBuilder>();
1414
}
1515

1616
public CouchDocumentBuilder<TSource> Document<TSource>()
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,10 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using CouchDB.Driver.Indexes;
4-
using CouchDB.Driver.Types;
5-
6-
namespace CouchDB.Driver.Options
1+
namespace CouchDB.Driver.Options
72
{
8-
public class CouchDocumentBuilder<TSource>
9-
where TSource : CouchDocument
3+
public abstract class CouchDocumentBuilder
104
{
11-
internal List<IndexSetupDefinition<TSource>> IndexDefinitions { get; }
12-
13-
internal CouchDocumentBuilder()
14-
{
15-
IndexDefinitions = new List<IndexSetupDefinition<TSource>>();
16-
}
17-
18-
public CouchDocumentBuilder<TSource> HasIndex(string name, Action<IIndexBuilder<TSource>> indexBuilderAction,
19-
IndexOptions? options = null)
20-
{
21-
var indexDefinition = new IndexSetupDefinition<TSource>(name, indexBuilderAction, options);
22-
IndexDefinitions.Add(indexDefinition);
23-
return this;
24-
}
25-
}
5+
internal string? Database { get; set; }
6+
internal int? Shards { get; set; }
7+
internal int? Replicas { get; set; }
8+
internal string? Discriminator { get; set; }
9+
}
2610
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using CouchDB.Driver.Indexes;
2+
using CouchDB.Driver.Types;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace CouchDB.Driver.Options
7+
{
8+
public class CouchDocumentBuilder<TSource> : CouchDocumentBuilder
9+
where TSource : CouchDocument
10+
{
11+
internal List<IndexSetupDefinition<TSource>> IndexDefinitions { get; }
12+
13+
internal CouchDocumentBuilder()
14+
{
15+
IndexDefinitions = new List<IndexSetupDefinition<TSource>>();
16+
}
17+
18+
public CouchDocumentBuilder<TSource> ToDatabase(string database)
19+
{
20+
Database = database;
21+
return this;
22+
}
23+
24+
public CouchDocumentBuilder<TSource> WithShards(int shards)
25+
{
26+
Shards = shards;
27+
return this;
28+
}
29+
30+
public CouchDocumentBuilder<TSource> WithReplicas(int replicas)
31+
{
32+
Replicas = replicas;
33+
return this;
34+
}
35+
36+
public CouchDocumentBuilder<TSource> HasIndex(string name, Action<IIndexBuilder<TSource>> indexBuilderAction,
37+
IndexOptions? options = null)
38+
{
39+
var indexDefinition = new IndexSetupDefinition<TSource>(name, indexBuilderAction, options);
40+
IndexDefinitions.Add(indexDefinition);
41+
return this;
42+
}
43+
}
44+
}

src/CouchDB.Driver/Query/QueryOptimizer.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace CouchDB.Driver.Query
1515
/// </summary>
1616
internal class QueryOptimizer : ExpressionVisitor, IQueryOptimizer
1717
{
18+
private static readonly MethodInfo WrapInWhereGenericMethod
19+
= typeof(MethodCallExpressionBuilder).GetMethod(nameof(MethodCallExpressionBuilder.WrapInDiscriminatorFilter));
1820
private bool _isVisitingWhereMethodOrChild;
1921
private readonly Queue<MethodCallExpression> _nextWhereCalls;
2022

@@ -28,10 +30,7 @@ public Expression Optimize(Expression e, string? discriminator)
2830
if (discriminator is not null)
2931
{
3032
Type? sourceType = e.Type.GetGenericArguments()[0];
31-
MethodInfo? wrapInWhere = typeof(MethodCallExpressionBuilder)
32-
.GetMethod(nameof(MethodCallExpressionBuilder.WrapInDiscriminatorFilter))
33-
.MakeGenericMethod(new[] { sourceType });
34-
33+
MethodInfo? wrapInWhere = WrapInWhereGenericMethod.MakeGenericMethod(new[] { sourceType });
3534
e = (Expression)wrapInWhere.Invoke(null, new object[] { e, discriminator });
3635
}
3736

0 commit comments

Comments
 (0)