Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
611 views
in Technique[技术] by (71.8m points)

asp.net mvc 4 - How to upload related entities via MVC4 upshot

I get a simple DTO entity A loaded into my upshot viewmodel which is happily viewable via Knockoutjs.

My DTO A contains a List entities. So I can foreach over the elements inside A.

again:

class A
    {
       someprop;
        List<B> childB;
    }
Class B
{
   somepropB;
}

So far so good. I can iterated over the data with no problem. But if I change "someprop" inside an instance of A and SaveAll the server will not respond at all. The updateData controlle method is not even invoked. If I clear the childB.Clear() before transmitting it to the client, all is fine.

It seems the upshot is not able to update entities with collections?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

There is a bit of work to do if you want such a scenario to work. Upshot only turns the parent entities in observable items. So only the javascript representation of class A is a knockout observable, the javascript representation of class B is not. Therefore Upshot is not aware of any changes in associated objects.

The solution is to map the entities manually. To make my life easier, I've used code from my 'DeliveryTracker' sample application in the code snippets below. In my blog article you can see an example of manual mapping: http://bartjolling.blogspot.com/2012/04/building-single-page-apps-with-aspnet.html so my examples below are working on the 'delivery' and 'customer' objects.

The server-side domain model

namespace StackOverflow.q9888839.UploadRelatedEntities.Models
{
    public class Customer
    {
        [Key]
        public int CustomerId { get; set; }

        public string Name { get; set; }
        public string Address { get; set; }
        public double Latitude { get; set; }
        public double Longitude { get; set; }

        public virtual ICollection<Delivery> Deliveries { get; set; }
    }

    public class Delivery
    {
        [Key]
        public int DeliveryId { get; set; }
        public string Description { get; set; }
        public bool IsDelivered { get; set; }

        [IgnoreDataMember] //needed to break cyclic reference
        public virtual Customer Customer { get; set; }
        public virtual int CustomerId { get; set; }
    }

    public class AppDbContext : DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Delivery> Deliveries { get; set; }
    }
}

The data service controller

The data service controller exposes the data conforming to OData standards on "http://localhost:[yourport]/api/dataservice/GetCustomers". In order to be able to update both customers and deliveries you need to define an UpdateCustomer AND UpdateDelivery function

namespace StackOverflow.q9888839.UploadRelatedEntities.Controllers
{
    public class DataServiceController : DbDataController<AppDbContext>
    {
        //Service interface for Customer
        public IQueryable<Customer> GetCustomers()
        {
            return DbContext.Customers.Include("Deliveries").OrderBy(x => x.CustomerId);
        }
        public void InsertCustomer(Customer customer) { InsertEntity(customer); }
        public void UpdateCustomer(Customer customer) { UpdateEntity(customer); }
        public void DeleteCustomer(Customer customer) { DeleteEntity(customer); }

        //Service interface for Deliveries
        public void InsertDelivery(Delivery delivery) { InsertEntity(delivery); }
        public void UpdateDelivery(Delivery delivery) { UpdateEntity(delivery); }
        public void DeleteDelivery(Delivery delivery) { DeleteEntity(delivery); }
    }
}

Client-side domain model

Add a new javascript file containing your client-side model. Here I'm explicitly turning every property into an knockout observable. The key for solving your problem is the line inside the constructor of the Customer object where I'm mapping the incoming deliveries into an observable array

/// <reference path="_references.js" />
(function (window, undefined) {

    var deliveryTracker = window["deliveryTracker"] = {}; //clear namespace

    deliveryTracker.DeliveriesViewModel = function () {
        // Private
        var self = this;
        self.dataSource = upshot.dataSources.Customers;
        self.dataSource.refresh();
        self.customers = self.dataSource.getEntities();
    };

    deliveryTracker.Customer = function (data) {
        var self = this;

        self.CustomerId = ko.observable(data.CustomerId);
        self.Name = ko.observable(data.Name);
        self.Address = ko.observable(data.Address);
        self.Latitude = ko.observable(data.Latitude);
        self.Longitude = ko.observable(data.Longitude);

        self.Deliveries = ko.observableArray(ko.utils.arrayMap(data.Deliveries, function (item) {
            return new deliveryTracker.Delivery(item);
        }));

        upshot.addEntityProperties(self, "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models");
    };

    deliveryTracker.Delivery = function (data) {
        var self = this;

        self.DeliveryId = ko.observable(data.DeliveryId);
        self.CustomerId = ko.observable(data.CustomerId);
        self.Customer = ko.observable(data.Customer ? new deliveryTracker.Customer(data.Customer) : null);
        self.Description = ko.observable(data.Description);
        self.IsDelivered = ko.observable(data.IsDelivered);

        upshot.addEntityProperties(self, "Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models");
    };

    //Expose deliveryTracker to global
    window["deliveryTracker"] = deliveryTracker;
})(window);

The View

In the index.cshtml you initialize Upshot, specify custom client mapping and bind the viewmodel

@(Html.UpshotContext(bufferChanges: false)
              .DataSource<StackOverflow.q9888839.UploadRelatedEntities.Controllers.DataServiceController>(x => x.GetCustomers())
              .ClientMapping<StackOverflow.q9888839.UploadRelatedEntities.Models.Customer>("deliveryTracker.Customer")
              .ClientMapping<StackOverflow.q9888839.UploadRelatedEntities.Models.Delivery>("deliveryTracker.Delivery")
)
<script type="text/javascript">
    $(function () {
        var model = new deliveryTracker.DeliveriesViewModel();
        ko.applyBindings(model);
    });
</script>

<section>
<h3>Customers</h3>
    <ol data-bind="foreach: customers">
        <input data-bind="value: Name" />

        <ol data-bind="foreach: Deliveries">
            <li>
                <input type="checkbox" data-bind="checked: IsDelivered" >
                    <span data-bind="text: Description" /> 
                </input>
            </li>
        </ol>
    </ol>
</section>

The Results

When navigating to the index page, the list of customers and related deliveries will be loaded asynchronously. All the deliveries are grouped by customer and are pre-fixed with a checkbox that is bound to the 'IsDelivered' property of a delivery. The customer's name is editable too since it's bound to an INPUT element

I don't have enough reputation to post a screenshot so you will have to do without one

When checking or unchecking the IsDelivered checkbox now, Upshot will detect the change and post it to the DataService Controller

[{"Id":"0",
    "Operation":2,
    "Entity":{
        "__type":"Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models",
        "CustomerId":1,
        "DeliveryId":1,
        "Description":"NanoCircuit Analyzer",
        "IsDelivered":true
    },
    "OriginalEntity":{
        "__type":"Delivery:#StackOverflow.q9888839.UploadRelatedEntities.Models",
        "CustomerId":1,
        "DeliveryId":1,
        "Description":"NanoCircuit Analyzer",
        "IsDelivered":false
    }
}]

When modifying the customer's name, Upshot will submit the changes when the input box loses focus

[{
    "Id": "0",
    "Operation": 2,
    "Entity": {
        "__type": "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models",
        "Address": "Address 2",
        "CustomerId": 2,
        "Latitude": 51.229248,
        "Longitude": 4.404831,
        "Name": "Richie Rich"
    },
    "OriginalEntity": {
         "__type": "Customer:#StackOverflow.q9888839.UploadRelatedEntities.Models",
        "Address": "Address 2",
        "CustomerId": 2,
        "Latitude": 51.229248,
        "Longitude": 4.404831,
        "Name": "Rich Feynmann"
    }
}]

So if you follow above procedure, Upshot will both update parent and child entities for you.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...