Заранее извиняюсь за длину этого вопроса. Я пишу приложение UWP. Из-за ограничений аппаратной платформы целью является Windows 1703 (сборка 15063). Базовая модель вызывает веб-сервис WCF для взаимодействия с SQL сервером. У меня есть метод тестирования (MSTest) для моей модели представления, который приводит к записи данных в базу данных (через веб-сервис). В том же методе испытаний я прочитал результаты обратно. Поскольку эта сборка Windows не поддерживает sql клиента, я написал другой веб-сервис, предназначенный исключительно для поддержки моих тестов, который будет запрашивать базу данных. Вкратце, проблема в том, что чтение из моей базы данных (с использованием службы поддержки) происходит до записи! Я проверил это с помощью SQL Server Profiler. Конечно, все Asyn c, но, насколько мне известно, код должен все еще выполняться в последовательности. Надеюсь, код объясняет это лучше, чем я:
[TestMethod]
public async Task Coolbox_Cage_ValidTest()
{
// Set up mocks etc...
// Send a message to the view model. sessionViewModel will call a WFC web service to
// write to the DB.
await sessionViewModel.SetInput(user, "Route-062-1");
// If we wait for a second, the correct results are returned from the database.
// If we don't, we get an early read where the results of SetInput have not yet been written.
//await Task.Delay(1000);
// GetResultsAsXDoc calls a different WCF web service to query the DB.
XDocument xdoc = await GetResultsAsXDoc(client, "select * from STC_Box;");
var q1 = from el in xdoc.Elements("DataSet").Elements("DataRow")
select el;
// This assert will fail if we don't have the Task.Delay
// Otherwise it succeeds.
var b1 = from b in q1
where (string)b.Elements("Barcode").FirstOrDefault() == "062_S007743898_99_01"
&& (string)b.Elements("Cage").FirstOrDefault() == "Route-062-1"
select b;
Assert.IsTrue(b1.Count() == 1, "Did not find STC_Box record for 062_S007743898_99_01 in Cage 'Route-062-1'.");
}
private async Task<XDocument> GetResultsAsXDoc(TestServices.TestServicesClient client, string sql)
{
string xml = await client.GetDataXmlAsync(sql);
XDocument xdoc = XDocument.Parse(xml);
return xdoc;
}
Модель представления:
public async Task SetInput(User user, string input)
{
// ...other stuff
if (await loadCoolBoxIntoCage(user.UserID, rCoolbox, route)) { return; }
// ...
}
private async Task<bool> loadCoolBoxIntoCage(string userID, CoolboxInputCode cb, RouteInputCode route)
{
string msg = $"Is cool box {cb.ScanCode} being loaded into cage {route.ScanCode}?";
DialogResult dialogResult = await _dialogService.GetYesNo(msg, "Confirm");
if (dialogResult != DialogResult.Yes) return false;
_despatchModel.LoadCoolBoxIntoCage(userID, cb.ScanCode, route.ScanCode);
return true;
}
Модель:
public async void LoadCoolBoxIntoCage(string userID, string CoolboxCode, string CageCode)
{
try
{
await Common.STC_LoadCoolBoxIntoCage(userID, CoolboxCode, CageCode);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
throw;
}
}
// Common (Class Library)
public static async Task STC_LoadCoolBoxIntoCage(string username, string Coolbox, string Cage)
{
// Local function delegate to call the service method
async Task del(ScannerServicesClient client)
{
await client.STC_LoadCoolBoxIntoCageAsync(username, Coolbox, Cage);
}
// Call the service, passing the method
await CallClient(del);
// Done
return ;
}
/// <summary>
/// Gets a service client, does something with it, then disposes of it.
/// Properly (?) handles WCF errors.
/// This function exists mainly so we don't have to repeat this error-handling in separate routines
/// for every ScannerServicesClient method.
/// </summary>
/// <param name="doSomethingAsync">A delegate that performs some action with the client.</param>
/// <returns>Task</returns>
/// <see cref="https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/use-close-abort-release-wcf-client-resources"/>
/// <see cref="https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-call-wcf-service-operations-asynchronously"/>
private static async Task CallClient(Func<ScannerServicesClient, Task> doSomethingAsync)
{
ScannerServicesClient client = Services.GetScannerServicesClient();
try
{
// Get the service client
await client.OpenAsync();
// Do something with the client
await doSomethingAsync(client);
// Dispose the client.
await client.CloseAsync();
}
catch (FaultException<DataCommandExecutionFault> e)
{
client.Abort();
throw new Exception(e.Detail.Message);
}
catch (CommunicationException e)
{
client.Abort();
throw new Exception(e.Message);
}
catch (TimeoutException e)
{
client.Abort();
throw new Exception(e.Message);
}
catch
{
client.Abort();
throw;
}
}
Основной метод веб-службы:
public void STC_LoadCoolBoxIntoCage(string username, string Coolbox, string Cage)
{
using (var db = new Model.DbContext())
{
SqlParameter pReturnValue = CreateSqlParameter("RETURN_VALUE", SqlDbType.Int, null, ParameterDirection.Output); // ParameterDirection.ReturnValue does not work here!
SqlParameter pUserID = CreateSqlParameter("UserID", SqlDbType.VarChar, username, ParameterDirection.Input);
SqlParameter pCoolbox = CreateSqlParameter("Coolbox", SqlDbType.VarChar, Coolbox, ParameterDirection.Input);
SqlParameter pCage = CreateSqlParameter("Cage", SqlDbType.VarChar, Cage, ParameterDirection.Input);
var data = db.Database.SqlQuery<object>("exec @RETURN_VALUE = [dbo].[spSTC_LoadCoolBoxIntoCage2] @UserID, @Coolbox, @Cage;",
pReturnValue, pUserID, pCoolbox, pCage);
// Force the fetch of results (there are no results!)
var result = data.FirstOrDefault();
}
}
и (наконец!) Метод службы поддержки:
public string GetDataXml(string sql)
{
string connStr = ConfigurationManager.ConnectionStrings["testDB"].ConnectionString;
using (SqlConnection conn = new SqlConnection(connStr))
{
using (SqlCommand command = new SqlCommand(sql, conn))
{
conn.Open();
SqlDataReader reader = command.ExecuteReader();
return reader2Xml(reader);
}
}
}
private string reader2Xml(SqlDataReader reader)
{
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
DataTable schema = reader.GetSchemaTable();
XElement root = new XElement("DataSet", new XAttribute("FieldCount", reader.FieldCount),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"));
int rowIndex = 0;
while (reader.Read())
{
XElement dataRowElement = new XElement("DataRow", new XAttribute("Index", rowIndex++));
for (int i = 0; i < reader.FieldCount; ++i)
{
var ia = schema.Rows[i].ItemArray; // schema row [i] describes data column [i]
var oName = ia[0];
var oType = ia[12];
object cobj = reader[i];
// Express NULL data by omitting the element.
if (cobj != DBNull.Value)
{
dataRowElement.Add(new XElement(oName.ToString(), new XAttribute("Type", oType.ToString()), cobj.ToString()));
}
}
root.Add(dataRowElement);
}
return root.ToString();
}