Giter VIP home page Giter VIP logo

dotnet-arangodb's Introduction

Build Nuget Nuget

dotnet add package Core.Arango

.NET driver for ArangoDB

  • .NET Standard 2.0, 2.1 and .NET 8 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 available.

Extension Nuget Command
Core.Arango.Migration Nuget Nuget dotnet add package Core.Arango.Migration
Core.Arango.DataProtection Nuget Nuget dotnet add package Core.Arango.DataProtection
Core.Arango.DevExtreme Nuget Nuget dotnet add package Core.Arango.DevExtreme
Core.Arango.Serilog Nuget Nuget 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

    // 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;
        }
    }

Serializer definition

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())

Serialize DateTime / DateTimeOffset to Unix Timestamp

  • 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

Create database

await arango.Database.CreateAsync("database");

Create collection

await arango.Collection.CreateAsync("database", "collection", ArangoCollectionType.Document);

Collection with keys in ascending lexicographical sort order (ideal for log/audit collections)

await arango.Collection.CreateAsync("database", new ArangoCollection
{
    Name = "paddedcollection",
    Type = ArangoCollectionType.Document,
    KeyOptions = new ArangoKeyOptions
    {
        Type = ArangoKeyType.Padded,
        AllowUserKeys = false
    }
});

Create document

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.

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"
  }
});

Update document

await arango.Document.UpdateAsync("database", "collection", new
{
    Key = Guid.Parse("some-guid"),
    SomeValue = 2
});

Ignore specific properties

// 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

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.

Split queries into parts

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

Inject data into Arango return

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}");

Query with async enumerator

// 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

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

  • 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

var list1 = await Arango.Query<Project>("database").Where(p => p.Id == "myid").ToListAsync();

AQL debug

var q = Arango.Query<Project>("database").Where(p => p.Id == "myid");

// Execute
await q.ToListAsync();

// Debug
var (aql, bindVars) = q.ToAql();

DOCUMENT() lookup

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();

Let clauses for subqueries

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();

Update

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();

Partial Update

  • Note: To push an object to inner collection.
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

Remove

await Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Remove().In<Project>().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".

await Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
.Options(() => new { indexHint = "byName" }).ToListAsync();

Create index

await arango.Index.CreateAsync("database", "collection", new ArangoIndex
{
    Fields = new List<string> {"SomeValue"},
    Type = ArangoIndexType.Hash
});

Create analyzer

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" }
});

Create view

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
        }
    }
});

Create graph

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"}
        }
    }
});

Graph manipulation

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

await arango.Function.CreateAsync("database", new ArangoFunctionDefinition
{
  Name = "CUSTOM::TIMES10",
  Code = "function (a) { return a * 10; }",
  IsDeterministic = true
});

Stream transactions

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);

Foxx services

// 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"]);

Hot Backup (Enterprise Edition only)

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);

dotnet-arangodb's People

Contributors

andyzukunft avatar asolomatin avatar coronabytes avatar dependabot[bot] avatar erff-h avatar erffun avatar harrycordewener avatar jtone123 avatar robsiera avatar thecraftymaker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

dotnet-arangodb's Issues

Traversal options suppert

current version Traversal options on use linq does not work.
I checked the source code,options suppert commented out.
can be released a new version in nuget to repair it?

this is current version source code:
if (traversalClause.Options != null) { // TODO: corona options //QueryText.AppendFormat(" options {0} ", new DocumentSerializer(Db).SerializeWithoutReader(traversalClause.Options.Value)); }

Key parameter missing when getting Vertex of Graph

I get an 500 "method not supported" error when using the GetAsync method of the GraphVertexModule

public async Task<TR> GetAsync<TR>(ArangoHandle database, string graph, string collection, string key,
CancellationToken cancellationToken = default)
{
var res = await SendAsync<ArangoVertexResponse<TR>>(database, HttpMethod.Get,
ApiPath(database, $"gharial/{UrlEncode(graph)}/vertex/{UrlEncode(collection)}"),
cancellationToken: cancellationToken);
return res.Vertex;
}

After researching a bit in the documentation I found that the Key parameter seems to be missing from the call to the API:

https://www.arangodb.com/docs/stable/http/gharial-vertices.html#get-a-vertex

If somebody confirms that this is the problem I can provide a fix.

Options not used in ExecuteStreamAsync

First of all, thanks for the outstanding library.
Whilst trying to execute a streaming cursor, I think I found a possible bug. Whilst the ExecuteStreamAsync method accepts an ArangoOptions instance, it is not passed to the ArangoCursor instance being instantiated.

(line 160 in ArangoQueryModule.cs) =>

   public IAsyncEnumerable<T> ExecuteStreamAsync<T>(ArangoHandle database, string query,
        IDictionary<string, object> bindVars, bool? cache = null, int? batchSize = null,
        double? ttl = null, long? memoryLimit = null,
        ArangoQueryOptions options = null,
        CancellationToken cancellationToken = default)
    {
        query = query.Trim();

        return ExecuteStreamAsync<T>(database, new ArangoCursor
        {
            Query = query,
            BindVars = bindVars,
            BatchSize = batchSize ?? Context.Configuration.BatchSize,
            TTL = ttl,
            MemoryLimit = memoryLimit,
            Cache = cache,
            Options = options  <-- should be added ?
        }, cancellationToken);
    }

Support for netstandard2.0

I needed and created support for netstandard2.0.
I'm willing to share this.
Maybe you want to create a feature branch to receive my pull request there?

Replacing ArangoViews

Hello everybody.

I've done some experiments with the "Create / Drop" View functions and that got me to wonder if there is a "Replace / Update" command implemented somewhere and if not, if that's something that's planned in the future?

Thank you!

NullPointerException when parameter silent = true

ArangoDocumentModule.CreateAsync methods causes NullPointerException when parameter silent = true:

Proposed quickfix:

Replace return res.SingleOrDefault(); with if (silent.HasValue && silent.Value) { return null; } return res.SingleOrDefault();

Support for optimistic locking using the _rev attribute

Would you please add support for optimistic locking using the _rev attribute?
Based on my tests, it can be achieved by:

  1. adding 'ignoreRevs=false' parameter to UpdateManyAsync's parameter dictionary, and
  2. extending the 'Revision' field of the documents with attibute.

Possible solution for (1):

diff --git a/Core.Arango/Modules/Internal/ArangoDocumentModule.cs b/Core.Arango/Modules/Internal/ArangoDocumentModule.cs
--- a/Core.Arango/Modules/Internal/ArangoDocumentModule.cs	(date 1617716910120)
+++ b/Core.Arango/Modules/Internal/ArangoDocumentModule.cs	(date 1617716910120)
@@ -187,6 +187,8 @@
         {
             var parameter = new Dictionary<string, string>();
 
+            parameter.Add("ignoreRevs", "false");
+            
             if (waitForSync.HasValue)
                 parameter.Add("waitForSync", waitForSync.Value.ToString().ToLowerInvariant());


Possible solution for (2):

public abstract class BaseEntity : IBaseEntity {
  ...
  [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
  public string Revision { get; set; }
  ...
}

ArangoCollectionModule Fix and Enhancements

  • DropCollectionAsync: Line 81 - Remove closing parenthesis prefixing url encoded collection name
    From: ApiPath(database, $"collection/){UrlEncode(collection)}")
    To: ApiPath(database, $"collection/{UrlEncode(collection)}")

  • Perhaps rename method to DropAsync to match naming conventions (same for IArangoCollectionModule)

  • Add 2 additional support methods and expose through IArangoCollectionModule

        public async Task<bool> ExistAsync(ArangoHandle database, string collection,
            CancellationToken cancellationToken = default)
        {
            var collections = await ListAsync(database, cancellationToken);

            return collections.Contains(collection);
        }

        public async Task<ArangoCollection> GetAsync(ArangoHandle database, string collection,
            CancellationToken cancellationToken = default)
        {
            return await SendAsync<ArangoCollection>(
                HttpMethod.Get, 
                ApiPath(database, $"collection/{UrlEncode(collection)}"),
                null, database.Transaction, cancellationToken: cancellationToken);
        }

How to avoid the save of a property of the model class?

Hi,

Is there any way to avoid the save of a property in a document? For example:

public class User{
  public string userId { get; set; }
  public string name { get; set; }
  public string lastName { get; set; }
  // And If I don't want to store this property. What can I do?
  public string lastName { get { return $"{name} {lastName}"; }}
}

BR.

Next NuGet Release

When is your next release going to be available as a NuGet package ?

You guys fixed my error / request last friday extremly fast and I'm greatful, but it would be nice to know when it's going to be available.

Thanks for your help, keep up the good work.

Trying to connect but I get unknown path '/_open/auth'

Following is the simplest code I can write for the default local installation.

    using System;
    using Core.Arango;

    namespace ConsoleApp1
    {
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                 var ctx = new ArangoContext("Server=http://192.168.60.130:8529;User=root;");

                var t0 = ctx.Database.ListAsync().GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }

           }
        }
    }

yet I keep getting this error:

Core.Arango.ArangoException: {"error":true,"code":404,"errorNum":404,"errorMessage":"unknown path '/_open/auth'"}
at Core.Arango.Transport.ArangoHttpTransport.SendAsync[T](HttpMethod m, String url, Object body, String transaction, Boolean throwOnError, Boolean auth, IDictionary2 headers, CancellationToken cancellationToken) at Core.Arango.Transport.ArangoHttpTransport.Authenticate(Boolean auth, CancellationToken cancellationToken) at Core.Arango.Transport.ArangoHttpTransport.SendAsync[T](HttpMethod m, String url, Object body, String transaction, Boolean throwOnError, Boolean auth, IDictionary2 headers, CancellationToken cancellationToken)
at Core.Arango.Modules.Internal.ArangoDatabaseModule.ListAsync(CancellationToken cancellationToken)
at ConsoleApp1.Program.Main(String[] args) in /projects/learning/ConsoleApp1/ConsoleApp1/Program.cs:line 15

What am I doing wrong here?

Support for Arango Load Balancing

I'm not sure if this belongs perhaps in discussions or be tagged as a feature request, but we are currently investigating the use of this library versus the official Node and Java libraries.

The main one we are looking at adding is round robin and looking inside the Node and Java drivers it supports passing in an array of endpoints and then there is some internal logic that figures out which endpoint each request should be sent to.

After thinking on it, this could potentially be handled in our code, where we create multiple contexts and target each one to a different server with our own logic used to figure out which context should handle the request and all of them using the common HttpClient that we have created and augmented with Polly.NET policies

So I suppose there are two questions I'm asking:

  • Would the above help achieve the round robin style load balancing or are there any nuances in the context I have missed
  • Would it be possible to get load balancing functionality added natively to this package

Possible documentation improvements

Hey,

I am replacing my current Arango driver (ArangoDB.Client) with yours. I realized that some of the documentation could be improved.

Trying to execute queries always returned error 401 from the database. After some try and fail and checking your test code I figured out the following:

  • Realm is .. I don't know. I must not specify this for my connection to work. At least not provide my db name here.
  • The executions parameter of type ArangoHandle (e.g. CreateAsync("test"), UpdateAsync("test"), ...) references to the used database (-> "test")

Some other documentation questions:

  • How to best handle large-ish queries (just thousands of documents, nothing larger) if I want to link those to data grids (e.g. with Blazorize) for dynamic filtering/sorting?
  • Is LINQ recommended or should one use AQL?
  • In general: can you provide options for the LINQ params? The only way to find what is supported seems to have a look at the code base documentation. And AsQueryable() is not realy self-explanatory IMHO especially on how to use it properly (when/where/why) vs. ToListAsync() (I guess ToList() processes all the data, AsQueryable does not, but how to use it properly as a return value?).
  • What's the difference between WHERE() and FILTER() (maybe this is a LINQ specific question, I am using it from time to time but I am not an expert. If it is LINQ specific question than please don't invest time in an answer)?
  • List options for the serializers and specify that System.Text.Json Pascal Case is the default (you write it somewhere, but it's not easy to see, found it here: https://github.com/coronabytes/dotnet-arangodb/blob/master/Core.Arango.Tests/Core/TestBase.cs
  • Is it possible to specify a default database on the ArangoContext (didn't find anything to ConnectionString at the Arango documentation homepage)?
  • Why document JsonIgnore and ArangoIgnore so prominently? Isn't ArangoIgnore a ... real special case - especially because it only works with Newtonsoft.Json?
  • Supported data types? My previous driver didn't support DateTime (well or Arango, however you want to look at it). It would be good to now which data types can be used beforehand .
  • Is there a way to specify how a enum value is serialized (as Int or String)?
  • You say hot backup is only available for enterprise system. Any recommendations on how to backup / restore data in general? Possible to use Arango.Backup but taking DB offline beforehand?

Anyway that's everything I managed to collect on the top of my hat after using your library for roughly 3 hours. Thanks for your great work!

Regards,

Andy

CreateAsync always creates collection with Document type

Even I pass ArangoCollectionType.Edge in DB will be created Document collection.
I have updated version to 3.11.2 and error still exists.

await _arangoContext.Collection.CreateAsync(_arangoDBName, new ArangoCollection
{
    Name = collectionName,
    Type = ArangoCollectionType.Edge,
    KeyOptions = new ArangoKeyOptions
    {
        Type = ArangoKeyType.Traditional,
        AllowUserKeys = allowUserKeys
    }
});

Memory usage ExecuteStreamAsync

I noticed the ExecuteStreamAsync method retains the firstResult for the entire duration of the enumerator. This results in a potentially large chunk of memory being held unnecessarily.

I have changed the code - for my purposes - to this:

public async IAsyncEnumerable<T> ExecuteStreamAsync<T>(ArangoHandle database, ArangoCursor cursor,
           [EnumeratorCancellation] CancellationToken cancellationToken = default)
       {
           QueryResponse<T> response = null;
           string responseId = null;

           while (response == null || response.HasMore)
           {
               if (response == null)
               {
                   response = await SendAsync<QueryResponse<T>>(database, HttpMethod.Post, ApiPath(database, "cursor"), cursor, 
                                                                cancellationToken: cancellationToken).ConfigureAwait(false);
                   responseId = response.Id;
                   Context.Configuration.QueryProfile?.Invoke(cursor.Query, cursor.BindVars, response.Extra.Statistic);
               }
               else
               {
                   response = await SendAsync<QueryResponse<T>>(database, HttpMethod.Post, ApiPath(database, $"/cursor/{responseId}"), 
                                                                cancellationToken: cancellationToken).ConfigureAwait(false);
               }

               if (response.Result?.Any() == true)
                   foreach (var result in response.Result)
                       yield return result;
               // Force GC? 
               response.Result = null;
           }
       }

ExecuteAsync FormattableString causing an unexpected answer

Hello, i have been trying to make a generic get method for an easier generic implementation (with dynamic filters)

i am basically trying to build a query following this model

FormattableString limits = $"";
  if (pagingAndFilter.paging != null && pagingAndFilter.paging.numberOfElements != 0) 
    limits = $"limit {pagingAndFilter.paging.startPoint}, {pagingAndFilter.paging.numberOfElements}";

FormattableString filters = $"";
  if (pagingAndFilter.filters != null && pagingAndFilter.filters != "")
    filters = $"FILTER {pagingAndFilter.filters}";
    
FormattableString query = $" FOR c IN {collectionName} {filters} {limits} RETURN c";

For the situation where i will send the following arguments

{
  "paging": {
    "startPoint": 0,
    "numberOfElements": 10
  },
  "filters": "c.name == 'filteringName'"
}

The fiilters aren't being applied even if the value of query is "FOR c IN Product filter c.name == 'filteringName' limit 0, 10 RETURN c" which is a correctly formatted query

I was also thinking about sending the Filtering and Limit part as a static string, but it raised a lot of other issues, having a method to execute AQL from a simple and plain string could solve a lot of issues and offer a last ressort alternative

Edit : When trying to flatten the Filter part (@p2) with Invariant i get the following error

AQL: syntax error, unexpected bind parameter near '@P2 limit @P3, @P4 RETURN c' at position 1:14 (while parsing)

How to create a persistent index

Hi,

¿What is the equivalent or how to create a persistent index?

The Index types are:

Edge
FullText
Geo
Hash
Primary
Skiplist
Ttl

BR

Database.DropAsync() Does Not Work!!!

In my asp.net core project, after creating a context with the IArangoContext interface, I'm able to successfully create a database with the following code:

_arango.Database.CreateAsync("dbname") - _arango is the IArangoContext context used to create the database.

However, when I attempt to drop the created database (with DropAsync), nothing happens. Here's a sample code I wrote to attempt a drop:

await _arango.Database.DropAsync("dbname");

In fact, when I use the following code:

bool dbExists = _arango.Database.ExistAsync() to check if the database I created exists before attempting to drop, it returns a "false"; meaning that the database doesn't exist. However, when I fire-up arangosh and issue the command:

_db.databases(), it fetches all the databases, including the one the above C# code claimed is non-existent.

Finally, I'm able to drop the database via arangosh using the command _db.dropDatabase("dbname") and it successfully drops the database. But the _arango.Database.DropAsync("dbname"); code does nothing to the database; neither does the bool dbExists = _arango.Database.ExistAsync() code work as expected.

Is this a known-issue? Or am I missing something?

Analyzer and view

Hello i'm having this exception when trying to create an analyzer

Failure initializing an arangosearch analyzer instance for name 'B2BExport-CoreData::B2BNgramAnalyzer' type 'ngram'. 
Properties '{
  "locale" : "fr.utf-8",
  "case" : "lower",
  "stopwords" : [
    "le",
    "la",
    "les",
    "de",
    "des",
    "du"
  ],
  "accent" : false,
  "stemming" : false,
  "edgeNgram" : {
    "min" : 3,
    "max" : 26,
    "preserveOriginal" : true
  }
}' was rejected by analyzer. Please check documentation for corresponding analyzer type.

here is my code

if ((await arangoGlobalContext.Analyzer.ListAsync(StaticDBIdentifiers.DBName)).Where(o => o.Name == "B2BExport-CoreData::" + StaticDBIdentifiers.NgramAnalyzer).ToList().Count() <= 0)
                try
                {
                    await arangoGlobalContext.Analyzer.CreateAsync(StaticDBIdentifiers.DBName, new ArangoAnalyzer
                    {
                        Name = StaticDBIdentifiers.NgramAnalyzer,
                        Features = new List<string> { "frequency", "norm", "position" },
                        Type = ArangoAnalyzerType.Ngram,
                        Properties = new ArangoAnalyzerProperties
                        {
                            EdgeNgram = new ArangoEdgeNgram{
                                Min= 3,
                                Max= 26,
                                PreserveOriginal= true
                            },
                            Locale = "fr.utf-8",
                            Case = ArangoAnalyzerCase.Lower,
                            Accent = false,
                            Stopwords = new List<string>() { "le", "la", "les", "de", "des", "du" },
                            Stemming = false
                        }
                        
                    });
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex);
                }

Creating with arangosh is done like

a = require("@arangodb/analyzers")
a.save('ngramAnalyzer','text', {edgeNgram: { min: 3, max: 26, preserveOriginal: true }, locale: 'fr.utf-8', case: 'lower', stopwords: ['le', 'la', 'les', 'de', 'des', 'du'], accent: false, stemming: false }, ["frequency", "norm", "position"])

LINQ filtering methods

Hello,

I'm having some issues with the LINQ implementation, specifically the .Where() method. I'm trying to filter some data using methods like:

.Where(x => x.Contains("string"))
or
.Where(x => x.Id.Equals(id))

Here's an example:

var q = await arango.Query<AnotherTestPerson>("TestDB").Where(x => x.Fullname.Contains("D")).FirstOrDefaultAsync();

Is there any possibility for getting support for these methods in the future? For now I have to convert the arango IQueryable to a list, then cast it again as a IQuearyable to access those methods.

Usage of returnNew parameter in Document.CreateAsync

As the issue name stands, what is the usecase or the expected syntax for using returnNew parameter in a query like this:

var result = await _context.Document.CreateAsync(_options.CurrentValue.DatabaseName, _options.CurrentValue.CollectionNames.Lookaround, lookaround, returnNew: true);

The return type is ArangoVoid, does this imply some sort of possiblity to typecast it to an object or parsing the json?

Appreciate any info.

ArangoDocumentModule Enhancement - Add optional throwOnError parameter when getting document (default to true)

  • ArangoDocumentModule (default to true to maintain backward compatibility)
        public async Task<T> GetAsync<T>(ArangoHandle database, string collection, string key,
            CancellationToken cancellationToken = default, bool throwOnError = true) where T : class
        {
            return await SendAsync<T>(HttpMethod.Get, ApiPath(database, $"document/{UrlEncode(collection)}/{key}"),
                null, database.Transaction, throwOnError: throwOnError, cancellationToken: cancellationToken);
        }

  • Corresponding change in IArangoDocumentModule
        Task<T> GetAsync<T>(ArangoHandle database,
            string collection,
            string key,
            CancellationToken cancellationToken = default,
            bool throwOnError = true) where T : class;

ArangoIndex Json Deserialization with Inverted Index and Persistant Index

Hello, i have been trying to create/init a collection with a couple of indexes.
When i am trying to read all existing indexes of a collection i am getting a System.Text.Json.JsonException with the Message "The JSON value could not be converted to System.String. Path: $.indexes[1].fields[0]."
After some research, I found out that there is a difference between the model of the persistent index and the model of the inverted index.

The 'fields' property is a string array in the persistent index and an object array in the inverted index. When querying, however, it tries to convert both to an IList<string>

Secure ArangoHttpTransport request

Is there a way currently supported to setup ArangoHttpTransport to send secure requests to arangodb server? I could implement my own IArangoHttpTransport but would be nice if HttpClient was injected via the constructor instead of instantiated internally to this class and registering either to pass in a client configured with proper certificates or maybe register the ArangoHttpTransport as a typed/named http client

Allow Realm or custom context in DataProtection

I'd like to use a different connection string for the SeriLog and DataProtection than for the rest of my application. Right now, DataProtection currently gets the ArangoContext based on injection which means I have to subclass ArangoContext to use in the rest of my application.

It would be nice if:

dataProtection.PersistKeysToArangoDB(database: "dataprotection", collection: "keys");

took a an optional realm, connection string, or instance.

ExecuteAsync failing with: expecting PUT /_api/cursor/<cursor-id>

Hi,

I'm normally a user of Foxx services, but as a C# dev, so wanted to try your library and see how I get on.

Referenced the open source a few times to work out syntaxes, but think I'm there now to being able to achieve most queries.

Firstly, I wanted to retrieve a list of strings (id's) on ExecuteAsyn but since T must be parameterless constructor type, was unable. Fortunatlely, this mapping was for document _id's so I cast to ArangoHandle.

But...whilst the ExecuteAsync works fine when limiting the query, it seems to error when the cursor is required with

code: 400
error: true, 
errorMessage: expecting PUT /_api/cursor/<cursor-id>

The query generates 200k Ids which I plan to cache in the webserver on startup for fast random lookups of documents.

Can you advise on what I can do to debug this? It's very simple query

ArangoDb.Query.ExecuteAsync<ArangoHandle>("myDb", $"FOR doc IN coll FILTER doc.myAttr != null RETURN doc._id");
Thanks

Arango Search view, setting IncludeAllFields

i have created a view using this piece of code

 if ((await _arango.View.ListAsync(StaticDBIdentifiers.DBName)).Where(o => o.Name == StaticDBIdentifiers.ViewProductWithSort).ToList().Count() <= 0)
                    await _arango.View.CreateAsync(StaticDBIdentifiers.DBName, new ArangoView
                    {
                        Name = StaticDBIdentifiers.ViewProductWithSort,
                        Links = new Dictionary<string, ArangoLinkProperty>
                        {
                            [StaticDBIdentifiers.Vertexes.Product] = new ArangoLinkProperty
                            {
                                Fields = new Dictionary<string, ArangoLinkProperty>
                                {
                                    ["name"] = new ArangoLinkProperty
                                    {
                                        Analyzers = new List<string>
                                        {
                                            StaticDBIdentifiers.AnalyzerNgram,
                                            StaticDBIdentifiers.AnalyzerTokenizer
                                        },
                                        IncludeAllFields = true
                                    },
                                    ["description"] = new ArangoLinkProperty
                                    {
                                        Analyzers = new List<string>
                                        {
                                            StaticDBIdentifiers.AnalyzerNgram,
                                            StaticDBIdentifiers.AnalyzerTokenizer
                                        }
                                    },
                                }
                            }
                        },
                        PrimarySort = new List<ArangoSort>
                        {
                            new ArangoSort
                            {
                                Field = "creationDate",
                                Direction = ArangoSortDirection.Desc,
                            }
                        }
                    });

once executed, i get this

{
  "writebufferSizeMax": 33554432,
  "id": "6513316",
  "storedValues": [],
  "name": "ViewPartnerWithSort",
  "type": "arangosearch",
  "consolidationPolicy": {
    "type": "tier",
    "segmentsBytesFloor": 2097152,
    "segmentsBytesMax": 5368709120,
    "segmentsMax": 10,
    "segmentsMin": 1,
    "minScore": 0
  },
  "writebufferActive": 0,
  "links": {
    "Partner": {
      "analyzers": [
        "identity"
      ],
      "fields": {
        "name": {
          "analyzers": [
            "BBMAnalyzerNgram",
            "BBMAnalyzerTokenizer"
          ]
        },
        "description": {
          "analyzers": [
            "BBMAnalyzerNgram",
            "BBMAnalyzerTokenizer"
          ]
        }
      },
      "includeAllFields": false,
      "storeValues": "none",
      "trackListPositions": false
    }
  },
  "commitIntervalMsec": 1000,
  "consolidationIntervalMsec": 1000,
  "globallyUniqueId": "h396FF17F2DDD/6513316",
  "cleanupIntervalStep": 2,
  "primarySort": [
    {
      "field": "creationDate",
      "asc": false
    }
  ],
  "primarySortCompression": "lz4",
  "writebufferIdle": 64
}

i'm either using it the wrong way, or there is something wrong with the setter

No result in case of dropping nonexistent database

ArangoDatabaseModule.DropAsync method doesn't give any feedback in case of dropping a nonexistent database:

public async Task DropAsync(ArangoHandle name, CancellationToken cancellationToken = default (CancellationToken)) { ArangoDatabaseModule arangoDatabaseModule = this; ArangoVoid arangoVoid = await arangoDatabaseModule.SendAsync<ArangoVoid>(name, HttpMethod.Delete, arangoDatabaseModule.ApiPath((ArangoHandle) "_system", "database/" + arangoDatabaseModule.RealmPrefix((string) name)), throwOnError: false, cancellationToken: cancellationToken); }

LINQ Query Document Delete

Hi, I just wanted to ask if deleting a document with LINQ is possible. I didn't find anything in the documentation or the tests.

Something like: .Where(t => t.Key== "").DeleteAsync()

Thanks for your help!

Document creation with db created index

Hello, i wanted to try using padded keys, and relying on the database to generate the key.
but when i try to create a document with no key i get "arangodb illegal document key".

what's the recommended way to do so.

Question about user friendliness of GetManyAsync

GetManyAsync requires to insert an enumerable of objects with entities with the Keys we want to access. (I looked at how the unit tests are written)

Why not have a function of GetManyAsync with parameter as IEnumerable where we would not need dummy entity objects and directly pass keys?

Is there a reason on how its passed to ArangoDB on the inside?

The main thing with this is I can just validate a json array with keys and pass that to the query, but right now need to repackage them into "entity" objects.

.Query<> seems to ignore Core.Arango.Linq.Attributes.CollectionProperty Collection name

When querying a simple list:
await _dbContext.Context("gt").Query<GT_Common.DataContracts.ArangoDatabase.Tenant>("env").ToListAsync();

It seems to always search for content in the collection "Tenant". But on the model we have this property present:

 [Core.Arango.Linq.Attributes.CollectionProperty(CollectionName = "tenants", Naming = Core.Arango.Linq.Interface.NamingConvention.UnChanged)]
    public class Tenant
    {

{"{\"code\":404,\"error\":true,\"errorMessage\":\"AQL: collection or view not found: Tenant (while parsing)\",\"errorNum\":1203}"}

Is this a bug or a missuse of the property ?

Null Pointer when updating a DB Entry with LINQ

I have another problem with my LINQ queries. I'm not sure what it is, probably a missuse on my side.

Can somebody explain to me how to update only certain values on a document ?

 var result = await _dbContext.QueryLinq<Tenant>("gt", "env").Where(t => t.Key == key).Update(x => new
            {
                Name = x.Name,
                ShortForm = x.ShortForm,
                Disabled = x.Disabled,
                PrimaryColor = primaryColor,
                SecondaryColor = secondaryColor,
                BaseUrl = baseUrl,
                AvailableRegions = availableRegions.ToList(),
                AvailableFeatures = availableFeatures.ToList(),
                Logo = logo.Length > 0 ? logo : x.Logo,
                Key = key
            }, x => x.Key).ToListAsync();

As soon as I try to access "x", I get a NULLPOINTER exception, and when I dont set those values, they will all get overwritten with NULL.

Thanks in advance!

cancellationToken on LINQ Query?

Hello,

I've been using your driver for some days. I'm not that proficient on working with other people's code, so I might have overlooked some things around.

I have a case where I want to get a list of all of the entities (or documents) inside a collection, so I'm using LINQ Queries for that, as I find it quite easy to use it. The problem is that I cant seem to find where I can pass the cancellation token that you ussually find inside other Async methods.

For example

var q = arango.Query<AnotherTestPerson>("TestDB").FirstAsync();

Any help with that would be appreciated.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.