- This is a minimalistic .NET driver for ArangoDB (3.5+) with adapters to Serilog and DevExtreme
- The key difference to any other available driver is the ability to switch databases on a per request basis, which allows for easy database per tenant deployments
- Id, Key, From, To properties will always be translated to their respective arango form (_id, _key, _from, _to), which allows to construct updates from anonymous types
- First parameter of any method in most cases is an ArangoHandle which has implicit conversion from string and GUID
- e.g. "master" and "logs" database and GUID for each tenant
- Realm prefixes all further database handles (e.g. "myproject-database")
var arango = new ArangoContext("Server=http://localhost:8529;Realm=myproject;User=root;Password=;");
await arango.CreateCollectionAsync("database", "collection", ArangoCollectionType.Document);
await arango.EnsureIndexAsync("database", "collection", new ArangoIndex
{
Fields = new List<string> {"SomeValue"},
Type = ArangoIndexType.Hash
});
await arango.CreateAnalyzerAsync("database", new ArangoAnalyzer
{
Name = "text_de_nostem",
Type = "text",
Properties = new ArangoAnalyzerProperties
{
Locale = "de.utf-8",
Case = "lower",
Accent = false,
Stopwords = new List<string>(),
Stemming = false
},
Features = new List<string> { "position", "norm", "frequency" }
});
await arango.CreateViewAsync("database", new ArangoView
{
Name = "SomeView",
Links = new Dictionary<string, ArangoLinkProperty>
{
["collection"] = new ArangoLinkProperty
{
Fields = new Dictionary<string, ArangoLinkProperty>
{
["SomeProperty"] = new ArangoLinkProperty
{
Analyzers = new List<string>
{
"text_en"
}
}
}
}
},
PrimarySort = new List<ArangoSort>
{
new ArangoSort
{
Field = "SomeProperty",
Direction = "asc"
}
}
});
await arango.CreateGraphAsync("database", new ArangoGraph
{
Name = "SomeGraph",
EdgeDefinitions = new List<ArangoEdgeDefinition>
{
new ArangoEdgeDefinition
{
Collection = "collection_edges",
From = new List<string> {"collection"},
To = new List<string> {"collection"}
}
}
});
await arango.CreateDocumentAsync("database", "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 1
});
await arango.UpdateDocumentAsync("database", "collection", new
{
Key = Guid.Parse("some-guid"),
SomeValue = 2
});
var list = new List<int> {1, 2, 3};
var result = await arango.QueryAsync<JObject>("database",
$"FOR c IN collection FILTER c.SomeValue IN {list} RETURN c");
var transaction = await arango.BeginTransactionAsync("database", new ArangoTransaction
{
Collections = new ArangoTransactionScope
{
Write = new List<string> { "collection" }
}
});
await arango.CreateDocumentAsync(transaction, "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 1
});
await arango.CreateDocumentAsync(transaction, "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 2
});
await arango.CommitTransactionAsync(transaction);
webBuilder.UseSerilog((c, log) =>
{
var arango = c.Configuration.GetConnectionString("Arango");
log.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Sink(new ArangoSerilogSink(new ArangoContext(arango),
database: "logs",
collection: "logs",
batchPostingLimit: 50,
TimeSpan.FromSeconds(2)),
restrictedToMinimumLevel: LogEventLevel.Information);
// This is unreliable...
if (Environment.UserInteractive)
log.WriteTo.Console(theme: AnsiConsoleTheme.Code);
});
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new ArangoContext(Configuration.GetConnectionString("Arango")));
var dataProtection = services.AddDataProtection()
.SetApplicationName("App")
.SetDefaultKeyLifetime(TimeSpan.FromDays(30));
dataProtection.PersistKeysToArangoDB(database: "dataprotection", collection: "keys");
}
- Translates DevExtreme queries to AQL with filtering, sorting, grouping and summaries on a 'best effort basis'
- DataSourceLoadOptions need to be parsed by Newtonsoft Json and not System.Text.Json
- services.AddControllers().AddNewtonsoftJson();
- Parameters are escaped with bindvars
- Property names
- need to match ^[A-Za-z_][A-Za-z0-9\.]*$
- need to be within 128 characters
- can be customized with ValidPropertyName() and PropertyTransform()
- Developer retains full control over the projection - full document by default
- Check safety limits in settings if your query fails
- Support for ArangoSearch is coming soon
- Not so soon...
- Groupings by foreign key can be enriched with displayValue using GroupLookups()
private static readonly ArangoTransformSettings Transform = new ArangoTransformSettings
{
IteratorVar = "x",
Key = "key",
Filter = "x.Active == true",
RestrictGroups = new HashSet<string>
{
"ProjectKey", "UserKey"
}, // null allows all groupings (not recommended) / empty hashset disables grouping
GroupLookups = new Dictionary<string, string>
{
["ProjectKey"] = "DOCUMENT(AProject, ProjectKey).Name",
["UserKey"] = "DOCUMENT(AUser, UserKey).Name"
}
};
[HttpGet("dx-query")]
public async Task<ActionResult<DxLoadResult>> DxQuery([FromQuery] DataSourceLoadOptions loadOptions)
{
var arangoTransform = new ArangoTransform(loadOptions, Transform);
if (!arangoTransform.Transform(out var error))
return BadRequest(error);
return await arangoTransform.ExecuteAsync<SomeEntity>(arango, "database", "collection");
}