DataStax C# Driver

repository·master·Indexed 20 days ago

https://github.com/datastax/csharp-driver

A feature-rich client library for Apache Cassandra (2.0+) and DataStax Enterprise (DSE 4.8+). It supports synchronous and asynchronous APIs, built-in mapping for UDTs and LINQ, and high-performance optimizations like connection pooling and request pipelining. Key features include automatic failover, IAddressTranslator for custom node mapping, support for DSE Unified Authentication (Proxy Login/Execute), and secure connection bundles for DataStax Astra.

Tokens
49K
Snippets
139
Records
205
Agent score
71%

What's inside datastax-csharp-driver

  1. Overview of DataStax C# Driver features

    master

    The DataStax C# Driver is a highly tunable client library for Apache Cassandra (2.0+) and DataStax Enterprise (4.8+). It utilizes Cassandra's binary protocol and CQL v3 to provide high-performance connectivity.

    Key capabilities include:

    • Core Connectivity: Address resolution, automatic failover, connection pooling, and connection heartbeats.
    • Data Abstractions: Support for CQL data types to C# types, Geospatial types, Graph support, User-Defined Types (UDTs), and User-Defined Functions (UDFs).
    • Query Management: Parametrized queries, query timestamps, query warnings, and execution profiles.
    • Performance & Reliability: Speculative executions, tuning policies, and request tracking.
    • Observability: Metrics (including App.Metrics provider) and OpenTelemetry support.
    • Security: Authentication and TLS/SSL support.
  2. Explore C# Driver usage samples

    master

    The repository provides several specialized code samples for common implementation patterns:

    Core Patterns

    • Concurrent execution: Managing parallel requests when inserting multiple rows.
    • Mapper: Using the Mapper component across multiple keyspaces using a single Session.

    Security and Connectivity

    • DataStax Astra: Getting started with Astra DB using the Secure Connection Bundle.
    • TLS/SSL:
      • Server-only authentication (loading certificates manually or via the Windows Certificate Store).
      • Two-way authentication (Client and Server auth) using manual certificate loading or the Windows Certificate Store.
    • Column Encryption: Implementing the Column Encryption feature.

    Observability

    • Metrics: Integrating with Graphite and Grafana using App.Metrics.
    • OpenTelemetry:
      • Using the OpenTelemetry Console Exporter.
      • Implementing Distributed Tracing across Web APIs and clients.
  3. Map Cassandra User Defined Types (UDT) to C# Classes

    master

    You can map Cassandra UDTs to application entities using session.UserDefinedTypes.Define(). You can map properties automatically by name or manually using .Map() to specify the relationship between class properties and CQL field names. Once defined, you can retrieve the UDT directly from a Row using GetValue<T>().

    // Map the properties by name automatically
    session.UserDefinedTypes.Define(
      UdtMap.For<Address>()
    );
    
    // Or you can define the properties manually
    session.UserDefinedTypes.Define(
      UdtMap.For<Address>()
        .Map(a => a.Street, "street")
        .Map(a => a.City, "city")
        .Map(a => a.ZipCode, "zip_code")
        .Map(a => a.Phones, "phones")
    );
    
    // Usage after mapping
    var rs = session.Execute("SELECT id, name, address FROM users where id = x");
    var row = rs.First();
    var userAddress = row.GetValue<Address>("address");
  4. Map nested User-Defined Types

    master

    If a CQL UDT contains another UDT (nesting), you must define the mapping for all involved types at the session level. For example, if an Address UDT contains a list of Phone UDTs, you must register both UdtMap.For<Phone>() and UdtMap.For<Address>().

    // Define mappings for all types in the hierarchy
    session.UserDefinedTypes.Define(
       UdtMap.For<Phone>(),
       UdtMap.For<Address>()
          .Automap()
          .Map(a => a.ZipCode, "zip_code")
    );
    
    // Usage
    var userAddress = row.GetValue<Address>("address");
    var mainPhone = userAddress.Phones.First();
  5. Understand the Snappy stream format

    master

    While Snappy does not have a single universal stream format, this library uses a specific implementation with the following characteristics:

    • Block-based compression: User input is divided into blocks, and each block is compressed individually.
    • Uncompressible data handling: If a compressed block is larger than the original input, the library writes the uncompressed original instead. This significantly improves performance for data that does not compress well (e.g., JPG images).
    • Integrity checks: A checksum of the user input data is written for each block to ensure the stream has not been corrupted during transit.
    • Concatenation support: Similar to gzip, compressed Snappy files can be concatenated. The input stream ignores Snappy stream headers found in the middle of a stream, making it easy to combine files in environments like Hadoop or S3.

    For a formal definition of the stream format, refer to the SnappyOutputStream documentation.

  6. Handle Graph query results

    master

    Graph queries return a GraphResultSet, which is a sequence of GraphNode elements. You can process results in several ways:

    1. Direct Iteration: Iterate over IGraphNode objects.
    2. Type Conversion: Use .To<T>() on a single node or on the entire GraphResultSet to convert nodes to specific types like IVertex, IVEdge, or custom classes.
    3. Implicit Conversion: GraphNode supports implicit conversion to string, int, long, etc.
    4. Dynamic Access: GraphNode inherits from DynamicObject, allowing use of the dynamic keyword.
    5. ElementMap: For DSE 6.8+ (Core Engine) queries using elementMap(), use the ElementMap class for easier manipulation instead of Dictionary<IGraphNode, IGraphNode>.
    6. Properties: Use GetProperty(name) on an element to access values.
    // Convert entire result set to a specific type
    foreach (IVertex vertex in rs.To<IVertex>())
    {
        Console.WriteLine(vertex.Label);
    }
    
    // Using ElementMap for elementMap() queries (DSE 6.8+)
    foreach (ElementMap elementMap in rs.To<ElementMap>())
    {
        Console.WriteLine(elementMap.Label);
    }
    
    // Accessing properties
    var vertex = session.ExecuteGraph(new SimpleGraphStatement("g.V()")).First().To<IVertex>();
    Console.WriteLine(vertex.GetProperty("name").Value.ToString());
    
    // Implicit conversion to string
    foreach (string location in rs) 
    {
        Console.WriteLine(location);
    }
  7. When to use Frozen UDTs and Collections

    master

    In Cassandra, frozen UDTs and collections are serialized as a single cell value, whereas non-frozen versions serialize individual elements/fields as separate cells.

    Recommendation: Use frozen UDTs and collections if you are using the Mapper or Linq2Cql to perform full-object updates (e.g., Mapper.Insert<T>(T obj) or Mapper.Update<T>(T obj)).

    Benefits of Frozen types:

    • Efficiency: When updating an entire entity, frozen types are more efficient as they are treated as a single value.
    • Performance: When using non-frozen collections, Cassandra must create a tombstone to invalidate existing elements during an INSERT. Frozen collections do not require these tombstones.
  8. Use the Unified Driver (v3.13.0+)

    master

    Starting with version 3.13.0, the driver is unified. It supports all DataStax products and features (including Unified Authentication, Kerberos, geo types, and graph traversal) using a single driver for Apache Cassandra, DSE, or other DataStax products.

    Key Change: The DefaultLoadBalancingPolicy is now the default. Its behavior is identical to the previous default policy for most workloads, with some specific adjustments for DSE workloads that should not impact existing applications.

  9. Use RequestTrackingInfo and HostTrackingInfo for telemetry

    master

    When implementing IRequestTracker or working with request observers, the driver uses two primary data structures to provide context:

    1. RequestTrackingInfo: Contains a ConcurrentDictionary<string, object> Items for storing arbitrary metadata (like OpenTelemetry activities) and an IStatement Statement representing the query being executed.
    2. HostTrackingInfo: A struct containing the Host object involved in the request, used to track node-specific success or error events.
    public class RequestTrackingInfo
    {
        public ConcurrentDictionary<string, object> Items { get; }
        public IStatement Statement { get; set; }
    }
    
    public struct HostTrackingInfo 
    {
        public Host Host { get; }
    }
  10. Use Proxy Login with DSE Unified Authentication

    master

    DSE Unified Authentication (DSE 5.1+) supports Proxy Login, which allows you to authenticate with one set of credentials but use the authorization/permissions of a different user.

    Requirements:

    1. The authenticated user must be granted permission to proxy the target user via CQL: GRANT PROXY.LOGIN ON ROLE '<target_user>' TO '<authenticated_user>'
    2. Use DsePlainTextAuthProvider with the third constructor argument representing the target user.

    When using Proxy Login, all requests executed through the resulting session will be authorized as the target user.

    using Cassandra.DataStax.Auth;
    
    // Grant permission in CQL first:
    // GRANT PROXY.LOGIN ON ROLE 'alice' TO 'ben'
    
    // Authenticate as 'ben' but act as 'alice'
    var authProvider = new DsePlainTextAuthProvider("ben", "ben", "alice");
    var cluster = Cluster.builder()
        .AddContactPoint("host1")
        .WithAuthProvider(authProvider)
        .Build();
    
    var session = cluster.Connect();
    // All requests will be executed as 'alice'
    session.Execute(query);
  11. Understand the driver's API change and versioning policy

    master

    The DataStax C# Driver follows semantic versioning. Understanding how the driver categorizes its interfaces helps you predict how updates will affect your code:

    Mockable Interfaces

    These are the main entry points for client applications (e.g., ISession, ICluster, IMapper, ICqlQueryAsyncClient). They are intended for use and mocking in tests. In minor releases, new methods may be added to these interfaces.

    Implementable Interfaces

    These allow applications to plug in custom behavior (e.g., ILoadBalancingPolicy via Builder.WithLoadBalancingPolicy, or ITimestampGenerator via Builder.WithTimestampGenerator). In minor releases, these interfaces will NOT receive new methods and existing methods will not change.

    Best Practices for Wrappers

    If you need to wrap driver functionality (e.g., for tracing), use composition instead of inheritance. Instead of implementing a driver interface in your wrapper, create your own interface and wrap the driver instance.