DataStax C# Driver
repository·master·Indexed 20 days ago
https://github.com/datastax/csharp-driverA 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.
What's inside datastax-csharp-driver
- This library is a pure Java port of the original C++ Snappy compression algorithm. It is designed to be extremely fast and produces a byte-for-byte exact copy of the output created by the original C++ implementation, ensuring compatibility.
Overview of DataStax C# Driver features
masterThe 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.
Explore C# Driver usage samples
masterThe 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.
Map Cassandra User Defined Types (UDT) to C# Classes
masterYou 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 aRowusingGetValue<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");Map nested User-Defined Types
masterIf a CQL UDT contains another UDT (nesting), you must define the mapping for all involved types at the session level. For example, if an
AddressUDT contains a list ofPhoneUDTs, you must register bothUdtMap.For<Phone>()andUdtMap.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();Understand the Snappy stream format
masterWhile 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
SnappyOutputStreamdocumentation.Handle Graph query results
masterGraph queries return a
GraphResultSet, which is a sequence ofGraphNodeelements. You can process results in several ways:- Direct Iteration: Iterate over
IGraphNodeobjects. - Type Conversion: Use
.To<T>()on a single node or on the entireGraphResultSetto convert nodes to specific types likeIVertex,IVEdge, or custom classes. - Implicit Conversion:
GraphNodesupports implicit conversion tostring,int,long, etc. - Dynamic Access:
GraphNodeinherits fromDynamicObject, allowing use of thedynamickeyword. - ElementMap: For DSE 6.8+ (Core Engine) queries using
elementMap(), use theElementMapclass for easier manipulation instead ofDictionary<IGraphNode, IGraphNode>. - 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); }- Direct Iteration: Iterate over
When to use Frozen UDTs and Collections
masterIn 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
MapperorLinq2Cqlto perform full-object updates (e.g.,Mapper.Insert<T>(T obj)orMapper.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.
Use the Unified Driver (v3.13.0+)
masterStarting 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
DefaultLoadBalancingPolicyis 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.Use RequestTrackingInfo and HostTrackingInfo for telemetry
masterWhen implementing
IRequestTrackeror working with request observers, the driver uses two primary data structures to provide context:RequestTrackingInfo: Contains aConcurrentDictionary<string, object> Itemsfor storing arbitrary metadata (like OpenTelemetry activities) and anIStatement Statementrepresenting the query being executed.HostTrackingInfo: A struct containing theHostobject 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; } }Use Proxy Login with DSE Unified Authentication
masterDSE 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:
- The authenticated user must be granted permission to proxy the target user via CQL:
GRANT PROXY.LOGIN ON ROLE '<target_user>' TO '<authenticated_user>' - Use
DsePlainTextAuthProviderwith 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);- The authenticated user must be granted permission to proxy the target user via CQL:
Understand the driver's API change and versioning policy
masterThe 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.,
ILoadBalancingPolicyviaBuilder.WithLoadBalancingPolicy, orITimestampGeneratorviaBuilder.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.