dotnet add package Core.Arango
This driver has various extensions available.
Extension | Nuget | Command |
---|---|---|
Core.Arango.Migration | dotnet add package Core.Arango.Migration | |
Core.Arango.DataProtection | dotnet add package Core.Arango.DataProtection | |
Core.Arango.DevExtreme | dotnet add package Core.Arango.DevExtreme | |
Core.Arango.Serilog | dotnet add package Core.Arango.Serilog |
Realm optionally prefixes all further database handles (e.g. "myproject-database")
Context is completely thread-safe and can be shared for your whole application
// from connection string
var arango = new ArangoContext("Server=http://localhost:8529;Realm=myproject;User=root;Password=;");
// from connection string - NO_AUTH
var arango = new ArangoContext("Server=http://localhost:8529;");
// from connection string with PascalCase serialization
var arango = new ArangoContext("Server=http://localhost:8529;Realm=myproject;User=root;Password=;",
new ArangoConfiguration
{
Serializer = new ArangoJsonSerializer(new ArangoJsonDefaultPolicy())
});
For AspNetCore DI extension is available:
public void ConfigureServices(IServiceCollection services)
{
// add with connection string
services.AddArango(Configuration.GetConnectionString("Arango"));
// or add with custom configuration
services.AddArango((sp, config) =>
{
config.ConnectionString = Configuration.GetConnectionString("Arango");
config.Serializer = new ArangoJsonSerializer(new ArangoJsonDefaultPolicy());
var logger = sp.GetRequiredService<ILogger<Startup>>();
config.QueryProfile = (query, bindVars, stats) =>
{
var boundQuery = query;
// replace parameters with bound values
foreach (var p in bindVars.OrderByDescending(x => x.Key.Length))
boundQuery = boundQuery.Replace("@" + p.Key, JsonConvert.SerializeObject(p.Value));
logger.LogInformation(boundQuery);
}
});
}
[ApiController]
[Route("api/demo")]
public class DemoController : Controller
{
private readonly IArangoContext _arango;
public DemoController(IArangoContext arango)
{
_arango = arango;
}
}
The specific serializer can be configured on setting the Arango configuration when creating the context.
Supported serializer:
Microsoft System.Text.Json
using Core.Arango.Serialization.Json;
// Specify with PascalCase
Serializer = new ArangoJsonSerializer(new ArangoJsonDefaultPolicy());
// Specify with camelCase
Serializer = new ArangoJsonSerializer(new ArangoJsonCamelCasePolicy());
Newtonsoft.Json
using Core.Arango.Serialization.Newtonsoft;
// Specify with PascalCase
Serializer = new ArangoNewtonsoftSerializer(new ArangoNewtonsoftDefaultContractResolver())
// Specify with CamelCase
Serializer = new ArangoNewtonsoftSerializer(new ArangoNewtonsoftCamelCaseContractResolver())
Microsoft System.Text.Json
using Core.Arango.Serialization.Json;
// Model example: uses DateTimeOffset
private class DateTimeOffsetEntity
{
public DateTimeOffset A { get; set; }
public DateTimeOffset B { get; set; }
}
// Define serializer on Arango context
Serializer = new ArangoJsonSerializer(new ArangoJsonDefaultPolicy())
{
UseTimestamps = true // Serialize DateTime / DateTimeOffset to Unix Timestamp (in milliseconds)
};
// Use
await arango.Document.CreateAsync("database", "collection", instanceOfDateTimeOffsetEntity); // Converts between Unix Timestamp in DB and C# data type
Newtonsoft.Json
// not supported
await arango.Database.CreateAsync("database");
await arango.Collection.CreateAsync("database", "collection", ArangoCollectionType.Document);
await arango.Collection.CreateAsync("database", new ArangoCollection
{
Name = "paddedcollection",
Type = ArangoCollectionType.Document,
KeyOptions = new ArangoKeyOptions
{
Type = ArangoKeyType.Padded,
AllowUserKeys = false
}
});
await arango.Document.CreateAsync("database", "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 1
});
Retrieve documents from a single collection based on a LinQ statement. See LinQ help for more information.
var list1 = await Arango.Query<Entity>("database").Where(p => p.Id == "myid").ToListAsync();
Get (many) documents by providing a list of keys or objects with "Key" and optional "Revision" property
var list1 = await Arango.Document.GetManyAsync<Entity>("database", "collection", new List<string> {
"1", "2"
});
var list2 = await Arango.Document.GetManyAsync<Entity>("database", "collection", new List<object>
{
new
{
Key = "1"
},
new
{
Key = "2"
}
});
await arango.Document.UpdateAsync("database", "collection", new
{
Key = Guid.Parse("some-guid"),
SomeValue = 2
});
// depending on serializer
using System.Text.Json.Serialization;
// or
using Newtonsoft.Json;
class ComplexEntity
{
public string Key { get; set; }
public string Name { get; set; }
// Will never be read or written from or to arangodb
[JsonIgnore]
public object Data { get; set; }
// Newtonsoft only
// Will only be read from query, on write will be ignored
[ArangoIgnore]
public object CalculatedProperty { get; set; }
}
await arango.Document.UpdateAsync("database", "collection", new ComplexEntity {
Key = "123",
Name = "SomeName"
});
var col = "collection";
var list = new List<int> {1, 2, 3};
// System.Text.Json
var result = await arango.Query.ExecuteAsync<JsonObject>("database",
$"FOR c IN {col:@} FILTER c.SomeValue IN {list} RETURN c");
// Newtonsoft.Json
var result = await arango.Query.ExecuteAsync<JObject>("database",
$"FOR c IN {col:@} FILTER c.SomeValue IN {list} RETURN c");
Results in AQL injection save syntax:
'FOR c IN @@C1 FILTER c.SomeValue IN @P2 RETURN c'
{
"@C1": "collection",
"P2": [1, 2, 3]
}
for collections parameters, formats '@'
, 'C'
and 'c'
are supported. They all mean the same format.
var collectionName = "collection";
var list = new List<int> {1, 2, 3};
FormattableString forPart = $"FOR c IN {collectionName:@}";
FormattableString filterPart = $"FILTER c.SomeValue IN {list}";
FormattableString returnPart = $"RETURN c";
// System.Text.Json
var result = await arango.Query.ExecuteAsync<JsonObject>("database",
$"{forPart} {filterPart} {returnPart}");
// Newtonsoft.Json
var result = await arango.Query.ExecuteAsync<JObject>("database",
$"{forPart} {filterPart} {returnPart}");
Note
If using multiple FormattableString
variables, every single injected string variable needs to be of type FormattableString
or Arango will return an error stating:
AQL: syntax error, unexpected bind parameter near @P3
public class MyClass2
{
public MyClass Data { get; set; }
public double CustomData { get; set; }
}
var collectionName = "collection";
var list = new List<int> {1, 2, 3};
var pointA = "[48.157741, 11.503159]";
var pointB = "[48.155739, 11.491601]";
FormattableString forPart = $"FOR c IN {collectionName:@}";
FormattableString filterPart = $"FILTER c.SomeValue IN {list}";
FormattableString letDistance = $"LET distance = GEO_DISTANCE({pointA}, {pointB})";
FormattableString returnPart = $"RETURN {{Data: c, Distance: distance}}";
var result = await arango.Query.ExecuteAsync<MyClass2>("database",
$"{forPart} {filterPart} {letDistance} {returnPart}");
// insert 100.000 entities
await Arango.Document.CreateManyAsync("database", "collection", Enumerable.Range(1, 100000).Select(x => new Entity { Value = x }));
// iterate in batches over 100.000 entity ids
await foreach (var x in Arango.Query.ExecuteStreamAsync<string>("database", $"FOR c IN collection RETURN c._id"))
{
Process(x)
}
LINQ support has been adapted from https://github.com/ra0o0f/arangoclient.net. Internalized re-motion relinq since their nuget is quite outdated
Work in progress as some things are deprecated or need to be modernized
.Intersect(list).Count()
will generate valid AQL but will apply the operators in the wrong order).Average
.Cast
.Distinct
.GroupJoin
.Intersect
.Join
.Last
.Reverse
var list1 = await Arango.Query<Project>("database").Where(p => p.Id == "myid").ToListAsync();
var q = Arango.Query<Project>("database").Where(p => p.Id == "myid");
// Execute
await q.ToListAsync();
// Debug
var (aql, bindVars) = q.ToAql();
var q = Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Select(x => new
{
x.Key,
x.Name,
ClientName = Aql.Document<Client>("Client", x.ClientKey).Name
});
// Execute
await q.ToListAsync();
// Debug
var (aql, bindVars) = q.ToAql();
Note: database/transaction is only specified on the root expression, not on the inner one
var q = from p in Arango.Query<Project>("test")
let clients = (from c in Arango.Query<Client>() select c.Key)
select new {p.Name, Clients = Aql.As<string[]>(clients) };
await q.ToListAsync();
var q = Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Update(x => new
{
Name = Aql.Concat(x.Name, "2")
}, x => x.Key);
await q.ToListAsync();
var q = Arango.Query<Project>("test")
.PartialUpdate(p=>p.hobbies, x => new
{
"val1"=1,"val2"=2
}, x => x.Key);
await q.ToListAsync();
will be converted to:
FOR doc IN Project
UPDATE doc WITH {
hobbies: PUSH(doc.hobbies, {"val1": 1, "val2":2})
} IN users
await Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Remove().In<Project>().Select(x => x.Key).ToListAsync();
"Option" is the query option that exists in ArangoDb. Some operations like FOR / Graph Traversal / SEARCH / COLLECT / INSERT / UPDATE / REPLACE / UPSERT / REMOVE would support "Options".
await Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Options(() => new { indexHint = "byName" }).ToListAsync();
await arango.Index.CreateAsync("database", "collection", new ArangoIndex
{
Fields = new List<string> {"SomeValue"},
Type = ArangoIndexType.Hash
});
await arango.Analyzer.CreateAsync("database", new ArangoAnalyzer
{
Name = "text_de_nostem",
Type = "text",
Properties = new ArangoAnalyzerProperties
{
Locale = "de.utf-8",
Case = ArangoAnalyzerCase.Lower,
Accent = false,
Stopwords = new List<string>(),
Stemming = false
},
Features = new List<string> { "position", "norm", "frequency" }
});
await arango.View.CreateAsync("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 = ArangoSortDirection.Asc
}
}
});
await arango.Collection.CreateAsync("database", "vertices", ArangoCollectionType.Document);
await Arango.Collection.CreateAsync("database", "edges", ArangoCollectionType.Edge);
await Arango.Graph.CreateAsync("database", new ArangoGraph
{
Name = "graph",
EdgeDefinitions = new List<ArangoEdgeDefinition>
{
new()
{
Collection = "edges",
From = new List<string> {"vertices"},
To = new List<string> {"vertices"}
}
}
});
await arango.Graph.Vertex.CreateAsync("database", "graph", "vertices", new
{
Key = "alice",
Name = "Alice"
});
await arango.Graph.Vertex.CreateAsync("database", "graph", "vertices", new
{
Key = "bob",
Name = "Bob"
});
await arango.Graph.Edge.CreateAsync("database", "graph", "edges", new
{
Key = "ab",
From = "vertices/alice",
To = "vertices/bob",
Label = "friend"
});
await arango.Graph.Edge.UpdateAsync("database", "graph", "edges", "ab", new
{
Label = "foe"
});
await arango.Graph.Vertex.RemoveAsync("database", "graph", "vertices", "bob");
await arango.Function.CreateAsync("database", new ArangoFunctionDefinition
{
Name = "CUSTOM::TIMES10",
Code = "function (a) { return a * 10; }",
IsDeterministic = true
});
var transaction = await arango.Transaction.BeginAsync("database", new ArangoTransaction
{
Collections = new ArangoTransactionScope
{
Write = new List<string> { "collection" }
}
});
await arango.Document.CreateAsync(transaction, "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 1
});
await arango.Document.CreateAsync(transaction, "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 2
});
await arango.Transaction.CommitAsync(transaction);
// Build Foxx service zip archive
await using var ms = new MemoryStream();
using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true, Encoding.UTF8))
{
await using (var manifest = zip.CreateEntry("manifest.json").Open())
{
await manifest.WriteAsync(Encoding.UTF8.GetBytes(@"
{
""$schema"": ""http://json.schemastore.org/foxx-manifest"",
""name"": ""SampleService"",
""description"": ""test"",
""version"": ""1.0.0"",
""license"": ""MIT"",
""engines"": {
""arangodb"": ""^3.0.0""
},
""main"": ""index.js"",
""configuration"": {
""currency"": {
""description"": ""Currency symbol to use for prices in the shop."",
""default"": ""$"",
""type"": ""string""
},
""secretKey"": {
""description"": ""Secret key to use for signing session tokens."",
""type"": ""password""
}
}
}
"));
}
await using (var readme = zip.CreateEntry("README").Open())
{
await readme.WriteAsync(Encoding.UTF8.GetBytes(@"
README!
"));
}
await using (var index = zip.CreateEntry("index.js").Open())
{
await index.WriteAsync(Encoding.UTF8.GetBytes(@"
'use strict';
const createRouter = require('@arangodb/foxx/router');
const router = createRouter();
module.context.use(router);
router.get('/hello-world', function (req, res) {{
res.send({{ hello: 'world' }});
}})
.response(['application/json'], 'A generic greeting.')
.summary('Generic greeting')
.description('Prints a generic greeting.');
"));
}
}
ms.Position = 0;
// install service
await Arango.Foxx.InstallServiceAsync("database", "/sample/service", ArangoFoxxSource.FromZip(ms));
// list services excluding system services
var services = await Arango.Foxx.ListServicesAsync("database", true);
// call service
var res = await Arango.Foxx.GetAsync<Dictionary<string, string>>("database", "/sample/service/hello-world");
Assert.Equal("world", res["hello"]);
var backup = await Arango.Backup.CreateAsync(new ArangoBackupRequest
{
AllowInconsistent = false,
Force = true,
Label = "test",
Timeout = 30
});
var backups = await Arango.Backup.ListAsync();
await Arango.Backup.RestoreAsync(backup.Id);
await Arango.Backup.DeleteAsync(backup.Id);