Reputation: 1553
Need to add an edit function in the mobile app we've created.
We used code from this blog post to resolve version conflicts (i.e. the client's version is always taken).
However, the edit function would sometimes work and would also, most of the times, result to an error. One time the data in the server was updated but an impending operation was still left in the mobile client.
We've taken a look at the exception in the client and the message was just "Error has occurred." Also taken a look at the server analytics and the result code is 500.
I've got three questions:
Update
Turned on logging in the server, changed PatchSale to async so that we can await UpdateAsync(id, patch), and put a try-catch where await UpdateAsync is called.
Here's what was logged in the catch area:
CATCH:
Helplink
Message
Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
StackTrace
at Microsoft.Azure.Mobile.Server.Tables.EntityUtils.<SubmitChangesAsync>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.Mobile.Server.EntityDomainManager`1.<UpdateAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.Mobile.Server.EntityDomainManager`1.<UpdateAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.Mobile.Server.TableController`1.<PatchAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at SynthesisServer.Controllers.SaleController.<PatchSale>d__3.MoveNext()
Source
Microsoft.Azure.Mobile.Server.Entity
AdrianHall suspected that there might be a discrepancy between the Sale Model in the .NET server and its corresponding SQL server. i've compared the two and can't seem to find any difference (i assume that Id, Version, CreatedAt, UpdatedAt, and Deleted are included when extending EntityData). Further, if there is a difference between the model in the .NET server and SQL server how is it possible that can we create a Sale and upload it to the server?
Also, posted the Sale Model in the server below and the columns in the SQL server.
Here's the code for reference:
Server: Update method in the controller
public Task<Sale> PatchSale(string id, Delta<Sale> patch)
{
System.Diagnostics.Trace.TraceInformation("INSIDE PATCH SALE!!!");
return UpdateAsync(id, patch);
}
Mobile Client: Updating Sale
public async Task<Sale> UpdateSaleAsync(Sale sale)
{
await saleTable.UpdateAsync(sale);
return sale;
}
Mobile Client: Syncing Sale
public async Task<bool> SyncSalesAsync()
{
bool wasPushed = true;
try
{
// Sync data with cloud
await MobileService.SyncContext.PushAsync();
await saleTable.PullAsync("allSales", saleTable.CreateQuery());
}
catch (MobileServicePreconditionFailedException<Sale> conflict)
{
Console.WriteLine($"taskTitle_Changed - Conflict Resolution for item ${conflict.Item.Id}");
}
catch (MobileServicePushFailedException exc)
{
Console.WriteLine("Sync Sales MSPFE Exception: ");
Console.WriteLine("/////////////////////");
Console.WriteLine("Message:");
Console.WriteLine(exc.Message);
Console.WriteLine("HelpLink:");
Console.WriteLine(exc.HelpLink);
Console.WriteLine("Source:");
Console.WriteLine(exc.Source);
Console.WriteLine("Stack Trace:");
Console.WriteLine(exc.StackTrace);
Console.WriteLine("/////////////////////");
if (exc.PushResult != null)
{
var c = 1;
foreach (var i in exc.PushResult.Errors)
{
Console.WriteLine("Inside push Details: " + c);
Console.WriteLine("Handled: ");
Console.WriteLine(i.Handled);
Console.WriteLine("Item");
Console.WriteLine(i.Item);
Console.WriteLine("O Kind");
Console.WriteLine(i.OperationKind);
Console.WriteLine("Status");
Console.WriteLine(i.Status);
Console.WriteLine("Table Name");
Console.WriteLine(i.TableName);
Console.WriteLine("Raw Result");
Console.WriteLine(i.RawResult);
Console.WriteLine("Result");
Console.WriteLine(i.Result);
Console.WriteLine("Item");
Console.WriteLine(i.Item);
c++;
Console.WriteLine("Cast Result to Sale");
var serverItem = i.Result.ToObject<Sale>();
Console.WriteLine("Cast Item to Sale");
var localItem = i.Item.ToObject<Sale>();
if (serverItem.Equals(localItem))
{
Console.WriteLine("server item equals");
// Items are the same, so ignore the conflict
await i.CancelAndDiscardItemAsync();
}
else
{
Console.WriteLine("else");
Console.WriteLine("localitem version: " + localItem.Version);
Console.WriteLine("serveritem version: " + serverItem.Version);
// Always take the client
localItem.Version = serverItem.Version ?? localItem.Version;
var item = JObject.FromObject(localItem);
Console.WriteLine("item from jobject");
Console.WriteLine(item);
try
{
await i.UpdateOperationAsync(item);
}
catch (Exception e)
{
Console.WriteLine("Else Message Error");
Console.WriteLine(e.Message);
Console.WriteLine("Else Stack Trace");
Console.WriteLine(e.StackTrace);
}
}
}
}
return false;
}
catch (MobileServiceInvalidOperationException msioe)
{
Console.WriteLine("Sync Sales MSIOE Exception: ");
Console.WriteLine("/////////////////////");
Console.WriteLine(msioe.Message);
Console.WriteLine("----");
Console.WriteLine(msioe.HelpLink);
Console.WriteLine("----");
Console.WriteLine(msioe.Source);
Console.WriteLine("----");
Console.WriteLine(msioe.StackTrace);
return false;
}
catch (Exception e)
{
Console.WriteLine("Sync Sales General Exception: ");
Console.WriteLine("/////////////////////");
Console.WriteLine(e.Message);
Console.WriteLine("----");
Console.WriteLine(e.HelpLink);
Console.WriteLine("----");
Console.WriteLine(e.Source);
Console.WriteLine("----");
Console.WriteLine(e.StackTrace);
return false;
}
return wasPushed;
}
Mobile Client: Sale Model
public class Sale
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "productId")]
public string ProductId { get; set; }
[JsonProperty(PropertyName = "promoterId")]
public string PromoterId { get; set; }
[JsonProperty(PropertyName = "storeId")]
public string StoreId { get; set; }
[JsonProperty(PropertyName = "paymentMethodId")]
public string PaymentMethodId { get; set; }
[JsonProperty(PropertyName = "corporateSale")]
public bool CorporateSale { get; set; }
[JsonProperty(PropertyName = "dateSold")]
public DateTime? DateSold { get; set; }
[JsonProperty(PropertyName = "priceSold")]
public double PriceSold { get; set; }
[JsonProperty(PropertyName = "quantitySold")]
public int QuantitySold { get; set; }
[JsonProperty(PropertyName = "remarks")]
public string Remarks { get; set; }
[JsonProperty(PropertyName = "deleted")]
public bool Deleted { get; set; }
[JsonProperty(PropertyName = "createdAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty(PropertyName = "updatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonProperty(PropertyName = "version")]
public string Version { get; set; }
[JsonProperty(PropertyName = "saleTransactionId")]
public string SaleTransactionId { get; set; }
[JsonIgnore]
public virtual Dictionary<string, string> Data
{
get
{
var data = new Dictionary<string, string>
{
["Id"] = Id,
["ProductId"] = ProductId,
["PromoterId"] = PromoterId,
["StoreId"] = StoreId,
["PaymentMethodId"] = StoreId,
["CorporateSale"] = CorporateSale.ToString(),
["DateSold"] = "",
["PriceSold"] = PriceSold.ToString(),
["QuantitySold"] = QuantitySold.ToString(),
["Remarks"] = Remarks,
["SaleTransactionId"] = SaleTransactionId,
["Deleted"] = Deleted.ToString(),
["CreatedAt"] = CreatedAt.ToString(),
["UpdatedAt"] = UpdatedAt.ToString(),
["Version"] = Version
};
if (DateSold != null) data["DateSold"] = ((DateTime)DateSold).ToString();
return data;
}
}
[JsonIgnore]
public bool IsNew
{
get
{
return string.IsNullOrEmpty(PromoterId) || UpdatedAt == null || CreatedAt == null || string.IsNullOrEmpty(Version);
}
}
public virtual Product Product { get; set;}
public virtual Store Store { get; set; }
public virtual PaymentMethod PaymentMethod { get; set; }
// Default constructor
public Sale() {}
public Sale(Dictionary<String, String> data)
{
DateSold = DateTime.Parse(data["DateSold"]);
CorporateSale = bool.Parse(data["CorporateSale"]);
ProductId = data["ProductId"];
PriceSold = Double.Parse(data["PriceSold"]);
QuantitySold = int.Parse(data["QuantitySold"]);
StoreId = data["StoreId"];
PaymentMethodId = data["PaymentMethodId"];
Remarks = data["Remarks"];
SaleTransactionId = Guid.NewGuid().ToString();
}
public virtual string TransactionId()
{
string value = "Not Synced";
if (!string.IsNullOrEmpty(SaleTransactionId)) value = SaleTransactionId;
return value;
}
public override string ToString()
{
return "I'm a Sale: DateSold " + DateSold + " ProductID " + ProductId + " StoreID " + StoreId + " Corporate Sale " + CorporateSale;
}
public virtual string FormattedCorporateSale()
{
string result = "No";
if (CorporateSale) result = "Yes";
return result;
}
public virtual string FormattedDateSold ()
{
if (DateSold == null) return "DateSold not recorded";
// Convert DateSold from DateTime? to DateTime cos DateTime? doesn't have the ToString with overload for
// formatting
DateTime date = (DateTime)DateSold;
return date.ToString("dd MMM yyyy") + " " + date.ToString("ddd");
}
public virtual string FormattedPriceSold()
{
return string.Format("{0:n}", PriceSold);
}
public virtual string FormattedPriceSoldForIndex()
{
return string.Format("{0:n}", PriceSold);
}
public virtual string FormattedQuantitySold()
{
string formattedQuantitySold = QuantitySold.ToString () + " unit";
if (QuantitySold > 1) formattedQuantitySold = formattedQuantitySold + "s";
return formattedQuantitySold;
}
public virtual string FormattedQuantitySoldForIndex()
{
string formattedQuantitySold = QuantitySold.ToString() + " unit";
if (QuantitySold > 1) formattedQuantitySold = formattedQuantitySold + "s";
return formattedQuantitySold;
}
public virtual string FormattedRemarks()
{
string result = "none";
if (!(String.IsNullOrEmpty(Remarks))) result = Remarks;
return result;
}
public virtual string FormattedProductSku()
{
return "Im a regular sale";
}
public virtual string FormattedProductSkuForIndex()
{
return "Im a regular sale";
}
public virtual string FormattedProductPartNumber()
{
return "I'm a regualr sale";
}
public virtual string FormattedStoreName()
{
return "I'm a regular sale";
}
public virtual string FormattedPaymentMethodName()
{
return "I'm a regular sale";
}
public virtual bool IsNoSale()
{
throw new NotImplementedException();
}
// Updates only those properties that are on the form
public virtual void Update(Dictionary<string, string> data)
{
DateSold = DateTime.Parse(data["DateSold"]);
CorporateSale = bool.Parse(data["CorporateSale"]);
ProductId = data["ProductId"];
PriceSold = Double.Parse(data["PriceSold"]);
QuantitySold = int.Parse(data["QuantitySold"]);
StoreId = data["StoreId"];
PaymentMethodId = data["PaymentMethodId"];
Remarks = data["Remarks"];
}
}
Server: Sale Model
[Table("sales.Sales")]
public class Sale : EntityData
{
public string PromoterId { get; set; }
public DateTime DateSold { get; set; }
[Range(1, Int32.MaxValue, ErrorMessage = "Quantity Sold must be > 0")]
public int QuantitySold { get; set; }
[Range (1, Double.MaxValue, ErrorMessage = "Price Sold must be > 0")]
public double PriceSold { get; set; }
public bool CorporateSale { get; set; }
[StringLength(255)]
public string Remarks { get; set; }
public string ProductId { get; set; }
public string StoreId { get; set; }
public string PaymentMethodId { get; set; }
public string SaleTransactionId { get; set; }
public virtual Product Product { get; set; }
public virtual Store Store { get; set; }
public virtual PaymentMethod PaymentMethod { get; set; }
[NotMapped, JsonIgnore]
public virtual Promoter Promoter { get; set; }
[NotMapped]
public string DateUploaded
{
get
{
string date = "";
if (CreatedAt != null)
{
var transformed = CreatedAt.GetValueOrDefault();
date = transformed.ToString("yyyy-MMM-dd");
}
return date;
}
set
{
}
}
[NotMapped]
public string DateSold_String
{
get
{
string date = "";
if (DateSold != null)
{
var transformed = DateSold;
date = transformed.ToString("yyyy-MMM-dd");
}
return date;
}
set
{
}
}
public override string ToString()
{
var message = "I'm a Sale! DateSold: ";
if (DateSold != null) message = message + DateSold;
else message = message + "x";
if (String.IsNullOrEmpty(ProductId)) message = message + " ProductID: " + "x";
else message = message + " ProductID: " + ProductId;
if (String.IsNullOrEmpty(StoreId)) message = message + " StoreID: " + "x";
else message = message + " StoreID: " + StoreId;
if (String.IsNullOrEmpty(PromoterId)) message = message + " PromoterID: " + "x";
else message = message + " PromoterID: " + PromoterId;
return message;
}
}
SQL Server: Sale Columns (dunno what else to show aside from the columns)
Id(PK, nvarchar(128), not null)
PromoterId(nvarchar(max), null)
DateSold(datetime, not null)
QuantitySold(int, not null)
PriceSold(float, not null)
CorporateSale(bit, not null)
Remarks(nvarchar(255), null)
ProductId(FK, nvarchar(128), null)
StoreId(FK, nvarchar(128), null)
PaymentMethodId(FK, nvarchar(128), null)
SaleTransactionId(nvarchar(max), null)
Version(timestamp, not null)
CreatedAt(datetimeoffset(7), not null)
UpdatedAt(datetimeoffset(7), null)
Deleted(bit, not null)
Upvotes: 0
Views: 681
Reputation: 6891
In my case, It seems like that using like below on the serverside using FK relationship as below doesnt work. between my client object and server object only difference is Tag property. If I remove it from the server object, update works fine. I dont know How Adrian Hall gives this example on his github sample and it works over here.
public class TodoItem : EntityData
{
public string UserId { get; set; }
public string Text { get; set; }
public bool Complete { get; set; }
public string TagId { get; set; }
[ForeignKey("TagId")]
public Tag Tag { get; set; }
}
Upvotes: 0
Reputation: 8035
Code 500 is "Invalid Server Response", which is generally "the request caused the server code to crash". To diagnose this, you need to go into the Azure portal and turn on diagnostics logging, then look at the log stream. If you are able, connect from Visual Studio via the remote debugger (check out http://aka.ms/zumobook - chapter 8 for some helpful hints on this).
From reviewing the code, I see some issues - using DateTime instead of DateTimeOffset?, for example. However, none of these should cause a crash, so I suspect a mismatch between the ASP.NET server and the SQL server in terms of model definition. However, you have not provided enough information to say this definitively.
Upvotes: 1