Dapper, SQL Server & Shipping Software

Dapper Performance Optimization:
Practical Techniques for High-Performance Shipping Software & .NET Applications

Learn practical Dapper performance optimization techniques for SQL Server, shipment processing, carrier API integration and high-performance enterprise .NET applications.

August 2026 • 14 min read • By Kshitij Mehta

Dapper performance optimization becomes especially important in shipping software, where shipment queries, address lookups, carrier service selection, tracking updates and operational reports can all depend on SQL Server. A shipping platform may process orders, packages, customers, sender and receiver addresses, carrier services and shipment status through the same application.

In ShippingPortal, the data-access layer combines Dapper, ADO.NET and Entity Framework over a SQL Server-backed application. The codebase works with shipment orders, sender and receiver address books, carrier service configuration, activity logs and other operational data. The key lesson is that Dapper is only one part of the performance path: SQL design, indexes, connection management, result-set size, synchronization and external carrier API latency all matter.

This guide uses shipping-oriented examples throughout so the techniques are relevant to enterprise shipping software, shipping API integration and multi-carrier platforms. Examples cover shipment lookup, address retrieval, carrier service selection, shipping rates, label processing, tracking, DHL API Integration, UPS API Integration, DPD API Integration and Asendia API Integration.

Key Takeaways
  • Optimize SQL Server before assuming Dapper is the bottleneck.
  • Reduce unnecessary shipment and address database round trips.
  • Return only the columns required by the shipping workflow.
  • Use pagination for large shipment and activity lists.
  • Keep carrier-specific API logic behind clear integration boundaries.
  • Measure database time separately from carrier API time.

1. Understand the ShippingPortal Performance Path

A shipping platform has more moving parts than a simple CRUD application. A shipment workflow can begin with order data, load sender and receiver addresses, determine a carrier service, retrieve shipping rates, create a shipment, process a label and store a tracking reference.

A useful model is:


ShippingPortal
    ↓
Application Service
    ↓
Dapper / ADO.NET / Entity Framework
    ↓
SQL Server
    ↓
Carrier Integration Layer
    ↓
DHL / UPS / DPD / Asendia API
                

Each layer has a different performance profile. SQL Server performance is affected by indexes and execution plans. Dapper performance depends heavily on the amount of data requested and mapped. Carrier API latency depends on the external service. Optimizing one layer does not automatically optimize the complete shipment workflow.


2. Measure Dapper Performance Before Optimizing

Start by measuring the complete shipping operation rather than changing database code based on assumptions. A shipment request may spend time in SQL Server, address processing, carrier API calls, response parsing and local application processing.

Useful measurements include:

  • Shipment API response time
  • SQL query execution time
  • Number of SQL Server round trips
  • Rows returned from shipment and address queries
  • Carrier API response time
  • Application CPU and memory usage

var stopwatch = Stopwatch.StartNew();

var shipment = await connection.QuerySingleOrDefaultAsync<Shipment>(
    sql,
    new
    {
        ShipmentNo = shipmentNo
    });

stopwatch.Stop();

logger.LogInformation(
    "Shipment lookup completed in {ElapsedMs} ms",
    stopwatch.ElapsedMilliseconds);
                
Shipping Tip

Measure database time separately from carrier API time. A slow shipment request is not necessarily a slow Dapper query.


3. Optimize Shipment Queries Before Optimizing Dapper

ShippingPortal stores shipment and order information in SQL Server. A shipment lookup should return the information needed by the current operation rather than loading an entire row when most columns are not required.


SELECT
    ID,
    OrderID,
    UserID,
    TrackingNo,
    CourierID,
    SenderName,
    ReceiverName,
    TotalCost,
    Label_Generation_Date
FROM tblOrders
WHERE ID = @OrderId;
                

The important optimization is not a Dapper-specific setting. It is reducing the work SQL Server performs and reducing the amount of data the application must map.

For shipment-heavy workloads, review execution plans for frequently used filters such as shipment identifiers, tracking numbers, user identifiers, carrier identifiers and shipment dates.


4. Select Only the Columns Required by the Shipping Workflow

Avoid SELECT * for shipment lists, tracking summaries and operational dashboards. Shipping records can contain many address, pricing and processing fields that are unnecessary for a particular screen.


var shipments = await connection.QueryAsync<ShipmentSummary>(
    """
    SELECT
        ID,
        OrderID,
        TrackingNo,
        CourierID,
        SenderName,
        ReceiverName,
        Label_Generation_Date
    FROM tblOrders
    ORDER BY ID DESC
    """);
                

Smaller shipment projections reduce SQL Server I/O, network traffic, Dapper mapping work and application memory usage.


5. Reduce Database Round Trips for Shipment Operations

A shipping dashboard can become inefficient when one request separately loads the shipment, sender, receiver, carrier service and tracking history.


Load Shipment
    ↓
Load Sender Address
    ↓
Load Receiver Address
    ↓
Load Carrier Service
    ↓
Load Tracking History
                

Review whether these operations genuinely require separate database calls. Some related information can be loaded together, while other information may have a different cache or lifecycle requirement.

Dapper's QueryMultipleAsync can help when several independent result sets naturally belong to one database operation.


using var multi = await connection.QueryMultipleAsync(
    """
    SELECT
        ID,
        TrackingNo,
        CourierID,
        SenderName,
        ReceiverName
    FROM tblOrders
    WHERE ID = @OrderId;

    SELECT
        ID,
        SenderName,
        SenderCountry,
        SenderCity,
        SenderZip
    FROM tblAddressBookSender
    WHERE ID = @SenderAddressId;
    """,
    new
    {
        OrderId = orderId,
        SenderAddressId = senderAddressId
    });

var shipment =
    await multi.ReadSingleOrDefaultAsync<ShipmentSummary>();

var sender =
    await multi.ReadSingleOrDefaultAsync<SenderAddress>();
                
Practical Rule

Reduce round trips when it simplifies the workflow, but do not combine unrelated queries merely to make the database call count smaller.


6. Optimize Sender and Receiver Address Lookups

Shipping systems repeatedly access sender and receiver addresses. ShippingPortal contains dedicated sender and receiver address-book workflows, including bulk address operations.


var receiver = await connection.QuerySingleOrDefaultAsync<ReceiverAddress>(
    """
    SELECT
        ID,
        ReceiverName,
        ReceiverAddress,
        ReceiverCountry,
        ReceiverCity,
        ReceiverZip,
        ReceiverPhoneNo
    FROM tblAdressBookReceiver
    WHERE UserID = @UserID
      AND ID = @ReceiverId;
    """,
    new
    {
        UserID = userId,
        ReceiverId = receiverId
    });
                

If the application frequently filters addresses by user, postal code, reference code or another business key, review the corresponding SQL Server indexes rather than repeatedly loading large address tables.


7. Use Server-Side Pagination for Shipment Lists

Shipping platforms can accumulate large shipment histories. Loading every shipment into memory for an operational grid does not scale.


var shipments = await connection.QueryAsync<ShipmentSummary>(
    """
    SELECT
        ID,
        OrderID,
        TrackingNo,
        CourierID,
        SenderName,
        ReceiverName
    FROM tblOrders
    ORDER BY ID DESC
    OFFSET @Offset ROWS
    FETCH NEXT @PageSize ROWS ONLY;
    """,
    new
    {
        Offset = (page - 1) * pageSize,
        PageSize = pageSize
    });
                

This pattern is useful for shipment search, order history, tracking records, address-book screens and operational activity logs.


8. Use Parameterized Dapper Queries

Shipping data contains customer, address, order and tracking information. Parameterized SQL should be the normal approach when application values are supplied to the query.


var shipments = await connection.QueryAsync<ShipmentSummary>(
    """
    SELECT
        ID,
        TrackingNo,
        CourierID,
        SenderName,
        ReceiverName
    FROM tblOrders
    WHERE UserID = @UserID
      AND TrackingNo LIKE '%' + @TrackingNo + '%';
    """,
    new
    {
        UserID = userId,
        TrackingNo = trackingNo
    });
                

Avoid constructing shipment-search SQL by concatenating customer input, tracking numbers or address values into query strings.


9. Tune SQL Server Indexes for Shipping Workloads

Dapper cannot make an unindexed SQL Server query fast. Shipping workloads commonly filter by shipment identifiers, tracking numbers, user identifiers, carrier identifiers and dates.

Review execution plans and database telemetry for:

  • Shipment and order lookup predicates
  • Tracking-number searches
  • Carrier service filters
  • User-specific shipment lists
  • Date-based shipment reporting
  • Address-book queries

Add indexes based on measured workload patterns and verify that additional indexes do not create unacceptable write overhead.


10. Manage SQL Connections Efficiently

ShippingPortal uses ADO.NET SqlConnection in its data-access layer, while Dapper can execute lightweight queries against those connections.


await using var connection =
    new SqlConnection(connectionString);

await connection.OpenAsync();

var shipment = await connection.QuerySingleOrDefaultAsync<Shipment>(
    sql,
    parameters);
                

Open the connection when the database operation is ready, execute the required work and dispose the connection when finished. Do not keep a SQL connection open while waiting for a DHL, UPS, DPD or Asendia API response.


11. Use Async Database Access in Shipping APIs

Shipment processing often waits on SQL Server and carrier services. Asynchronous Dapper APIs allow request threads to avoid blocking while waiting for database I/O.


public async Task<ShipmentSummary> GetShipmentAsync(
    int orderId)
{
    await using var connection =
        new SqlConnection(_connectionString);

    return await connection.QuerySingleOrDefaultAsync<ShipmentSummary>(
        """
        SELECT
            ID,
            OrderID,
            TrackingNo,
            CourierID,
            SenderName,
            ReceiverName
        FROM tblOrders
        WHERE ID = @OrderId;
        """,
        new
        {
            OrderId = orderId
        });
}
                

Avoid synchronous waits such as .Result and .Wait() inside ASP.NET Core request processing.


12. Keep Carrier API Integration Separate from Data Access

One important architecture pattern in ShippingPortal is separating common shipment data from carrier-specific communication. The database layer provides the shipment information, while the selected carrier integration builds the external request.


Shipping Data
    ↓
Common Shipment Model
    ↓
Carrier Integration
    ↓
Carrier-Specific Request
    ↓
External Carrier API
    ↓
Carrier Response
    ↓
Standard Shipment Result
                

This prevents DHL-specific, UPS-specific, DPD-specific and Asendia-specific request and response structures from spreading throughout the rest of the shipping application.


13. DHL API Integration with C# and .NET

DHL API Integration can be implemented as a carrier-specific workflow within a C# / .NET shipping platform. Common shipment information is prepared first, then the DHL integration constructs the request expected by the selected DHL service.

The ShippingPortal integration approach keeps the common shipment workflow independent from DHL-specific request and response models. Shipment data can include sender and receiver information, package dimensions, weight, quantity and service information.


// Sanitized DHL API integration example

var shipmentRequest = new
{
    ShipmentReference = shipment.Reference,
    ServiceCode = shipment.ServiceCode,

    Sender = new
    {
        shipment.SenderName,
        shipment.SenderCountry,
        shipment.SenderPostalCode
    },

    Receiver = new
    {
        shipment.ReceiverName,
        shipment.ReceiverCountry,
        shipment.ReceiverPostalCode
    },

    Packages = packages.Select(package => new
    {
        package.Weight,
        package.Length,
        package.Width,
        package.Height
    })
};

var response =
    await dhlClient.CreateShipmentAsync(shipmentRequest);
                

The carrier response can then be converted into the application's standard shipment result, including tracking information and shipping label data.


14. UPS API Integration with a Common Shipping Model

UPS API Integration can use the same application-level shipment model while maintaining a separate UPS-specific request structure.

A UPS shipping workflow can include authentication, service selection, sender and receiver information, package dimensions and weights, multi-package processing, shipment creation and response handling.


// Sanitized UPS API integration example

var upsRequest =
    BuildUpsShipmentRequest(
        shipment,
        packages,
        shipment.ServiceCode);

var upsResponse =
    await upsClient.CreateShipmentAsync(upsRequest);

var trackingNumber =
    upsResponse.TrackingNumber;

var label =
    upsResponse.Label;
                

Keeping UPS API Integration behind the carrier boundary allows UPS to coexist with DHL, DPD and Asendia within a centralized multi-carrier shipping platform.


15. DPD API Integration for Shipment Processing

DPD API Integration can follow the same common shipment workflow: validate shipment data, determine the requested DPD service, construct the carrier request, submit it and process the shipment, tracking and label information returned by the carrier.


// Sanitized DPD API integration example

var dpdShipment = new DpdShipmentRequest
{
    Reference = shipment.Reference,
    ServiceCode = shipment.ServiceCode,

    Sender = MapSender(shipment),
    Receiver = MapReceiver(shipment),

    Packages = packages
        .Select(MapPackage)
        .ToList()
};

var result =
    await dpdClient.CreateShipmentAsync(dpdShipment);

var trackingReference =
    result.TrackingReference;
                

The DPD-specific request and response handling remains inside the DPD integration while the rest of the application continues to use a common shipment workflow.


16. Asendia API Integration and Shipment Processing

Asendia API Integration can be implemented as another carrier adapter in the multi-carrier architecture. The common shipment model supplies the business data while the Asendia integration handles carrier-specific request construction and response processing.


// Sanitized Asendia API integration example

var asendiaRequest =
    BuildAsendiaShipmentRequest(
        shipment,
        packages,
        shipment.ServiceCode);

var asendiaResponse =
    await asendiaClient.CreateShipmentAsync(
        asendiaRequest);

var trackingNumber =
    asendiaResponse.TrackingNumber;
                

Separating Asendia API Integration from the common shipping workflow allows Asendia to be maintained alongside DHL API Integration, UPS API Integration and DPD API Integration without duplicating the complete application workflow.


17. Optimize Shipping Rates and Service Selection

Rate retrieval introduces another performance boundary because the workflow may involve external carrier services. The application should prepare the smallest useful request, avoid duplicate calls and return only the service and rate information required by the current shipment workflow.


Shipment Data
    ↓
Determine Carrier
    ↓
Request Services / Rates
    ↓
Normalize Results
    ↓
Display Shipping Options
                

If the same shipment repeatedly requests the same rate information, review whether short-lived caching or request de-duplication is appropriate for the business workflow.


18. Shipping Label Generation Without Excess Database Work

Shipping label generation should not repeatedly reload the same shipment from SQL Server. Once the shipment request has been prepared and submitted to the carrier, process the returned label information and associate it with the corresponding shipment.


var carrierResult =
    await carrierClient.CreateShipmentAsync(request);

if (carrierResult.Success)
{
    await connection.ExecuteAsync(
        """
        UPDATE tblOrders
        SET TrackingNo = @TrackingNo
        WHERE ID = @OrderId;
        """,
        new
        {
            TrackingNo = carrierResult.TrackingNumber,
            OrderId = orderId
        });
}
                

Keep label processing focused on shipment creation, carrier response processing and persistence of the required result.


19. Optimize Shipment Tracking and Status Updates

Tracking workflows can generate a high volume of reads and writes because shipment status may be checked or synchronized repeatedly.


var trackedShipments =
    await connection.QueryAsync<TrackingSummary>(
        """
        SELECT
            ID,
            TrackingNo,
            CourierID,
            IsProcessed,
            VerificationStatus
        FROM tblOrders
        WHERE TrackingNo IS NOT NULL
          AND TrackingNo <> '';
        """);
                

Store the tracking reference and processing state required by the platform, and use targeted projections for tracking screens rather than loading complete shipment records.


20. Use Bulk Processing for Large Shipping Imports

ShippingPortal contains bulk sender and receiver address workflows and uses SqlBulkCopy for large data transfers. That is very different from executing an individual insert for every imported address.


using var bulkCopy =
    new SqlBulkCopy(connection);

bulkCopy.DestinationTableName =
    "developer.tblAddressBookReceiver_Bulk";

bulkCopy.ColumnMappings.Add(
    "Name",
    "ReceiverName");

bulkCopy.ColumnMappings.Add(
    "PostalCode",
    "ReceiverZip");

bulkCopy.ColumnMappings.Add(
    "Country",
    "ReceiverCountry");

bulkCopy.WriteToServer(dataTable);
                

For large shipping imports, evaluate batch size, transaction strategy, indexes and validation so bulk throughput does not create excessive database load.


21. Use QueryMultiple for Related Shipping Dashboard Data

A shipping dashboard may need recent shipments and carrier service information. When those datasets naturally belong to the same request, Dapper can retrieve multiple result sets from one database interaction.


using var multi =
    await connection.QueryMultipleAsync(
        """
        SELECT TOP 20
            ID,
            TrackingNo,
            CourierID,
            SenderName,
            ReceiverName
        FROM tblOrders
        ORDER BY ID DESC;

        SELECT
            ID,
            ServiceProviderName,
            Product,
            SubCategory
        FROM developer.tblDeliveryService
        ORDER BY ID DESC;
        """);

var recentShipments =
    await multi.ReadAsync<ShipmentSummary>();

var carrierServices =
    await multi.ReadAsync<CarrierService>();
                

Use this deliberately. The objective is to reduce unnecessary database round trips without creating oversized and difficult-to-maintain SQL.


22. Keep Shipping Activity Logging Efficient

Shipping platforms need operational logging for shipment processing and troubleshooting, but excessive logging can become a database performance problem.

ShippingPortal maintains activity data in an activity-log table. Operational searches should be paginated and should return only the fields required by the administration screen.


SELECT
    ID,
    UserID,
    ActivityDetail
FROM developer.tblActivityLog
WHERE UserID = @UserID
ORDER BY ID DESC
OFFSET @Offset ROWS
FETCH NEXT @PageSize ROWS ONLY;
                

Avoid turning every successful database call or carrier request into a large database log record unless that detail is genuinely required for operational diagnostics.


23. Cache Stable Shipping Reference Data

Some shipping data changes less frequently than shipment transactions. Carrier service metadata, country lists, application configuration and other reference information may be suitable candidates for caching when business rules permit it.


var carrierServices =
    await cache.GetOrCreateAsync(
        "carrier-services",
        async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow =
                TimeSpan.FromHours(6);

            return await repository
                .GetCarrierServicesAsync();
        });
                

Do not blindly cache dynamic shipping rates or shipment status. Cache lifetime must match the business meaning of the data.


24. Do Not Hide Slow Carrier or SQL Operations with Larger Timeouts

A longer command timeout can be valid for a genuinely long-running operation, but it does not optimize that operation.

If a shipment query or carrier API request is unexpectedly slow, determine whether the delay comes from SQL execution, connection wait time, serialization, network latency or the external carrier service.

Important

A larger timeout changes how long the application waits. It does not make a slow SQL query or carrier API call faster.


25. Keep Shipment Database Transactions Focused

Transaction correctness is essential in shipping workflows, especially when several related database changes must succeed together.


await using var connection =
    new SqlConnection(connectionString);

await connection.OpenAsync();

await using var transaction =
    await connection.BeginTransactionAsync();

try
{
    await connection.ExecuteAsync(
        insertShipmentSql,
        shipment,
        transaction);

    await connection.ExecuteAsync(
        insertPackageSql,
        packages,
        transaction);

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}
                

Keep the transaction focused on database work. Do not hold a SQL transaction open while waiting for DHL, UPS, DPD or Asendia API responses unless the architecture explicitly requires it.


26. Handle Carrier API Errors Without Losing Shipment Context

A carrier API failure is different from a normal SQL exception because an external service may reject the shipment, return validation information or become temporarily unavailable.


var result =
    await carrierClient.CreateShipmentAsync(request);

if (!result.Success)
{
    logger.LogWarning(
        "Carrier shipment creation failed for {ShipmentReference}: {Message}",
        shipment.Reference,
        result.Message);

    return ShipmentResult.Failed(
        shipment.Reference,
        result.Message);
}
                

Preserve the internal shipment reference and processing context while translating the carrier response into a standard application result. This is useful across DHL API Integration, UPS API Integration, DPD API Integration and Asendia API Integration.


27. Benchmark Shipping Queries with Realistic Data

A Dapper benchmark using a few hundred shipments does not necessarily predict the performance of a shipping platform containing years of orders, addresses, tracking references and carrier history.

Test realistic workloads such as:

  • Large shipment tables
  • Concurrent shipment searches
  • Bulk address uploads
  • Tracking synchronization workloads
  • Concurrent carrier API requests
  • Peak operational dashboard usage

Measure SQL execution time, end-to-end response time, database CPU and I/O, result-set size and external API latency together.


28. Dapper, Entity Framework and ADO.NET Can Coexist

ShippingPortal demonstrates a practical enterprise-software principle: an application does not necessarily need one data-access technology for every operation.

Dapper can be useful for targeted SQL-controlled reads and operational queries. Entity Framework can remain appropriate for entity-oriented workflows, while direct ADO.NET and SqlBulkCopy can handle high-volume database operations such as bulk imports.


ShippingPortal Data Access
        │
        ├── Dapper
        │     └── Targeted queries / reports
        │
        ├── Entity Framework
        │     └── Entity-oriented operations
        │
        └── ADO.NET / SqlBulkCopy
              └── Direct SQL / bulk processing
                

The important optimization is choosing the appropriate technique for each workload rather than forcing the entire platform into one abstraction.


Dapper Performance Optimization Checklist for Shipping Software

Before considering a ShippingPortal query or workflow optimized, review the entire path from the application to SQL Server and the external carrier service.

  • Is the SQL query returning only the required shipment data?
  • Are shipment and address queries using appropriate indexes?
  • Can unnecessary database round trips be removed?
  • Are shipment lists paginated on the server?
  • Are SQL connections kept out of external carrier API waits?
  • Are Dapper queries parameterized?
  • Are large address imports handled with bulk techniques?
  • Are carrier APIs isolated behind carrier-specific integrations?
  • Are DHL, UPS, DPD and Asendia responses normalized into a common result?
  • Are SQL time and carrier API time measured separately?

Conclusion

Dapper performance optimization in shipping software is not about a single Dapper setting. It is about the complete shipping workflow: SQL Server queries, database indexes, result-set size, connections, shipment processing, carrier API calls and application architecture.

ShippingPortal provides a useful real-world example because the data-access layer spans shipment orders, sender and receiver addresses, carrier services, bulk imports, activity logs and tracking-related processing. These workloads benefit from different optimization techniques rather than one universal approach.

The same architectural principles apply when integrating DHL APIs, UPS APIs, DPD APIs and Asendia APIs. Keep common shipment workflows separate from carrier-specific request and response models, optimize the database independently from external API latency and measure the complete workflow before changing code.


About the Author

I'm Kshitij Mehta, a Senior .NET Developer and ASP.NET Core Consultant with over 14 years of experience designing, developing and optimizing enterprise software solutions.

My work includes logistics platforms, shipping management systems, carrier API integrations, POS solutions, REST APIs, SQL Server applications and business software built with C# and .NET.

This article is based on the architecture and data-access patterns used in an enterprise shipping platform and focuses on practical Dapper, SQL Server and carrier integration considerations rather than synthetic micro-benchmarks.