Stardust.Paradox.Data.Linq 1.0.0-rc3

This is a prerelease version of Stardust.Paradox.Data.Linq.
dotnet add package Stardust.Paradox.Data.Linq --version 1.0.0-rc3
                    
NuGet\Install-Package Stardust.Paradox.Data.Linq -Version 1.0.0-rc3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Stardust.Paradox.Data.Linq" Version="1.0.0-rc3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Stardust.Paradox.Data.Linq" Version="1.0.0-rc3" />
                    
Directory.Packages.props
<PackageReference Include="Stardust.Paradox.Data.Linq" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Stardust.Paradox.Data.Linq --version 1.0.0-rc3
                    
#r "nuget: Stardust.Paradox.Data.Linq, 1.0.0-rc3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Stardust.Paradox.Data.Linq@1.0.0-rc3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Stardust.Paradox.Data.Linq&version=1.0.0-rc3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Stardust.Paradox.Data.Linq&version=1.0.0-rc3&prerelease
                    
Install as a Cake Tool

Stardust.Paradox.Data.Linq

NuGet Version NuGet Downloads License

Powerful LINQ to Gremlin query provider for Stardust.Paradox graph database framework. Write natural C# LINQ queries that are automatically translated to efficient Gremlin traversals for CosmosDB, TinkerPop, and other Gremlin-compatible graph databases.

? Features

  • ?? Full LINQ Support: Where, Select, OrderBy, GroupBy, Join, and more
  • ?? Type-Safe Queries: Strong typing with IntelliSense support
  • ?? Graph Traversals: Native support for graph operations (Out, In, OutE, InE, etc.)
  • ? Query Optimization: Automatic translation to efficient Gremlin queries
  • ?? Lambda Expressions: Type-safe lambda expressions for graph traversals
  • ?? Aggregations: Sum, Count, Average, Min, Max with full LINQ syntax
  • ?? Pattern Matching: Match and select operations for complex graph patterns
  • ?? Set Operations: Union, Intersect, Except with proper deduplication
  • ?? Branching: Union and repeat operations with type preservation
  • ?? Async Support: Full async/await pattern support
  • ?? Performance: Optimized query generation for minimal server roundtrips

?? Installation

Package Manager

Install-Package Stardust.Paradox.Data.Linq

.NET CLI

dotnet add package Stardust.Paradox.Data.Linq

PackageReference

<PackageReference Include="Stardust.Paradox.Data.Linq" Version="1.0.0" />

?? Quick Start

Basic LINQ Queries

using Stardust.Paradox.Data;
using Stardust.Paradox.Data.Linq;

public class MyGraphContext : GraphContextBase
{
    public IGraphSet<IPerson> People => GraphSet<IPerson>();
    public IGraphSet<ICompany> Companies => GraphSet<ICompany>();
    
    // Constructor and configuration...
}

// Basic where clause
var activePeople = context.People
    .AsQueryable()
 .Where(p => p.IsActive)
    .ToList();

// Multiple conditions
var seniorDevelopers = context.People
    .AsQueryable()
    .Where(p => p.Age > 30 && p.Role == "Developer")
    .ToList();

// String operations
var searchResults = context.People
    .AsQueryable()
    .Where(p => p.Email.Contains("@example.com"))
    .ToList();

// Ordering
var sortedPeople = context.People
.AsQueryable()
    .OrderBy(p => p.LastName)
    .ThenBy(p => p.FirstName)
    .ToList();

Projections and Selections

// Select single property
var names = context.People
    .AsQueryable()
    .Select(p => p.Name)
    .ToList();

// Select anonymous type
var personInfo = context.People
    .AsQueryable()
    .Where(p => p.Age > 25)
    .Select(p => new { p.Name, p.Age, p.Email })
    .ToList();

// Complex projection
var detailedInfo = context.People
    .AsQueryable()
    .Where(p => p.IsActive)
    .OrderBy(p => p.Score)
    .Select(p => new 
    {
        FullName = p.Name,
   Contact = p.Email,
        Demographics = new { p.Age, p.City },
        IsHighScorer = p.Score > 90
    })
    .ToList();

Aggregations

// Count
var totalPeople = context.People
    .AsQueryable()
.Count();

var activePeopleCount = context.People
  .AsQueryable()
    .Count(p => p.IsActive);

// Average, Sum, Min, Max
var averageAge = context.People
    .AsQueryable()
 .Average(p => p.Age);

var totalScore = context.People
    .AsQueryable()
    .Sum(p => p.Score);

var youngestAge = context.People
    .AsQueryable()
  .Min(p => p.Age);

var highestScore = context.People
    .AsQueryable()
    .Max(p => p.Score);

// Any and All
var hasActiveUsers = context.People
    .AsQueryable()
    .Any(p => p.IsActive);

var allAdults = context.People
    .AsQueryable()
    .All(p => p.Age >= 18);

GroupBy Operations

// Simple grouping
var peopleByCity = context.People
    .AsQueryable()
  .GroupBy(p => p.City)
    .ToList();

// Grouping with aggregation
var cityStatistics = context.People
    .AsQueryable()
    .GroupBy(p => p.City)
.Select(g => new 
    {
        City = g.Key,
 Count = g.Count(),
        AverageAge = g.Average(p => p.Age),
        TotalScore = g.Sum(p => p.Score)
    })
    .ToList();

// Multiple grouping keys
var roleAndDepartmentGroups = context.People
    .AsQueryable()
    .GroupBy(p => new { p.Role, p.Department })
    .Select(g => new 
{
   g.Key.Role,
      g.Key.Department,
        Count = g.Count()
    })
    .ToList();

Paging and Limiting

// Take first N results
var topTen = context.People
    .AsQueryable()
    .OrderByDescending(p => p.Score)
    .Take(10)
    .ToList();

// Skip and Take (pagination)
var page2 = context.People
    .AsQueryable()
    .OrderBy(p => p.Name)
    .Skip(20)
    .Take(10)
    .ToList();

// First, Single, FirstOrDefault, SingleOrDefault
var firstPerson = context.People
    .AsQueryable()
    .First();

var specificPerson = context.People
    .AsQueryable()
    .Single(p => p.Email == "john@example.com");

var maybeExists = context.People
  .AsQueryable()
    .FirstOrDefault(p => p.Name == "Unknown");

?? Graph Traversal Operations

Type-Safe Graph Navigation

using Stardust.Paradox.Data.Linq;

// Define your graph entities
[VertexLabel("person")]
public interface IPerson : IVertex
{
 string Name { get; set; }
    int Age { get; set; }
    
    // Navigation properties
    IEdgeCollection<IPerson> Friends { get; }
  IEdgeCollection<ICompany> Employers { get; }
}

[VertexLabel("company")]
public interface ICompany : IVertex
{
    string Name { get; set; }
    IEdgeCollection<IPerson> Employees { get; }
}

Out, In, OutE, InE Operations

// Navigate outgoing edges
var friends = context.People
    .AsQueryable()
  .Where(p => p.Name == "Alice")
    .SelectMany(p => p.Friends.ToVertices())
    .ToList();

// Find friends of friends
var extendedNetwork = context.People
    .AsQueryable()
    .Where(p => p.Name == "Alice")
    .SelectMany(p => p.Friends.ToVertices())
    .SelectMany(friend => friend.Friends.ToVertices())
    .Distinct()
    .ToList();

// Incoming relationships
var popularPeople = context.People
    .AsQueryable()
  .Where(p => p.Friends.Count() > 10)
    .ToList();

// Edge filtering
var recentFriendships = context.People
    .AsQueryable()
    .Where(p => p.Name == "Alice")
    .SelectMany(p => p.Friends.Where(e => e.CreatedDate > DateTime.UtcNow.AddYears(-1)))
    .ToList();

Lambda Expression Support for Graph Traversals

using Stardust.Paradox.Data.Linq;

// Create a typed traversal
var traversal = new GraphTraversal<IPerson>();

// Has with lambda expressions
var result = traversal
    .Has(p => p.Age > 25)
    .Has(p => p.IsActive == true)
    .Has(p => p.Name != "Bob");

// Navigate using lambda expressions
var friends = traversal
    .Out(p => p.Friends)
    .Has(p => p.Age >= 21);

// Edge traversal with lambda
var employmentEdges = traversal
    .OutE(p => p.Employers)
    .Has("startDate", P.Gt(new DateTime(2020, 1, 1)));

// Complex chain with multiple filters
var complexQuery = traversal
    .Has(p => p.Age > 50)
    .Has(p => p.IsActive == true)
  .Out(p => p.Companies)
    .Dedup();

// Generate Gremlin query
string gremlinQuery = complexQuery.ToGremlinQuery();
// Result: "has('age', P.gt(50)).has('isActive', true).out('companies').dedup()"

Gremlin Predicates

using Stardust.Paradox.Data.Linq;

// P class for predicates (Gremlin-style)
var query = traversal
    .Has("age", P.Gt(25))              // Greater than
    .Has("age", P.Gte(18))             // Greater than or equal
    .Has("age", P.Lt(65))              // Less than
    .Has("age", P.Lte(30))     // Less than or equal
    .Has("age", P.Between(18, 65))    // Between (inclusive)
    .Has("age", P.Inside(18, 65))     // Inside (exclusive)
    .Has("age", P.Outside(18, 65))    // Outside
    .Has("name", P.Neq("Unknown"))    // Not equal
    .Has("city", P.Within("Seattle", "Portland", "Boston"))  // In list
    .Has("status", P.Without("inactive", "deleted"))        // Not in list
    .Has("email", P.Containing("@example.com"))   // String contains
    .Has("name", P.StartingWith("A"))  // String starts with
    .Has("name", P.EndingWith("son"))             // String ends with
    .Has("score", P.Not(P.Lt(50)));       // Negation

Repeat and Loop Operations

// Repeat traversal
var hierarchyTraversal = new GraphTraversal<IPerson>()
 .Repeat(t => t.Out("manages"))
    .Times(3)  // Repeat exactly 3 times
    .Emit();   // Emit intermediate results

// Until condition
var pathToRoot = new GraphTraversal<IPerson>()
    .Repeat(t => t.In("reportsTo"))
    .Until(t => t.Has("isRoot", true));

// Emit with filter
var organizationTree = new GraphTraversal<IPerson>()
    .Repeat(t => t.Out("manages"))
    .Emit(t => t.Has("level", P.Lte(5)))
    .Times(10);

?? Advanced LINQ Operations

Set Operations

// Union - Combine two queries
var techAndManagers = context.People
    .AsQueryable()
    .Where(p => p.Department == "Technology")
    .Union(
     context.People.AsQueryable().Where(p => p.Role == "Manager")
    )
    .ToList();

// Intersect - Find common elements
var seniorManagers = context.People
    .AsQueryable()
    .Where(p => p.Age > 40)
    .Intersect(
context.People.AsQueryable().Where(p => p.Role == "Manager")
    )
    .ToList();

// Except - Find difference
var nonManagers = context.People
    .AsQueryable()
    .Except(
        context.People.AsQueryable().Where(p => p.Role == "Manager")
  )
    .ToList();

// Concat - Simple concatenation (with duplicates)
var allEmployees = context.People
    .AsQueryable()
    .Where(p => p.Department == "Sales")
    .Concat(
     context.People.AsQueryable().Where(p => p.Department == "Marketing")
    )
    .ToList();

Pattern Matching with Match

// Match complex graph patterns
var matchResult = context.People
    .AsQueryable()
    .Match(
 p => p.Out("knows").As("friend"),
        p => p.As("friend").Out("worksAt").As("company"),
  p => p.As("company").In("worksAt").As("colleague")
    )
    .Select("friend", "company", "colleague")
    .ToList();

Branch and Choose Operations

// Conditional branching
var categorizedPeople = new GraphTraversal<IPerson>()
    .Choose(
      p => p.Has("age", P.Gt(30)),
        seniorPath => seniorPath.Values("name", "email"),
        juniorPath => juniorPath.Values("name")
    );

// Coalesce - First matching path
var contactInfo = new GraphTraversal<IPerson>()
    .Coalesce(
  t => t.Values("primaryEmail"),
        t => t.Values("secondaryEmail"),
        t => t.Constant("no-email@unknown.com")
    );

Local Scoping

// Local aggregation
var localMax = new GraphTraversal<IPerson>()
    .Out("friends")
    .Local(
        t => t.Values("age").Max()
    );

// Local ordering within groups
var topFriendsByAge = new GraphTraversal<IPerson>()
    .Out("friends")
    .Local(
  t => t.Order().By("age", Order.Desc).Limit(5)
    );

Deduplication

// Simple dedup
var uniquePeople = context.People
    .AsQueryable()
    .SelectMany(p => p.Friends.ToVertices())
    .Distinct()
    .ToList();

// Dedup by property
var distinctCities = new GraphTraversal<IPerson>()
    .Dedup()
    .By("city")
    .Values("city");

?? Async Operations

// All LINQ operations support async
var activeUsersAsync = await context.People
    .AsQueryable()
    .Where(p => p.IsActive)
    .ToListAsync();

var countAsync = await context.People
 .AsQueryable()
    .CountAsync();

var firstAsync = await context.People
    .AsQueryable()
    .FirstOrDefaultAsync(p => p.Email == "test@example.com");

// Async enumeration
await foreach (var person in context.People
    .AsQueryable()
    .Where(p => p.Age > 25)
    .AsAsyncEnumerable())
{
    Console.WriteLine(person.Name);
}

?? Type Preservation and Casting

// Type preservation through operations
IQueryable<IPerson> query = context.People.AsQueryable();

var result = query
    .Where(p => p.Age > 25)     // Still IQueryable<IPerson>
    .OrderBy(p => p.Name)        // Still IQueryable<IPerson>
    .Take(10)        // Still IQueryable<IPerson>
    .ToList();           // List<IPerson>

// Explicit typing after projection
var projections = context.People
    .AsQueryable()
    .Select(p => new PersonDto 
    { 
   Name = p.Name, 
        Age = p.Age 
    })
    .ToList();  // List<PersonDto>

?? Query Translation Details

Supported LINQ Operators

LINQ Method Gremlin Translation Description
Where(predicate) has(property, value) Filter vertices/edges
Select(selector) project().by() or values() Project properties
OrderBy(keySelector) order().by(property) Sort ascending
OrderByDescending(keySelector) order().by(property, desc) Sort descending
ThenBy(keySelector) Additional by() modulator Secondary sort
GroupBy(keySelector) group().by() Group vertices/edges
Count() count() Count elements
Sum(selector) sum() Sum values
Average(selector) mean() Average values
Min(selector) min() Minimum value
Max(selector) max() Maximum value
Take(n) limit(n) Limit results
Skip(n) skip(n) Skip results
First() limit(1) Get first element
FirstOrDefault() limit(1).fold() Get first or null
Single() limit(2) + validation Get single element
Distinct() dedup() Remove duplicates
Union(other) union() Set union
Intersect(other) Custom implementation Set intersection
Except(other) Custom implementation Set difference
Concat(other) union() Concatenation
Any() hasNext() Check if any exist
All(predicate) not(has(!predicate)) Check if all match
SelectMany(selector) flatMap() or out()/in() Flatten collections

Comparison Operators

C# Operator Gremlin Predicate Example
== Value or eq() p => p.Age == 30 ? has('age', 30)
!= neq() p => p.Age != 30 ? has('age', P.neq(30))
> gt() p => p.Age > 30 ? has('age', P.gt(30))
>= gte() p => p.Age >= 30 ? has('age', P.gte(30))
< lt() p => p.Age < 30 ? has('age', P.lt(30))
<= lte() p => p.Age <= 30 ? has('age', P.lte(30))

String Operations

C# Method Gremlin Translation Example
Contains(value) containing() p => p.Email.Contains("@")
StartsWith(value) startingWith() p => p.Name.StartsWith("A")
EndsWith(value) endingWith() p => p.Name.EndsWith("son")

Boolean Operations

C# Operator Gremlin Translation Example
&& Chained has() p => p.Age > 25 && p.IsActive
\|\| or() step p => p.Age < 18 \|\| p.Age > 65
! not() step p => !p.IsActive ? not(has('isActive', true))

?? Best Practices

Query Optimization

// ? Good: Filter early
var result = context.People
    .AsQueryable()
    .Where(p => p.IsActive)           // Filter first
    .Where(p => p.Age > 25)         // Additional filter
    .OrderBy(p => p.Name)              // Then sort
    .Take(10)             // Limit results
    .ToList();

// ? Avoid: Late filtering
var badResult = context.People
    .AsQueryable()
    .OrderBy(p => p.Name)     // Sorting all records
    .ToList()        // Materializing all
    .Where(p => p.IsActive)   // Filtering in memory
    .Take(10)
    .ToList();

// ? Good: Use Count() for existence checks
var hasActive = context.People
    .AsQueryable()
    .Any(p => p.IsActive);

// ? Avoid: Materializing for count
var badCount = context.People
    .AsQueryable()
    .ToList()         // Fetches all data
    .Count(p => p.IsActive);           // Counts in memory

Async Patterns

// ? Good: Use async throughout
public async Task<List<IPerson>> GetActivePeopleAsync()
{
    return await context.People
        .AsQueryable()
        .Where(p => p.IsActive)
    .ToListAsync();
}

// ? Good: Async enumeration for large datasets
public async IAsyncEnumerable<IPerson> StreamPeopleAsync()
{
    await foreach (var person in context.People
        .AsQueryable()
        .AsAsyncEnumerable())
    {
      yield return person;
    }
}

Type Safety

// ? Good: Use strongly typed entities
var people = context.People
    .AsQueryable()
    .Where(p => p.Age > 25)  // IntelliSense and compile-time checking
  .ToList();

// ? Good: Use projection for DTOs
var dtos = context.People
.AsQueryable()
    .Select(p => new PersonDto 
    { 
   Name = p.Name,
        Age = p.Age,
Contact = p.Email
    })
    .ToList();

?? Debugging and Troubleshooting

View Generated Gremlin Queries

// For IQueryable
var queryable = context.People
    .AsQueryable()
    .Where(p => p.Age > 25)
    .OrderBy(p => p.Name);

// Cast to GraphQueryable to see the query
if (queryable is GraphQueryable<IPerson> graphQuery)
{
    string gremlinQuery = graphQuery.ToString();
    Console.WriteLine($"Generated Gremlin: {gremlinQuery}");
}

// For GraphTraversal
var traversal = new GraphTraversal<IPerson>()
    .Has(p => p.Age > 25)
    .Out(p => p.Friends);

string query = traversal.ToGremlinQuery();
Console.WriteLine($"Gremlin Query: {query}");

Testing with InMemory Provider

using Stardust.Paradox.Data.InMemory;
using Stardust.Paradox.Data.Linq;

[TestClass]
public class LinqQueryTests
{
    private TestGraphContext _context;

    [TestInitialize]
    public void Setup()
    {
        var connector = InMemoryGremlinLanguageConnector.Create();
        _context = new TestGraphContext(connector);
        
  // Add test data
        var person = _context.CreateEntity<IPerson>("test1");
        person.Name = "Test Person";
   person.Age = 30;
      _context.SaveChangesAsync().Wait();
    }

    [TestMethod]
    public void TestLinqQuery()
    {
        // Act
        var result = _context.People
        .AsQueryable()
     .Where(p => p.Age > 25)
     .ToList();

        // Assert
        Assert.AreEqual(1, result.Count);
        Assert.AreEqual("Test Person", result[0].Name);
    }
}

?? Integration with Stardust.Paradox.Data

GraphContext Integration

using Stardust.Paradox.Data;
using Stardust.Paradox.Data.Linq;

public class MyGraphContext : GraphContextBase
{
    public MyGraphContext(IGremlinLanguageConnector connector, IServiceProvider serviceProvider)
   : base(connector, serviceProvider)
  {
    }

    // Define your graph sets
    public IGraphSet<IPerson> People => GraphSet<IPerson>();
    public IGraphSet<ICompany> Companies => GraphSet<ICompany>();
    public IGraphSet<IProject> Projects => GraphSet<IProject>();

    protected override bool InitializeModel(IGraphConfiguration configuration)
    {
     configuration
            .ConfigureCollection<IPerson>()
        .Out(p => p.Friends, "knows")
     .Out(p => p.Employers, "worksAt")
 .ConfigureCollection<ICompany>()
                .In(c => c.Employees, "worksAt");

     return true;
    }
}

// Usage
var connector = /* your connector */;
var context = new MyGraphContext(connector, serviceProvider);

// Now use LINQ
var result = context.People
    .AsQueryable()
    .Where(p => p.IsActive)
    .OrderBy(p => p.Name)
    .ToList();

Combining with Direct Gremlin Queries

// Use LINQ for simple queries
var linqResult = context.People
    .AsQueryable()
  .Where(p => p.Age > 25)
    .ToList();

// Use direct Gremlin for complex graph patterns
var gremlinResult = await context.ExecuteAsync<IPerson>(g =>
    g.V().HasLabel("person")
     .Out("knows").Out("knows")  // Friends of friends
     .Dedup()
     .Limit(10));

// Combine approaches
var peopleIds = context.People
.AsQueryable()
    .Where(p => p.Department == "Engineering")
  .Select(p => p.Id)
    .ToList();

var detailedResults = await context.ExecuteAsync<IPerson>(g =>
    g.V(peopleIds.ToArray())
     .Out("knows")
     .HasLabel("person"));

?? Additional Resources

  • Stardust.Paradox.Data: Core graph database framework
  • Stardust.Paradox.Data.Annotations: Entity annotations and interfaces
  • Stardust.Paradox.Data.InMemory: In-memory graph database for testing
  • Stardust.Paradox.Data.Providers.Gremlin: Gremlin.Net provider
  • Stardust.Paradox.Data.Providers.CosmosDb: Azure CosmosDB provider

Documentation

Support

??? Architecture

Query Translation Pipeline

LINQ Expression Tree
       ?
Expression Visitor Pattern
       ?
Gremlin Query Builder
       ?
Gremlin Query String
       ?
IGremlinLanguageConnector
       ?
Graph Database (CosmosDB, TinkerPop, etc.)

Key Components

  • GremlinQueryProvider: IQueryProvider implementation for LINQ
  • GremlinQueryTranslator: Translates LINQ expressions to Gremlin
  • GraphQueryable<T>: IQueryable<T> implementation for graph entities
  • ExpressionVisitor: Visitor pattern for expression tree traversal
  • GraphTraversal<T>: Fluent API for building Gremlin traversals

?? Examples

Real-World Scenarios

Social Network Queries
// Find mutual friends
var mutualFriends = context.People
  .AsQueryable()
    .Where(p => p.Name == "Alice")
    .SelectMany(p => p.Friends.ToVertices())
    .Intersect(
        context.People
.AsQueryable()
        .Where(p => p.Name == "Bob")
        .SelectMany(p => p.Friends.ToVertices())
    )
    .ToList();

// Find people within 2 degrees of separation
var network = new GraphTraversal<IPerson>()
    .Has("name", "Alice")
    .Repeat(t => t.Out("knows"))
    .Times(2)
    .Emit()
    .Dedup();

// Most connected people
var influencers = context.People
    .AsQueryable()
    .OrderByDescending(p => p.Friends.Count())
    .Take(10)
    .ToList();
Organization Hierarchy
// Find all reports (direct and indirect)
var allReports = new GraphTraversal<IPerson>()
    .Has("name", "CEO")
    .Repeat(t => t.Out("manages"))
    .Emit()
    .Dedup();

// Find managers with most direct reports
var topManagers = context.People
    .AsQueryable()
    .Where(p => p.Role == "Manager")
    .OrderByDescending(p => p.DirectReports.Count())
    .Select(p => new 
    {
        p.Name,
        DirectReportCount = p.DirectReports.Count()
    })
    .Take(10)
    .ToList();

// Organization statistics by level
var levelStats = context.People
    .AsQueryable()
    .GroupBy(p => p.Level)
    .Select(g => new 
    {
        Level = g.Key,
        Count = g.Count(),
        AverageTenure = g.Average(p => p.YearsInRole)
    })
    .OrderBy(x => x.Level)
    .ToList();
E-Commerce Recommendations
// Find recommended products (collaborative filtering)
var recommendations = context.Products
    .AsQueryable()
    .Where(p => p.Category == targetProduct.Category)
    .SelectMany(p => p.PurchasedBy.ToVertices())
    .SelectMany(customer => customer.Purchases.ToVertices())
    .Where(p => p.Id != targetProduct.Id)
    .GroupBy(p => p.Id)
    .OrderByDescending(g => g.Count())
    .Select(g => g.First())
    .Take(5)
    .ToList();

// Customer purchase patterns
var patterns = context.Customers
    .AsQueryable()
    .Select(c => new 
    {
        CustomerId = c.Id,
        TotalPurchases = c.Purchases.Count(),
  AveragePrice = c.Purchases.Average(p => p.Price),
  Categories = c.Purchases
 .Select(p => p.Category)
            .Distinct()
            .ToList()
    })
    .ToList();

?? Performance Tips

Index Usage

// ? Good: Uses index on IsActive
var active = context.People
    .AsQueryable()
    .Where(p => p.IsActive)  // Indexed property
    .ToList();

// Ensure your graph database has appropriate indexes:
// - CosmosDB: Automatic indexing on all properties
// - TinkerPop: Configure composite indexes for frequently queried properties

Projection for Large Datasets

// ? Good: Only fetch needed properties
var lightweightData = context.People
    .AsQueryable()
    .Select(p => new { p.Id, p.Name })
    .ToList();

// ? Avoid: Fetching full entities when not needed
var heavyData = context.People
    .AsQueryable()
    .ToList();  // Loads all properties

Batching

// ? Good: Process in batches
var pageSize = 100;
var page = 0;

while (true)
{
 var batch = context.People
  .AsQueryable()
        .OrderBy(p => p.Id)
      .Skip(page * pageSize)
      .Take(pageSize)
        .ToList();

if (!batch.Any()) break;

    // Process batch
    await ProcessBatchAsync(batch);
    page++;
}

?? Version Compatibility

  • .NET Standard 2.0: Full support
  • C# 7.3+: Required for language features
  • Stardust.Paradox.Data: 2.3.0 or higher recommended
  • Gremlin.Net: Compatible with TinkerPop 3.x protocol
  • Azure CosmosDB: Gremlin API support

?? License

Copyright � Stardust 2025

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


Made with ?? by the Stardust.Paradox team

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-rc3 185 11/3/2025

Initial release of LINQ to Gremlin query provider with comprehensive LINQ support, type-safe graph traversals, lambda expressions, and async operations.