Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api restructure #125

Merged
merged 28 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Benchmark/Benchmarks/Update.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public class UpdateOne : BenchBase

public UpdateOne()
{
DB.SaveAsync(new Author { ID = id, FirstName = "initial" }).GetAwaiter().GetResult();
DB.Context.SaveAsync(new Author { ID = id, FirstName = "initial" }).GetAwaiter().GetResult();
}

[Benchmark]
public override Task MongoDB_Entities()
{
return DB.Update<Author>()
var update = DB.Context.Update<Author>();
update.MatchID(id);
return DB.Context.Update<Author>()
.MatchID(id)
.Modify(a => a.FirstName, "updated")
.ExecuteAsync();
Expand All @@ -44,7 +46,8 @@ public class Update100 : BenchBase

public Update100()
{
DB.Index<Author>()
DB.Context
.Index<Author>()
.Key(a => a.FirstName, KeyType.Ascending)
.Option(o => o.Background = false)
.CreateAsync()
Expand All @@ -64,7 +67,7 @@ public Update100()
[Benchmark]
public override Task MongoDB_Entities()
{
return DB
return DB.Context
.Update<Author>()
.Match(x => x.FirstName == guid)
.Modify(x => x.FirstName, "updated")
Expand Down
52 changes: 52 additions & 0 deletions MongoDB.Entities/Builders/Distinct.Base.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
namespace MongoDB.Entities;

public class DistinctBase<T, TProperty, TSelf> : FilterQueryBase<T, TSelf>, IDistinct<T,TProperty,TSelf>
where TSelf : DistinctBase<T, TProperty, TSelf>
{
internal DistinctOptions _options = new();
internal FieldDefinition<T, TProperty>? _field;

internal DistinctBase(DistinctBase<T, TProperty, TSelf> other) : base(other)
{
_options = other._options;
_field = other._field;
}
internal DistinctBase(Dictionary<Type, (object filterDef, bool prepend)> globalFilters) : base(globalFilters)
{
_globalFilters = globalFilters;
}


private TSelf This => (TSelf)this;


/// <summary>
/// Specify an option for this find command (use multiple times if needed)
/// </summary>
/// <param name="option">x => x.OptionName = OptionValue</param>
public TSelf Option(Action<DistinctOptions> option)
{
option(_options);
return This;
}

/// <summary>
/// Specify the property you want to get the unique values for (as a string path)
/// </summary>
/// <param name="property">ex: "Address.Street"</param>
public TSelf Property(string property)
{
_field = property;
return This;
}

/// <summary>
/// Specify the property you want to get the unique values for (as a member expression)
/// </summary>
/// <param name="property">x => x.Address.Street</param>
public TSelf Property(Expression<Func<T, TProperty>> property)
{
_field = property.FullPath();
return This;
}
}
25 changes: 25 additions & 0 deletions MongoDB.Entities/Builders/Distinct.Interface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace MongoDB.Entities;

public interface IDistinct<T, TProperty, TSelf>
where TSelf : IDistinct<T, TProperty, TSelf>
{
/// <summary>
/// Specify an option for this find command (use multiple times if needed)
/// </summary>
/// <param name="option">x => x.OptionName = OptionValue</param>
public TSelf Option(Action<DistinctOptions> option);

/// <summary>
/// Specify the property you want to get the unique values for (as a string path)
/// </summary>
/// <param name="property">ex: "Address.Street"</param>
public TSelf Property(string property);

/// <summary>
/// Specify the property you want to get the unique values for (as a member expression)
/// </summary>
/// <param name="property">x => x.Address.Street</param>
public TSelf Property(Expression<Func<T, TProperty>> property);


}
244 changes: 48 additions & 196 deletions MongoDB.Entities/Builders/Distinct.cs
Original file line number Diff line number Diff line change
@@ -1,209 +1,61 @@
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace MongoDB.Entities
namespace MongoDB.Entities;

/// <summary>
/// Represents a MongoDB Distinct command where you can get back distinct values for a given property of a given Entity.
/// </summary>
/// <typeparam name="T">Any Entity that implements IEntity interface</typeparam>
/// <typeparam name="TProperty">The type of the property of the entity you'd like to get unique values for</typeparam>
public class Distinct<T, TProperty> : DistinctBase<T, TProperty, Distinct<T, TProperty>>, ICollectionRelated<T>
{
public DBContext Context { get; }
public IMongoCollection<T> Collection { get; }

internal Distinct(
DBContext context,
IMongoCollection<T> collection,
DistinctBase<T, TProperty, Distinct<T, TProperty>> other) : base(other)
{
Context = context;
Collection = collection;
}
internal Distinct(
DBContext context,
IMongoCollection<T> collection) : base(globalFilters: context.GlobalFilters)
{
Context = context;
Collection = collection;
}

/// <summary>
/// Represents a MongoDB Distinct command where you can get back distinct values for a given property of a given Entity.
/// Run the Distinct command in MongoDB server and get a cursor instead of materialized results
/// </summary>
/// <typeparam name="T">Any Entity that implements IEntity interface</typeparam>
/// <typeparam name="TProperty">The type of the property of the entity you'd like to get unique values for</typeparam>
public class Distinct<T, TProperty> where T : IEntity
/// <param name="cancellation">An optional cancellation token</param>
public Task<IAsyncCursor<TProperty>> ExecuteCursorAsync(CancellationToken cancellation = default)
{
private FieldDefinition<T, TProperty> field;
private FilterDefinition<T> filter = Builders<T>.Filter.Empty;
private readonly DistinctOptions options = new();
private readonly IClientSessionHandle session;
private readonly Dictionary<Type, (object filterDef, bool prepend)> globalFilters;
private bool ignoreGlobalFilters;
private readonly string tenantPrefix;

internal Distinct(
IClientSessionHandle session,
Dictionary<Type, (object filterDef, bool prepend)> globalFilters, string tenantPrefix)
{
this.session = session;
this.globalFilters = globalFilters;
this.tenantPrefix = tenantPrefix;
}

/// <summary>
/// Specify the property you want to get the unique values for (as a string path)
/// </summary>
/// <param name="property">ex: "Address.Street"</param>
public Distinct<T, TProperty> Property(string property)
{
field = property;
return this;
}

/// <summary>
/// Specify the property you want to get the unique values for (as a member expression)
/// </summary>
/// <param name="property">x => x.Address.Street</param>
public Distinct<T, TProperty> Property(Expression<Func<T, object>> property)
{
field = property.FullPath();
return this;
}

/// <summary>
/// Specify the matching criteria with a filter expression
/// </summary>
/// <param name="filter">f => f.Eq(x => x.Prop, Value) &amp; f.Gt(x => x.Prop, Value)</param>
public Distinct<T, TProperty> Match(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>> filter)
{
this.filter &= filter(Builders<T>.Filter);
return this;
}

/// <summary>
/// Specify the matching criteria with a lambda expression
/// </summary>
/// <param name="expression">x => x.Property == Value</param>
public Distinct<T, TProperty> Match(Expression<Func<T, bool>> expression)
{
return Match(f => f.Where(expression));
}

/// <summary>
/// Specify the matching criteria with a template
/// </summary>
/// <param name="template">A Template with a find query</param>
public Distinct<T, TProperty> Match(Template template)
{
filter &= template.RenderToString();
return this;
}

/// <summary>
/// Specify a search term to find results from the text index of this particular collection.
/// <para>TIP: Make sure to define a text index with DB.Index&lt;T&gt;() before searching</para>
/// </summary>
/// <param name="searchType">The type of text matching to do</param>
/// <param name="searchTerm">The search term</param>
/// <param name="caseSensitive">Case sensitivity of the search (optional)</param>
/// <param name="diacriticSensitive">Diacritic sensitivity of the search (optional)</param>
/// <param name="language">The language for the search (optional)</param>
public Distinct<T, TProperty> Match(Search searchType, string searchTerm, bool caseSensitive = false, bool diacriticSensitive = false, string language = null)
{
if (searchType == Search.Fuzzy)
{
searchTerm = searchTerm.ToDoubleMetaphoneHash();
caseSensitive = false;
diacriticSensitive = false;
language = null;
}

return Match(
f => f.Text(
searchTerm,
new TextSearchOptions
{
CaseSensitive = caseSensitive,
DiacriticSensitive = diacriticSensitive,
Language = language
}));
}

/// <summary>
/// Specify criteria for matching entities based on GeoSpatial data (longitude &amp; latitude)
/// <para>TIP: Make sure to define a Geo2DSphere index with DB.Index&lt;T&gt;() before searching</para>
/// <para>Note: DB.FluentGeoNear() supports more advanced options</para>
/// </summary>
/// <param name="coordinatesProperty">The property where 2DCoordinates are stored</param>
/// <param name="nearCoordinates">The search point</param>
/// <param name="maxDistance">Maximum distance in meters from the search point</param>
/// <param name="minDistance">Minimum distance in meters from the search point</param>
public Distinct<T, TProperty> Match(Expression<Func<T, object>> coordinatesProperty, Coordinates2D nearCoordinates, double? maxDistance = null, double? minDistance = null)
{
return Match(f => f.Near(coordinatesProperty, nearCoordinates.ToGeoJsonPoint(), maxDistance, minDistance));
}

/// <summary>
/// Specify the matching criteria with a JSON string
/// </summary>
/// <param name="jsonString">{ Title : 'The Power Of Now' }</param>
public Distinct<T, TProperty> MatchString(string jsonString)
{
filter &= jsonString;
return this;
}

/// <summary>
/// Specify the matching criteria with an aggregation expression (i.e. $expr)
/// </summary>
/// <param name="expression">{ $gt: ['$Property1', '$Property2'] }</param>
public Distinct<T, TProperty> MatchExpression(string expression)
{
filter &= "{$expr:" + expression + "}";
return this;
}

/// <summary>
/// Specify the matching criteria with a Template
/// </summary>
/// <param name="template">A Template object</param>
public Distinct<T, TProperty> MatchExpression(Template template)
{
filter &= "{$expr:" + template.RenderToString() + "}";
return this;
}

/// <summary>
/// Specify an option for this find command (use multiple times if needed)
/// </summary>
/// <param name="option">x => x.OptionName = OptionValue</param>
public Distinct<T, TProperty> Option(Action<DistinctOptions> option)
{
option(options);
return this;
}

/// <summary>
/// Specify that this operation should ignore any global filters
/// </summary>
public Distinct<T, TProperty> IgnoreGlobalFilters()
{
ignoreGlobalFilters = true;
return this;
}
if (_field == null)
throw new InvalidOperationException("Please use the .Property() method to specify the field to use for obtaining unique values for!");

/// <summary>
/// Run the Distinct command in MongoDB server and get a cursor instead of materialized results
/// </summary>
/// <param name="cancellation">An optional cancellation token</param>
public Task<IAsyncCursor<TProperty>> ExecuteCursorAsync(CancellationToken cancellation = default)
{
if (field == null)
throw new InvalidOperationException("Please use the .Property() method to specify the field to use for obtaining unique values for!");

var mergedFilter = Logic.MergeWithGlobalFilter(ignoreGlobalFilters, globalFilters, filter);
var mergedFilter = MergedFilter;

return session == null
? DB.Collection<T>(tenantPrefix).DistinctAsync(field, mergedFilter, options, cancellation)
: DB.Collection<T>(tenantPrefix).DistinctAsync(session, field, mergedFilter, options, cancellation);
}
return Context.Session is IClientSessionHandle session
? Collection.DistinctAsync(session, _field, mergedFilter, _options, cancellation)
: Collection.DistinctAsync(_field, mergedFilter, _options, cancellation);
}

/// <summary>
/// Run the Distinct command in MongoDB server and get a list of unique property values
/// </summary>
/// <param name="cancellation">An optional cancellation token</param>
public async Task<List<TProperty>> ExecuteAsync(CancellationToken cancellation = default)
/// <summary>
/// Run the Distinct command in MongoDB server and get a list of unique property values
/// </summary>
/// <param name="cancellation">An optional cancellation token</param>
public async Task<List<TProperty>> ExecuteAsync(CancellationToken cancellation = default)
{
var list = new List<TProperty>();
using (var csr = await ExecuteCursorAsync(cancellation).ConfigureAwait(false))
{
var list = new List<TProperty>();
using (var csr = await ExecuteCursorAsync(cancellation).ConfigureAwait(false))
while (await csr.MoveNextAsync(cancellation).ConfigureAwait(false))
{
while (await csr.MoveNextAsync(cancellation).ConfigureAwait(false))
{
list.AddRange(csr.Current);
}
list.AddRange(csr.Current);
}
return list;
}
return list;
}
}
Loading