Is there a way to improve the code? .Net Core 6.0 and Cosmos Database with SQL API - asp.net-core

It's my first time using .Net Core 6.0 WebAPI and Cosmos Database with SQL API, so I'm concerned that I've done things correctly.
I have used /address/zipcode as a partition key in the below sample
public class FamilyService : IFamilyService
{
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public Task<Family> GetFamilyDataAsync(string id, string zipcode)
{
return Task.FromResult(_container.GetItemLinqQueryable<Family>(allowSynchronousQueryExecution: true)
.Where(p => p.Id == id && p.Address.ZipCode == zipcode)
.ToList().First());
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipcode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipcode));
}
private async Task<bool> GetFamilyDataFromId(string id, string zipcode)
{
string query = $"select * from c where c.id=#familyId";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#familyId", id);
List<Family> familyResults = new List<Family>();
FeedIterator streamResultSet = _container.GetItemQueryStreamIterator(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipcode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<Family> familyResult = streamResponse.Documents.ToObject<List<Family>>();
familyResults.AddRange(familyResult);
}
else
{
return false;
}
}
}
if (familyResults != null && familyResults.Count > 0)
{
return true;
}
return familyResults;
}
Especially I want to focus more on those methods that retrieves the data from the database.
Update#1 : I have updated the code as suggested by #Mark Brown.
public class FamilyService : IFamilyService
{
private Container _container;
private static readonly JsonSerializer Serializer = new JsonSerializer();
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public async Task<Family> GetFamilyDataAsync(string id, string zipCode)
{
return await _container.ReadItemAsync<Family>(id, new PartitionKey(zipCode));
}
public async Task<List<Family>> GetAllFamilyDataAsync(string zipCode)
{
string query = $"SELECT * FROM Family";
QueryDefinition queryDefinition = new QueryDefinition(query);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataByLastNameAsync(string lastName, string zipCode)
{
string query = $"SELECT * FROM Family where Family.lastName = #lastName";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#lastName", lastName);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataPaginatedAsync(int pageNumber, string zipCode)
{
int pageSize = 2;
int itemsToSkip = (pageSize * pageNumber) - pageSize;
QueryRequestOptions options = new QueryRequestOptions { MaxItemCount = pageSize };
string continuationToken = null;
List<Family> familyResults = new List<Family>();
IQueryable<Family> query = this._container
.GetItemLinqQueryable<Family>(false, continuationToken, options)
.Where(i => i.Address.ZipCode == zipCode)
.Skip(itemsToSkip);
using (FeedIterator<Family> iterator = query.ToFeedIterator<Family>())
{
FeedResponse<Family> feedResponse = await iterator.ReadNextAsync();
foreach (Family item in feedResponse)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipCode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipCode));
}
}

If you are searching for something with the partition key and id then that can only return a single item as id must be unique within a pk. So GetFamilyDataAsync() can be implemented using ReadItemAsync() rather than a query. PS: Not sure why you would ever do this, allowSynchronousQueryExecution: true
GetFamilyDataFromId() also can be implemented using ReadItemAsync(). Even if this did require a query, it doesn't make sense to implement to return a stream, then deserialize into an object. Just use the variant that can deserialize directly into a POCO.
Can't say if this is completely optimal for everything, but you can go here to find more things as a place to start, https://learn.microsoft.com/en-us/azure/cosmos-db/sql/best-practice-dotnet
The other thing to call out is zip code as a partition key. This might work at small scale, but it could run into issues at larger scale depending on how many families and how many records for each family are stored within a single partition. Max size for a single partition key value is 20 GB. Fine for small workloads but possibly an issue at larger scale.

Related

.net core identity server Custom token provider generating long token string

This is how I configured my custom token provider,
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Tokens.ProviderMap.Add("CustomEmailConfirmationTokenProvider",
new TokenProviderDescriptor(typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
options.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmationTokenProvider";
})
.AddEntityFrameworkStores<IdentityDbContext>()
.AddTokenProvider<CustomEmailConfirmationTokenProvider<ApplicationUser>>("CustomEmailConfirmationTokenProvider");
And this is code for custom token provider class,
public class CustomEmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,IOptions<CustomEmailConfirmationTokenProviderOptions> options
, ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options,logger)
{ }
}
public class CustomEmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
public CustomEmailConfirmationTokenProviderOptions()
{
Name = "CustomEmailConfirmationTokenProvider";
TokenLifespan = TimeSpan.FromMinutes(1);
}
}
this is how I generate user token,
var myToken = await usrManager.GenerateUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose);
and this is how I verify the token validity using custom provider,
var isValid = await usrManager.VerifyUserTokenAsync(userinfo, "CustomEmailConfirmationTokenProvider",
UserManager<object>.ResetPasswordTokenPurpose, model.Token);
The above codes,including setting of TokenLifeSpan are working fine.
But the token is generating as a long string as example below,
CfDJ8IvIvIomoPJKkcJtJSNCN4wB6Fp82OPzYvkVaHtBzJBjY9EwOBt2nMg1WudWBTc1giurpRIXhSHeJTe3CLswJEOL7nng9Hd7H/ctDVNSEL5eBnzXVZpvSNmVCvgwIg3cwSNtcjjsYmGFA01EgyEkXXkBZg+jLDiEsKU8YgmaoQd5bOLE3WLopZo2lboG7dOnZv777SMHitbQNJ2SdRyZf2aMAybKAkHnKGIR3ZSyQXRM
I want to change this toke as just 6 digits characters.
Where should I modify to solve this issue?
If you look at the asp.net sources, you will see that the default token provider is inherited from the TotpSecurityStampBasedTokenProvider DefaultEmailTokenProvider which uses the "D6" format in the GenerateAsync method TotpSecurityStampBasedTokenProvider whereas your custom provider is inherited from the DataProtectorTokenProvider which uses the Base64String format in the GenerateAsync method DataProtectorTokenProvider. I think you can try to change the base class for your custom provider to get a 6 digits token, but the Rfc6238AuthenticationService that is used by the TotpSecurityStampBasedTokenProvider has a hardcoded lifespan of 3 minutes Rfc6238AuthenticationService. For creating a custom Rfc6238AuthenticationService see this answer.
CustomEmailConfirmationTokenProvider
public class CustomEmailConfirmationTokenProvider<TUser> : TotpSecurityStampBasedTokenProvider<TUser> where TUser : class
{
private readonly TimeSpan _timeStep;
public CustomEmailConfirmationTokenProvider()
{
// Here you can setup expiration time.
_timeStep = TimeSpan.FromMinutes(1);
}
public override async Task<string> GenerateAsync(
string purpose,
UserManager<TUser> manager,
TUser user)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
byte[] token = await manager.CreateSecurityTokenAsync(user);
string async = CustomRfc6238AuthenticationService.GenerateCode
(
token,
await this.GetUserModifierAsync(purpose, manager, user),
_timeStep
)
.ToString("D6", (IFormatProvider)CultureInfo.InvariantCulture);
token = (byte[])null;
return async;
}
public override async Task<bool> ValidateAsync(
string purpose,
string token,
UserManager<TUser> manager,
TUser user)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
int code;
if (!int.TryParse(token, out code))
return false;
byte[] securityToken = await manager.CreateSecurityTokenAsync(user);
string userModifierAsync = await this.GetUserModifierAsync(purpose, manager, user);
return securityToken != null && CustomRfc6238AuthenticationService.ValidateCode(
securityToken,
code,
userModifierAsync,
_timeStep);
}
// It's just a dummy for inheritance.
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<TUser> manager, TUser user)
{
return Task.FromResult(true);
}
}
CustomRfc6238AuthenticationService
public static class CustomRfc6238AuthenticationService
{
private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3);
private static readonly Encoding _encoding = new UTF8Encoding(false, true);
internal static int ComputeTotp(
HashAlgorithm hashAlgorithm,
ulong timestepNumber,
byte[]? modifierBytes)
{
// # of 0's = length of pin
const int Mod = 1000000;
// See https://tools.ietf.org/html/rfc4226
// We can add an optional modifier
var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber));
var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifierBytes));
// Generate DT string
var offset = hash[hash.Length - 1] & 0xf;
Debug.Assert(offset + 4 < hash.Length);
var binaryCode = (hash[offset] & 0x7f) << 24
| (hash[offset + 1] & 0xff) << 16
| (hash[offset + 2] & 0xff) << 8
| (hash[offset + 3] & 0xff);
return binaryCode % Mod;
}
private static byte[] ApplyModifier(Span<byte> input, byte[] modifierBytes)
{
var combined = new byte[checked(input.Length + modifierBytes.Length)];
input.CopyTo(combined);
Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length);
return combined;
}
// More info: https://tools.ietf.org/html/rfc6238#section-4
private static ulong GetCurrentTimeStepNumber(TimeSpan timeStep)
{
var delta = DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch;
return (ulong)(delta.Ticks / timeStep.Ticks);
}
public static int GenerateCode(byte[] securityToken, string? modifier = null, TimeSpan? timeStep = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than time step in either direction
var currentTimeStep = GetCurrentTimeStepNumber(timeStep ?? _timestep);
var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
return ComputeTotp(hashAlgorithm, currentTimeStep, modifierBytes);
}
}
public static bool ValidateCode(byte[] securityToken, int code, string? modifier = null, TimeSpan? timeStep = null)
{
if (securityToken == null)
{
throw new ArgumentNullException(nameof(securityToken));
}
// Allow a variance of no greater than time step in either direction
var currentTimeStep = GetCurrentTimeStepNumber(timeStep ?? _timestep);
using (var hashAlgorithm = new HMACSHA1(securityToken))
{
var modifierBytes = modifier is not null ? _encoding.GetBytes(modifier) : null;
for (var i = -2; i <= 2; i++)
{
var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep - i), modifierBytes);
if (computedTotp == code)
{
return true;
}
}
}
// No match
return false;
}
}
Usage
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
var token = await _userManager.GenerateUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose);
var isValid = await _userManager.VerifyUserTokenAsync(user, "CustomEmailConfirmationTokenProvider", UserManager<object>.ResetPasswordTokenPurpose, token);
}
Note: This algorithm has a variance of no greater than time step in either direction.

How to retrieve particular data using sqlflite in flutter

I have one page called login page in that page I've use labels for text field and button eg(Username, Login), I've use login label for Login Button, but I want that label from sqlflite database also I've stored data to database but I want to retrieve that data to particular that label.
Here is this login form Widget label text is Username but I want it from SQL database I've already stored into the database I just want to retrieve at label text.
Here I've code tried.
Database Helper Code
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:tudo/src/utils/sql/sql_appconstant_model.dart';
class DbOperation {
static final DbOperation _instance = DbOperation._();
static Database _database;
DbOperation._();
factory DbOperation() {
return _instance;
}
Future<Database> get db async {
if (_database != null) {
return _database;
}
_database = await init();
return _database;
}
Future<Database> init() async {
Directory directory = await getApplicationDocumentsDirectory();
String dbPath = join(directory.path, 'database.db');
var database = openDatabase(dbPath, version: 1, onCreate: _onCreate);
return database;
}
void _onCreate(Database db, int version) {
db.execute('''
CREATE TABLE appConstant(
id INTEGER PRIMARY KEY AUTOINCREMENT,
language TEXT,
screen INTEGER,
value INTEGER,
label INTEGER)
''');
print("Database was created!");
}
Future<int> insertdata(DbAppConstant dbAppConstant) async {
var client = await db;
return client.insert('appConstant', dbAppConstant.toMapForDb(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<List<DbAppConstant>> fetchAll() async {
var client = await db;
var res = await client.query('appConstant');
if (res.isNotEmpty) {
var appConst = res
.map((appConstantMap) => DbAppConstant.fromDb(appConstantMap))
.toList();
return appConst;
}
return [];
}
Future deleteAll() async {
var client = await db;
client.delete('appConstant');
}
Future closeDb() async {
var client = await db;
client.close();
}
}
Login Screen Username Widget
Widget _buildUserNameField() {
return EnsureVisibleWhenFocused(
focusNode: _emailFocusNode,
child: TudoEmailWidget(
focusNode: _emailFocusNode,
prefixIcon: Icon(Icons.email),
labelText: "Username",
validator: (val) => Validators.validateEmail(val.trim()),
onSaved: (val) => _username = val.trim(),
// onChanged:(val) => _username = val.trim(),
),
);
}
Database Model
import 'package:meta/meta.dart';
class DbAppConstant {
#required
final int id;
#required
final String language;
#required
final String screen;
#required
final String value;
#required
final String label;
DbAppConstant({this.id, this.language, this.screen, this.value, this.label});
DbAppConstant.random()
: this.id = null,
this.language = 'en',
this.screen = 'login_screen',
this.value = 'email',
this.label = 'Email';
DbAppConstant.fromDb(Map<String, dynamic> map)
: id = map['id'],
language = map['language'],
screen = map['screen'],
value = map['value'],
label = map['label'];
Map<String, dynamic> toMapForDb() {
var map = Map<String, dynamic>();
map['id'] = id;
map['language'] = language;
map['screen'] = screen;
map['value'] = value;
map['label'] = label;
return map;
}
}

Npgsql - BulkCopy from ConcurrentQueue<T>

Let's say I have a method like this:
public async Task BulkCopy(ConcurrentQueue<Model> modelQueue, string connectionString)
{
while(modelQueue.IsEmpty == false)
{
try
{
using(NpgsqlConnection connection = new NpgsqlConnection(connectionString))
{
await connection.OpenAsync();
using(var writer = connection.BeginBinaryImport("COPY myTable (Id,Name,Something,SomethingElse)"))
{
// Is this what I'm supposed to do?
foreach(Model item in modelQueue)
{
writer.WriteRow(item);
}
}
}
}
}
}
Model has properties Guid Id, string Name, string Something, string SomethingElse(just like the table).
Can I use WriteRow() and pass in an entire object? Is this implementation way to go or I am doing it wrong?
The answer to this is pretty simple, since ConcurrentQueue implements IEnumerable all I had to do is run through the queue and write the data.
public async Task BulkCopy(ConcurrentQueue<Model> modelQueue, string connectionString)
{
while(modelQueue.IsEmpty == false)
{
try
{
using(NpgsqlConnection connection = new NpgsqlConnection(connectionString))
{
await connection.OpenAsync();
using(var writer = connection.BeginBinaryImport("COPY myTable (Id,Name,Something,SomethingElse) FROM STDIN (FORMAT BINARY)"))
{
foreach(Model item in modelQueue)
{
writer.StartRow();
writer.Write(item.Id, NpgsqlTypes.NpgsqlDbType.Uuid);
writer.Write(item.Name);
writer.Write(item.Something);
writer.Write(item.SomethingElse);
}
}
}
}
}
}
This seems to do the job. The time needed for 30000 records is average 540ms.

How to use Scroll while passing raw json Query to ElasticSearch using NEST

var query = #"
{
""query"": {
""match_all"": { }
}
}";
Func<SearchRequestParameters, SearchRequestParameters> requestParameters = a =>
a.SearchType(SearchType.Scan).Scroll(TimeSpan.FromSeconds(60));
var searchResult = await client.LowLevel.SearchAsync<SearchResponse<T>>(indexName, mappingName, query , requestParameters)
if (searchResult.Body.IsValid)
{
var scrollNo = 0;
var results = await client.ScrollAsync<T>("10s", searchResult.Body.ScrollId);
while (results.Documents.Any())
{
documents.AddRange(results.Documents);
scrollNo++;
results = await client.ScrollAsync<T>("10s", results.ScrollId);
return new Customresponse<T>
{
Documents = documents,
total = result.Body.Total
};
}
Would like to pull all data using scroll while passing raw json query. but scroll is not working properly while passing json raw query. Can anyone help on this ?.
Your example is nearly there but not quite; you're missing a closing brace for the while loop to collect all documents before returning the custom response.
Here's an example I just ran on the Stackoverflow data set, to return all questions tagged with nest
private IElasticClient _client;
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var defaultIndex = "default-index";
var connectionSettings = new ConnectionSettings(pool);
_client = new ElasticClient(connectionSettings);
var query = #"
{
""query"": {
""term"": {
""tags"": {
""value"": ""nest""
}
}
}
}";
var result = RunScrollAsync(query).Result.Dump();
}
private async Task<Customresponse<Question>> RunScrollAsync(string query)
{
var scrollTime = "10s";
// omit the .SearchType(Scan) which is deprecated. Not
// specifying means the first response contains the first set
// of documents
var esResponse = await _client.LowLevel.SearchAsync<SearchResponse<Question>>(
"posts",
"question",
query, r => r.Scroll(TimeSpan.FromSeconds(10))).ConfigureAwait(false);
if (esResponse.Body.IsValid && esResponse.Body.Documents.Any())
{
// assume you have less than 2,147,483,647 documents to return?
var documents = new List<Question>((int)esResponse.Body.Total);
documents.AddRange(esResponse.Body.Documents);
var scrollNo = 0;
var response = await _client.ScrollAsync<Question>(scrollTime, esResponse.Body.ScrollId).ConfigureAwait(false);;
// keep scrolling until no more documents are returned
while (response.Documents.Any())
{
documents.AddRange(response.Documents);
scrollNo++;
response = await _client.ScrollAsync<Question>(scrollTime, response.ScrollId).ConfigureAwait(false);;
}
return new Customresponse<Question>
{
Documents = documents,
total = response.Total
};
}
// return an empty result.
// Or throw an exception, or log - whatever you need to do
return new Customresponse<Question>
{
Documents = Enumerable.Empty<Question>(),
total = 0
};
}
public class Customresponse<T>
{
public IEnumerable<T> Documents { get; set; }
public long total { get; set; }
}
This returns all 342 questions, with a total of 342 (Data set is from June 2016).

RavenDB StartsWith Variable on left-hand-side of Query

I have a collection of docs with a Url property that looks like:
{ Url: www.test.com }
{ Url: www.test.com/abc }
{ Url: www.test.com/abc/xyz }
I want to query the docs with www.test.com/abc such that I am returned all the documents that match the url on everything up-to-and-including the query url. i.e. the results would return:
{ Url: www.test.com }
{ Url: www.test.com/abc }
Below is the query I have written to accomplish this. As you can see, it relies on the 'Starts With' to be performed on the queryTerm, as opposed to being performed on a property of the document (the left-hand-side of the query).
var queryTerm = "www.test.com/abc";
pages = session.Query<MyDocument>()
.Where(x => queryTerm.StartsWith(x.Url));
This does not work in Raven; I get an 'Expression type not supported' error. Any ideas?
Raven doesn't appear to support that in its expression. Interestingly, the query x => queryTerm.StartsWith(x.Url) can be exploded into something along the lines of:
x => x.Url == queryTerm ||
x.Url == queryTerm.Remove(queryTerm.Length - 1) ||
x.Url == queryTerm.Remove(queryTerm.Length - 2) ||
x.Url == queryTerm.Remove(queryTerm.Length - 3) ||
x.Url == queryTerm.Remove(queryTerm.Length - 4) ||
etc.
Which can be codified as:
var terms = Enumerable.Range(0, queryTerm.Length)
.Skip(1)
.Select(i => queryTerm.Remove(i));
pages = session.Query<MyDocument>()
.Where(x => terms.Contains(x.Url));
Except Raven doesn't supports Contains() like that either. So I coded this up:
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client;
using Raven.Client.Embedded;
using Raven.Client.Linq;
namespace RavenTest
{
[TestFixture]
public class HowToDoBackwardsStartsWithInRaven
{
private readonly string[] _testUrls = new[]
{
"www.bad.com",
"www.test.com",
"www.test.com/abc",
"www.test.com/abc/xyz"
};
private readonly string _queryTerm = "www.test.com/abc";
private readonly string[] _expectedResults = new[]
{
"www.test.com",
"www.test.com/abc"
};
[Test]
public void FailsWithContains()
{
List<MyDocument> results;
using (var store = InitStore())
{
LoadTestData(store);
using (var session = store.OpenSession())
{
var terms = GetAllStartingSubstrings(_queryTerm);
results = session.Query<MyDocument>()
.Where(x => terms.Contains(x.Url))
.ToList();
}
}
Assert.That(results.Select(p => p.Url), Is.EquivalentTo(_expectedResults));
}
[Test]
public void SucceedsWithSuperWhere()
{
List<MyDocument> results;
using (var store = InitStore())
{
LoadTestData(store);
using (var session = store.OpenSession())
{
var terms = GetAllStartingSubstrings(_queryTerm);
var query = session.Query<MyDocument>()
.Where(x => x.Url.Length <= _queryTerm.Length);
foreach (var term in terms)
query = query.Where(x => x.Url == term ||
x.Url.Length != term.Length);
results = query.ToList();
}
}
Assert.That(results.Select(p => p.Url), Is.EquivalentTo(_expectedResults));
}
private static IDocumentStore InitStore()
{
var store = new EmbeddableDocumentStore
{
RunInMemory = true,
};
return store.Initialize();
}
private static string[] GetAllStartingSubstrings(string queryTerm)
{
return Enumerable.Range(0, queryTerm.Length)
.Skip(1)
.Select(i => queryTerm.Remove(i))
.ToArray();
}
private void LoadTestData(IDocumentStore store)
{
using (var session = store.OpenSession())
{
foreach (var testUrl in _testUrls)
session.Store(new MyDocument {Url = testUrl});
session.SaveChanges();
}
}
public class MyDocument
{
public string Id { get; set; }
public string Url { get; set; }
}
}
}
Now... I have no idea how efficient the indexing will be on that but the test does pass.