Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/coronabytes/dotnet-arangodb
.NET Driver for ArangoDB
https://github.com/coronabytes/dotnet-arangodb
arangodb arangodb-client csharp database driver graph linq netcore
Last synced: 6 days ago
JSON representation
.NET Driver for ArangoDB
- Host: GitHub
- URL: https://github.com/coronabytes/dotnet-arangodb
- Owner: coronabytes
- License: apache-2.0
- Created: 2020-01-31T23:59:44.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2024-11-30T09:36:55.000Z (about 2 months ago)
- Last Synced: 2025-01-13T05:02:46.190Z (13 days ago)
- Topics: arangodb, arangodb-client, csharp, database, driver, graph, linq, netcore
- Language: C#
- Homepage:
- Size: 970 KB
- Stars: 67
- Watchers: 3
- Forks: 18
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![Build](https://github.com/coronabytes/dotnet-arangodb/actions/workflows/build.yml/badge.svg)](https://github.com/coronabytes/dotnet-arangodb/actions/workflows/build.yml)
[![Nuget](https://img.shields.io/nuget/v/Core.Arango)](https://www.nuget.org/packages/Core.Arango)
[![Nuget](https://img.shields.io/nuget/dt/Core.Arango)](https://www.nuget.org/packages/Core.Arango)```
dotnet add package Core.Arango
```# .NET driver for ArangoDB
- .NET Standard 2.0, 2.1, .NET 8 and .NET 9 driver for ArangoDB 3.11+
- LINQ support (WIP)
- Newtonsoft.Json and System.Text.Json serialization support with PascalCase and camelCase options
- Updates from anonymous types supported as (Id, Key, Revision, From, To) properties are translated to (_id, _key, _rev, _from, _to)
- This means these property names are reserved and cannot be used for something else (e.g. "To" property in email collection)# Extensions
This driver has various [extensions](https://github.com/coronabytes/dotnet-arangodb-extensions) available.| Extension | Nuget | Command |
| :--- | :--- | :--- |
| [Core.Arango.Migration](https://www.nuget.org/packages/Core.Arango.Migration) | ![Nuget](https://img.shields.io/nuget/v/Core.Arango.Migration) ![Nuget](https://img.shields.io/nuget/dt/Core.Arango.Migration) | dotnet add package Core.Arango.Migration |
| [Core.Arango.DataProtection](https://www.nuget.org/packages/Core.Arango.DataProtection) | ![Nuget](https://img.shields.io/nuget/v/Core.Arango.DataProtection) ![Nuget](https://img.shields.io/nuget/dt/Core.Arango.DataProtection) | dotnet add package Core.Arango.DataProtection |
| [Core.Arango.DevExtreme](https://www.nuget.org/packages/Core.Arango.DevExtreme) | ![Nuget](https://img.shields.io/nuget/v/Core.Arango.DevExtreme) ![Nuget](https://img.shields.io/nuget/dt/Core.Arango.DevExtreme) | dotnet add package Core.Arango.DevExtreme |
| [Core.Arango.Serilog](https://www.nuget.org/packages/Core.Arango.Serilog) | ![Nuget](https://img.shields.io/nuget/v/Core.Arango.Serilog) ![Nuget](https://img.shields.io/nuget/dt/Core.Arango.Serilog) | dotnet add package Core.Arango.Serilog |# Examples
## Initialize context
- Realm optionally prefixes all further database handles (e.g. "myproject-database")
- Context is completely thread-safe and can be shared for your whole application
```csharp
// 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:
```csharp
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>();
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);
}
});
}
``````csharp
[ApiController]
[Route("api/demo")]
public class DemoController : Controller
{
private readonly IArangoContext _arango;public DemoController(IArangoContext arango)
{
_arango = arango;
}
}
```### Serializer definition
The specific serializer can be configured on setting the Arango configuration when creating the context.Supported serializer:
- **Microsoft System.Text.Json**
```csharp
using Core.Arango.Serialization.Json;// Specify with PascalCase
Serializer = new ArangoJsonSerializer(new ArangoJsonDefaultPolicy());// Specify with camelCase
Serializer = new ArangoJsonSerializer(new ArangoJsonCamelCasePolicy());
```- **Newtonsoft.Json**
```csharp
using Core.Arango.Serialization.Newtonsoft;// Specify with PascalCase
Serializer = new ArangoNewtonsoftSerializer(new ArangoNewtonsoftDefaultContractResolver())// Specify with CamelCase
Serializer = new ArangoNewtonsoftSerializer(new ArangoNewtonsoftCamelCaseContractResolver())
```
### Serialize DateTime / DateTimeOffset to Unix Timestamp
- **Microsoft System.Text.Json**
```csharp
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**
```csharp
// not supported
```
## Create database
```csharp
await arango.Database.CreateAsync("database");
```## Create collection
```csharp
await arango.Collection.CreateAsync("database", "collection", ArangoCollectionType.Document);
```### Collection with keys in ascending lexicographical sort order (ideal for log/audit collections)
```csharp
await arango.Collection.CreateAsync("database", new ArangoCollection
{
Name = "paddedcollection",
Type = ArangoCollectionType.Document,
KeyOptions = new ArangoKeyOptions
{
Type = ArangoKeyType.Padded,
AllowUserKeys = false
}
});
```## Create document
```csharp
await arango.Document.CreateAsync("database", "collection", new
{
Key = Guid.NewGuid(),
SomeValue = 1
});
```## Get documents
Retrieve documents from a single collection based on a LinQ statement. See LinQ help for more information.
```csharp
var list1 = await Arango.Query("database").Where(p => p.Id == "myid").ToListAsync();
```Get (many) documents by providing a list of keys or objects with "Key" and optional "Revision" property
```csharp
var list1 = await Arango.Document.GetManyAsync("database", "collection", new List {
"1", "2"
});var list2 = await Arango.Document.GetManyAsync("database", "collection", new List
{
new
{
Key = "1"
},
new
{
Key = "2"
}
});
```## Update document
```csharp
await arango.Document.UpdateAsync("database", "collection", new
{
Key = Guid.Parse("some-guid"),
SomeValue = 2
});
```### Ignore specific properties
```csharp
// 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"
});
```## Custom Query
### Bindable parameters
```csharp
var col = "collection";
var list = new List {1, 2, 3};// System.Text.Json
var result = await arango.Query.ExecuteAsync("database",
$"FOR c IN {col:@} FILTER c.SomeValue IN {list} RETURN c");// Newtonsoft.Json
var result = await arango.Query.ExecuteAsync("database",
$"FOR c IN {col:@} FILTER c.SomeValue IN {list} RETURN c");
```Results in AQL injection save syntax:
```js
'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.### Split queries into parts
```csharp
var collectionName = "collection";
var list = new List {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("database",
$"{forPart} {filterPart} {returnPart}");// Newtonsoft.Json
var result = await arango.Query.ExecuteAsync("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:
```exception
AQL: syntax error, unexpected bind parameter near @P3
```### Inject data into Arango return
```csharp
public class MyClass2
{
public MyClass Data { get; set; }
public double CustomData { get; set; }
}var collectionName = "collection";
var list = new List {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("database",
$"{forPart} {filterPart} {letDistance} {returnPart}");
```## Query with async enumerator
```csharp
// 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("database", $"FOR c IN collection RETURN c._id"))
{
Process(x)
}
```## Linq
LINQ support has been adapted from https://github.com/ra0o0f/arangoclient.net. Internalized re-motion relinq since their nuget is quite outdatedWork in progress as some things are deprecated or need to be modernized
- Basic queries generally work
- Some more complex queries (chaining multiple operators, complex subqueries, etc.) are not supported yet
- All these issues are solvable and pull requests are accepted
- NOTE : Some queries will blow up at run time and you will get an exception, but some queries will actually generate valid AQL with the wrong semantics.
- Combining multiple result operators is not recommended with the current implementation (e.g. `.Intersect(list).Count()` will generate valid AQL but will apply the operators in the wrong order)
- The following operators are not yet supported:
- `.Average`
- `.Cast`
- `.Distinct`
- `.GroupJoin`
- `.Intersect`
- `.Join`
- `.Last`
- `.Reverse`
- All other operators should be supported. If you find any queries that fail or generate incorrect AQL, open an issue so we can at least document it. A PR with a (failing) unit test would be even better!
- There is also development done on a driver without relinq and aggregate support
- Configurable property / collection / group naming for camelCase support### Simple query
```csharp
var list1 = await Arango.Query("database").Where(p => p.Id == "myid").ToListAsync();
```### AQL debug
```csharp
var q = Arango.Query("database").Where(p => p.Id == "myid");// Execute
await q.ToListAsync();// Debug
var (aql, bindVars) = q.ToAql();
```### DOCUMENT() lookup
```csharp
var q = Arango.Query("test")
.Where(x => x.Name == "Project A")
.Select(x => new
{
x.Key,
x.Name,
ClientName = Aql.Document("Client", x.ClientKey).Name
});// Execute
await q.ToListAsync();// Debug
var (aql, bindVars) = q.ToAql();
```### Let clauses for subqueries
Note: database/transaction is only specified on the root expression, not on the inner one
```csharp
var q = from p in Arango.Query("test")
let clients = (from c in Arango.Query() select c.Key)
select new {p.Name, Clients = Aql.As(clients) };await q.ToListAsync();
```### Update
```csharp
var q = Arango.Query("test")
.Where(x => x.Name == "Project A")
.Update(x => new
{
Name = Aql.Concat(x.Name, "2")
}, x => x.Key);
await q.ToListAsync();
```### Partial Update
- Note: To push an object to inner collection.```csharp
var q = Arango.Query("test")
.PartialUpdate(p=>p.hobbies, x => new
{
"val1"=1,"val2"=2
}, x => x.Key);
await q.ToListAsync();
```will be converted to:
```sql
FOR doc IN Project
UPDATE doc WITH {
hobbies: PUSH(doc.hobbies, {"val1": 1, "val2":2})
} IN users
```### Remove
```csharp
await Arango.Query("test")
.Where(x => x.Name == "Project A")
.Remove().In().Select(x => x.Key).ToListAsync();
```### OPTIONS
"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".```csharp
await Arango.Query("test")
.Where(x => x.Name == "Project A")
.Options(() => new { indexHint = "byName" }).ToListAsync();
```## Create index
```csharp
await arango.Index.CreateAsync("database", "collection", new ArangoIndex
{
Fields = new List {"SomeValue"},
Type = ArangoIndexType.Hash
});
```## Create analyzer
```csharp
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(),
Stemming = false
},
Features = new List { "position", "norm", "frequency" }
});
```## Create view
```csharp
await arango.View.CreateAsync("database", new ArangoView
{
Name = "SomeView",
Links = new Dictionary
{
["collection"] = new ArangoLinkProperty
{
Fields = new Dictionary
{
["SomeProperty"] = new ArangoLinkProperty
{
Analyzers = new List
{
"text_en"
}
}
}
}
},
PrimarySort = new List
{
new ArangoSort
{
Field = "SomeProperty",
Direction = ArangoSortDirection.Asc
}
}
});
```## Create graph
```csharp
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
{
new()
{
Collection = "edges",
From = new List {"vertices"},
To = new List {"vertices"}
}
}
});
```## Graph manipulation
```csharp
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");
```## Create custom function
```csharp
await arango.Function.CreateAsync("database", new ArangoFunctionDefinition
{
Name = "CUSTOM::TIMES10",
Code = "function (a) { return a * 10; }",
IsDeterministic = true
});
```## Stream transactions
```csharp
var transaction = await arango.Transaction.BeginAsync("database", new ArangoTransaction
{
Collections = new ArangoTransactionScope
{
Write = new List { "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);
```## Foxx services
```csharp
// 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>("database", "/sample/service/hello-world");
Assert.Equal("world", res["hello"]);
```## Hot Backup (Enterprise Edition only)
```csharp
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);
```