SQL Server command timeout: 3 options with .NET Entity Framework

85 views Asked by At

From time to time I get a Azure database transaction timeout from a .NET core application using entity framework. What's the hierachy of setting the command timeout? There are three options: a) within the connection string, b) Options, Query execution settings, c) buidler, AddDbContext options. Since the timeout never really precisely happens at the configured timeout settings I would like to know.

Apparently just setting it with the connection string doesn't work.

This is the transaction code:

protected async Task UpdateDatabaseAsync() {

    Logger.LogInformation( "Updating database..." );

    // convert BlockingCollection to list
    var scrapeResults = ScrapeResultsModel.ToList();

    // check if list is empty
    if( scrapeResults.Count == 0 )
        Logger.LogInformation( "List is empty, nothing to update!" );
    else {

        // create a TransactionScope to ensure all or nothing
        // Not using "using" here for DB context/connection is recommended by MS because of DI container.
        using var transactionScope = new TransactionScope( TransactionScopeAsyncFlowOption.Enabled );
        try {

            // load all objects by ObjectId from _scrapeResultModel from DB
            List<string> objectIds = new();
            foreach( var result in scrapeResults ) {
                objectIds.Add( result.ObjectId );
            }
            var query = DbContext.RealEstates.Where( x => objectIds.Contains( x.ObjectId ) );
            var existingRecords = await query.ToListAsync();

            // compare lists
            for( int i = 0; i < existingRecords.Count; i++ ) {
                for( int j = scrapeResults.Count - 1; j >= 0; j-- ) {

                    // exclusions for scraped objects here

                    // remove cemetery objects
                    var title = scrapeResults[ j ].Title?.ToLower();
                    var fullText = scrapeResults[ j ].FullTextEs?.ToLower();
                    if( fullText != null && title != null &&
                        ( title.Contains( "cementerio" ) || fullText.Contains( "cementerio" ) ) ) {
                        Logger.LogInformation(
                            $"Object {scrapeResults[ j ].ObjectId} is a cemetery/cementerio, removed from list! Title: {scrapeResults[ j ].Title}" );
                        scrapeResults.RemoveAt( j );
                        continue;
                    }

                    // if objects have the same ObjectId compare prices
                    if( existingRecords[ i ].ObjectId == scrapeResults[ j ].ObjectId ) {
                        if( existingRecords[ i ].PriceTotal == scrapeResults[ j ].PriceTotal &&
                            existingRecords[ i ].RentMonthly == scrapeResults[ j ].RentMonthly ) {

                            // if object already exists and price has not changed, delete from list
                            Logger.LogInformation(
                                $"Object {scrapeResults[ j ].ObjectId} already exists, removed from list." );
                            scrapeResults.RemoveAt( j );
                        }
                        else {
                            // Mark the new price as changed.
                            // Leave the object in list and it will be added to DB.
                            // Multiple entries will be created for the same object!
                            // for sale:
                            if( scrapeResults[ j ].Type == 1 ) {
                                scrapeResults[ j ].LastChangedField += "PriceTotal,";
                                scrapeResults[ j ].DateLastChange = DateTime.UtcNow;
                                decimal.TryParse( existingRecords[ i ].PriceTotal, out var oldPrice );
                                decimal.TryParse( scrapeResults[ j ].PriceTotal, out var newPrice );
                                if( oldPrice > 0 ) {
                                    var discount = decimal.Round( ( newPrice - oldPrice ) / oldPrice * 100, 2 );
                                    scrapeResults[ j ].Discount = discount.ToString( CultureInfo.CurrentCulture );
                                    Logger.LogInformation(
                                        $"New price for object {scrapeResults[ j ].ObjectId}: {scrapeResults[ j ].PriceTotal}" );

                                }
                            }
                            // for rent:
                            else if( scrapeResults[ j ].Type == 2 ) {
                                scrapeResults[ j ].LastChangedField += "RentMonthly,";
                                scrapeResults[ j ].DateLastChange = DateTime.UtcNow;
                                decimal.TryParse( existingRecords[ i ].RentMonthly, out var oldPrice );
                                decimal.TryParse( scrapeResults[ j ].RentMonthly, out var newPrice );
                                if( oldPrice > 0 ) {
                                    var discount = decimal.Round( ( oldPrice - newPrice ) / oldPrice * 100, 2 );
                                    scrapeResults[ j ].Discount = discount.ToString( CultureInfo.CurrentCulture );
                                    Logger.LogInformation(
                                        $"New price for object {scrapeResults[ j ].ObjectId}: {scrapeResults[ j ].RentMonthly}" );
                                }
                            }
                            else
                                throw new Exception( "Unknown type!" );
                        }
                    }
                }
            }
            // convert image BlockingCollection to list
            var scrapeResultsImage = ScrapeResultsImage.ToList();
            // add related images to real estate objects
            for( int i = 0; i < scrapeResults.Count; i++ ) {
                Logger.LogInformation( $"New image to DB for RealEstate objectId: {scrapeResults[ i ].ObjectId}" );
                for( int j = 0; j < scrapeResultsImage.Count; j++ ) {
                    if( scrapeResults[ i ].ObjectId == scrapeResultsImage[ j ].ObjectId ) {
                        Logger.LogInformation(
                            $"New image to DB, Guid = {scrapeResultsImage[ j ].Guid}, ObjectId = {scrapeResults[ i ].ObjectId}" );
                        scrapeResults[ i ].Images.Add( scrapeResultsImage[ j ] );
                    }
                }
            }

            // add completed list to DB
            Logger.LogInformation( $"Adding complete list with {scrapeResults.Count} items to DB..." );
            if( scrapeResults.Count > 0 ) {
                await DbContext.RealEstates.AddRangeAsync( scrapeResults );
                // Microsoft: SaveChangesAsync() is not recommended for use with TransactionScope.
                var result = DbContext.SaveChanges();
                transactionScope.Complete();
                Logger.LogInformation( $"List added to DB, commit! Results = {result}" );
            }
        }
        catch( Exception e ) {
            Logger.LogError( e, "Error updating database, rollback!" );
        }
    }
}
0

There are 0 answers