0

I am looking at Azure Mobile App and have setup a test service and client.

I've setup the following Entities service-side:

public class Hotel : EntityData
{
    public string Title { get; set; }

    public virtual ICollection<Booking> Bookings { get; set; }
}

public class Booking : EntityData
{
    public BookingStatus BookingStatus { get; set; }

    [ForeignKey("PersonId")]
    public virtual Person Person { get; set; }

    [ForeignKey("HotelId")]
    public virtual Hotel Hotel { get; set; }

    public string PersonId { get; set; }

    public string HotelId { get; set; }
}

public class Person : EntityData
{
    public string Name { get; set; }

    public virtual ICollection<Booking> Bookings { get; set; }
}

And the controller:

public class BookingController : TableController<Booking>
{
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
        MobileServiceContext context = new MobileServiceContext();
        DomainManager = new EntityDomainManager<Booking>(context, Request);
    }

    // GET tables/Booking/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public SingleResult<Booking> GetBooking(string id)
    {
        return Lookup(id);
    }

    // GET tables/Booking
    public IQueryable<Booking> GetAllBookings()
    {
        return Query();
    }

    // PATCH tables/Booking/48D68C86-6EA6-4C25-AA33-223FC9A27959
    public Task<Booking> PatchBooking(string id, Delta<Booking> patch)
    {
        return UpdateAsync(id, patch);
    }
}

I have added some default data using CreateDatabaseIfNotExists<MobileServiceContext> and when I startup and test the Web API, the DB gets populated and I am happy that the Keys/Relationships are setup correctly. I am just using the Code First convention naming (as per this tutorial)

I have also created a test client with the following Entities:

public class Person
{
    public string Id { get; set; }
    public byte[] Version { get; set; }

    public string Name { get; set; }
    public virtual ICollection<Booking> Bookings { get; set; }
}

public class Booking
{
    public string Id { get; set; }
    public byte[] Version { get; set; }

    public BookingStatus BookingStatus { get; set; }
    public string PersonId { get; set; }
    public string HotelId { get; set; }        
    public virtual Person Person { get; set; }
    public virtual Hotel Hotel { get; set; }
}

public class Hotel
{
    public string Id { get; set; }
    public byte[] Version { get; set; }

    public string Title { get; set; }
    public virtual ICollection<Booking> Bookings { get; set; }
}

And with this test logic:

using (var client = new MobileServiceClient(m_Url, new ODataParameterHandler())
{
    client.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
    client.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

    var bookingTable = client.GetTable<Booking>();
    var bookings = await placementTable
                .Where(p => p.BookingStatus == BookingStatus.Confirmed && p.PersonId == 10)
                .WithParameters(new Dictionary<string, string> { { "expand", "Hotel" } })
                .ToListAsync();

    var aBooking = bookings[0];     
    aBooking.BookingStatus = BookingStatus.Cancelled;

    await bookingTable.UpdateAsync(aBooking);
}

// Class to allow $expand= querystring value to be passed in.
public class ODataParameterHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        UriBuilder builder = new UriBuilder(request.RequestUri);

        builder.Query = builder.Query
            .Replace("expand", "$expand")
            .TrimStart('?');

        request.RequestUri = builder.Uri;

        return await base.SendAsync(request, cancellationToken);
    }
}

The GET/ToListAsync works ok and I get the child Hotel object attached to my Booking. However, the Update fails with:

The operation failed due to a conflict: 'Violation of PRIMARY KEY constraint 'PK_dbo.Hotels'. Cannot insert duplicate key in object 'dbo.Hotels'. The duplicate key value is (0e6e1bae-bd59-46ac-9630-a2b53dd04a90).\r\nThe statement has been terminated.

But why on earth is it attemping to INSERT my child object again? Firstly, I haven't altered it, and secondly, it has an Id, CreatedAt etc.

I cannot find any similar issues regarding Azure Mobile Apps, but I did find this SO Post regarding Entity Framework but the OP talks about having manually created the children, so I am not sure it fully applies as I have fetched the child Entity from the DB through the TableController.

Community
  • 1
  • 1
oatsoda
  • 2,088
  • 2
  • 26
  • 49
  • I'm not familiar with Azure Mobile, but it looks like some issue with the entities not having the right `Entry.State` in the context, the kind of problems you get in disconnected scenarios. It looks like the most voted answer in the SO post you link would fix your problem. Try to set your `aBooking.Hotel` navigation property child to null before doing the `UpdateAsync` and leave only the `HotelId` value. – Diana Nov 18 '16 at 21:44

1 Answers1

0

Azure Mobile Apps does not support relationships. You are bumping into one of the many issues that go along with that.

If you are using offline sync, then decompose the tables so that the linkage is less required, then sync each table individually.

If you are not using offline sync, use a custom API to commit the change to the database.

Adrian Hall
  • 7,990
  • 1
  • 18
  • 26
  • I'd like also to draw your attention to [this Microsoft blog post about 1:n relationships using Mobile](https://blogs.msdn.microsoft.com/azuremobile/2014/06/18/insertupdate-data-with-1n-relationship-using-net-backend-azure-mobile-services/). – oatsoda Nov 21 '16 at 08:21
  • That blog post is about Azure Mobile Services, which is the predecessor for Azure Mobile Apps. We have never supported relationships on Azure Mobile Apps. – Adrian Hall Nov 21 '16 at 10:26
  • Ah, ok. When you say "decompose the tables so that the linkage is less required" are you advocating that the Entity models should not have defined relationships (in a pseudo No-SQL manner), or do you mean denormalize and remove relationships? – oatsoda Nov 21 '16 at 15:47
  • Either one will work. In my applications, I create two models that nominally have a relationship - instead of adding a ModelB property in ModelA, I add a string ModelBId in ModelA - this delinks the two models, but still allows me to refer to the ModelB from ModelA. In many:many and 1:many relationships, I have an intermediary model that has the join. – Adrian Hall Nov 26 '16 at 01:31
  • Ok, makes sense. Presumably though, this means you cannot easily model "required" relationships? or requirements such as "once linked, cannot change"? – oatsoda Nov 30 '16 at 09:12
  • Correct. Relationships need to be loose with Azure Mobile Apps. Required relationships is a strict relationship and breaks the model. – Adrian Hall Nov 30 '16 at 20:15
  • @AdrianHall I am having a hard time with this very thing. Could you provide an example of how to do Either? I spent the last week or so trying to combine the information in the Contosouniversity application with the todoItems only to now realise that it simply isn't going to work. Is there anywhere I can see an example of the implementation of either of your recommendations? – xerotolerant Dec 11 '16 at 08:42
  • My book and blog have examples. Book is at http://aka.ms/zumobook - blog is at https://shellmonger.com – Adrian Hall Dec 13 '16 at 18:01