osse
osse

Reputation: 1553

Error in Updating a Row using the Mobile Client in Azure Mobile App

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:

  1. Why is the update operation resulting to an error?
  2. Are there other ways in debugging the error either in the client or server? Error 500 is very general and "Error has occurred" is not very helpful.
  3. Is it possible that the client can create a Sale and upload it into the server even if there is a discrepancy between the Sale model in the server and corresponding sql database?

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

Answers (2)

Emil
Emil

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

Adrian Hall
Adrian Hall

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

Related Questions