У нас сейчас проблема с производительностью. Многие проблемы напрямую связаны с базой данных, но я не уполномочен что-либо менять (из-за существующего программного обеспечения и т. Д.).
Проблемы, с которыми я столкнулся при создании API, заключались в том, что хранимые процедуры, которые содержат ужасные имена полей и часто используют сложные запросы для построения набора результатов.
Из-за этого мне не удалось использовать ORM с базой данных, и мне пришлось создавать собственное решение.
Я придумал этот дизайн:
public abstract class OracleService
{
protected readonly IOracleUnitOfWork OracleUnitOfWork;
protected OracleService(IOracleUnitOfWork oracleUnitOfWork) => OracleUnitOfWork = oracleUnitOfWork;
protected async Task<List<T>> ListAsync<T>(string procedureName, IList<OracleParameter> parameters, int limit, Func<DataRow, T> mapper) => await ListAsync(procedureName, parameters, limit, mapperSync: mapper);
protected async Task<List<T>> ListAsync<T>(string procedureName, IList<OracleParameter> parameters, int limit, Func<DataRow, Task<T>> mapper) => await ListAsync(procedureName, parameters, limit, mapperAsync: mapper);
protected async Task<List<T>> ListAsync<T>(string procedureName, IList<OracleParameter> parameters, Func<DataRow, T> mapper) => await ListAsync(procedureName, parameters, 0, mapperSync: mapper);
protected async Task<List<T>> ListAsync<T>(string procedureName, IList<OracleParameter> parameters, Func<DataRow, Task<T>> mapper) => await ListAsync(procedureName, parameters, 0, mapperAsync: mapper);
protected T Get<T>(string procedureName, IList<OracleParameter> parameters, Func<DataRow, T> mapper)
{
if (mapper == null) throw new ArgumentNullException(nameof(mapper));
var dataTable = OracleUnitOfWork.FillDataTable(procedureName, parameters);
var dataRows = dataTable.AsEnumerable();
if (dataRows.Count() != 1) throw new Exception("The number of rows returned is not 1.");
return mapper(dataRows.First());
}
private async Task<List<T>> ListAsync<T>(string procedureName, IList<OracleParameter> parameters, int limit, Func<DataRow, T> mapperSync = null, Func<DataRow, Task<T>> mapperAsync = null)
{
if (mapperSync == null && mapperAsync == null) throw new ArgumentNullException();
var dataTable = OracleUnitOfWork.FillDataTable(procedureName, parameters);
var dataRows = limit == 0 ? dataTable.AsEnumerable() : dataTable.AsEnumerable().Take(limit);
if (mapperSync != null)
return dataTable.AsEnumerable().Select(mapperSync).ToList();
var models = new List<T>();
foreach(var row in dataRows)
{
var model = await mapperAsync(row);
models.Add(model);
}
return models;
}
}
Как видите, мы используем DataTable
для извлечения наших данных (мы использовали DataReader
, но прирост производительности не был), которые затем передаются в класс отображения.
Класс mapper может быть таким простым:
public static partial class OracleMapper
{
public static class Complaint
{
/// <summary>
/// Maps the Returns entity
/// </summary>
/// <param name="reader">The data reader</param>
/// <returns>An Returns entity</returns>
public static Models.Complaint Map(DataRow reader) => new Models.Complaint
{
ComplaintNumber = reader["cidno"].ToString(),
ComplaintLine = reader["cidlineno"].ToString(),
AccountNumber = reader["accno"]?.ToString(),
OrderNumber = reader["ordernumber"]?.ToString(),
LineNumber = reader["orderline"]?.ToString(),
Quantity = reader["quantity"].ToDecimal(),
Reference = reader["complaint_reference"].ToString(),
Status = reader["status"].ToString(),
DateCreated = reader["complaintdate"].ToDate(),
CollectionDate = reader["collectiondate"].ToDate(),
ShipmentNumber = reader["shipmentNumber"]?.ToString()
};
}
}
или так сложно:
public static partial class OracleMapper
{
public static class Account
{
/// <summary>
/// Maps the Account entity
/// </summary>
/// <param name="reader">The data reader</param>
/// <returns>An Account entity</returns>
public static Models.Account Map(DataRow reader)
{
var account = new Models.Account
{
AccountNumber = reader["accno"]?.ToString(),
BusinessCategory = reader["buscat"]?.ToString(),
Category = reader["category"]?.ToString(),
CountryCode = reader["nationcode"]?.ToString(),
Name = reader["custname"]?.ToString(),
Disabled = reader["obsolete"]?.ToString(),
OnStop = reader["stopped"]?.ToString(),
CatalogueId = reader["prodcat"]?.ToString(),
Profile = reader["profilestring"]?.ToString(),
Trade = reader["proforma"].ToString(),
ShortAccountNumber = reader["customer"]?.ToString(),
StatementAccountNumber = reader["statementac"]?.ToString(),
SalesOfficeNotes = reader["salesofficenotes"]?.ToString(),
TerritoryCode = reader["territory"]?.ToString(),
Type = reader["accountType"]?.ToString().Trim(),
UnmannedAddress = reader["unmannedaddress"]?.ToString(),
ZoneRateCode = reader["zoneratecode"]?.ToString(),
Address = new Models.Address
{
Name = reader["custname"]?.ToString(),
HouseNumber = reader["add1"]?.ToString(),
Street = reader["add2"]?.ToString(),
Town = reader["add3"]?.ToString(),
County = reader["add4"]?.ToString(),
PostCode = reader["postcode"]?.ToString()
}
};
double.TryParse(reader["latitude"]?.ToString(), out var latitude);
double.TryParse(reader["longitude"]?.ToString(), out var longitude);
account.Address.Latitude = latitude;
account.Address.Longitude = longitude;
account.Contact = new Contact
{
Position = "Primary Contact",
Initial = "",
Title = "",
Name = reader["primary_contact"]?.ToString(),
Telephone = reader["phone"]?.ToString(),
Fax = reader["fax"]?.ToString(),
Email = reader["email"]?.ToString()
};
account.Currency = new Currency
{
Code = reader["currency"]?.ToString()
};
account.Group = new Group
{
Value = reader["grpcode"]?.ToString(),
Description = reader["grpdesc"]?.ToString()
};
account.SalesAnalysisRepresentative = new SalesRepresentative
{
Code = reader["sr_code"]?.ToString(),
FullName = reader["sr_name"]?.ToString()
};
account.CommissionAnalysisRepresentative = new SalesRepresentative
{
Code = reader["cr_code"]?.ToString(),
FullName = reader["cr_name"]?.ToString()
};
account.ServiceRepresentative = new SalesRepresentative
{
Code = reader["repcode"]?.ToString(),
FullName = reader["repname"]?.ToString(),
Email = reader["repemail"]?.ToString(),
Telephone = reader["reptelno"]?.ToString()
};
account.Finance = new Finance
{
Currency = reader["currency"]?.ToString(),
CurrenyConversionRate = Convert.ToDecimal(reader["currency_convrate"]?.ToString()),
Settlement = Convert.ToDecimal(reader["settlement"]?.ToString()),
CreditLimit = Convert.ToDecimal(reader["creditlimit"]?.ToString()),
CurrentBalance = Convert.ToDecimal(reader["balance"]?.ToString()),
OrdersOnHand = Convert.ToDecimal(reader["ordersonhand"]?.ToString()),
AvailableCredit = Convert.ToDecimal(reader["availablecredit"]?.ToString()),
TradingPeriod = reader["tradingperiod_nicestring"]?.ToString(),
VatCode = reader["vatcode"]?.ToString(),
Visible = reader["financialdatavisible"]?.ToString(),
CompanyRegistrationNumber = reader["company_registration_number"]?.ToString(),
CustomerPriceFile = reader["custpricefile"]?.ToString(),
EdiReference = reader["edi_reference"]?.ToString(),
ExpectedReceiptDay = Convert.ToInt16(reader["expectedreceiptday"]?.ToString()),
GroupPriceFile = reader["grouppricefile"]?.ToString(),
ListPriceFile = reader["listpricefile"]?.ToString(),
Owner = reader["Owner"]?.ToString(),
OwnerName = reader["OwnerName"]?.ToString(),
PaymentTerms = new PaymentTerms
{
Value = reader["paymenttermsval"]?.ToString(),
Description = reader["paymentterms"]?.ToString()
}
};
account.AvailableDeliveryDates = new List<DateTime>();
account.Contacts = StatementContactMapping.MapList(reader) ?? new List<Contact>();
return account;
}
}
}
Наши запросы не быстрые, а в некоторых случаях они невыносимы. Но, как я уже говорил, я не могу изменить базу данных или хранимые процедуры.
Есть ли у кого-нибудь код только предложения, которые могут помочь повысить нашу производительность?
Думаю, стоит упомянуть, что я пробовал Dapper и Entity Framework безрезультатно.