0

I have implemented domain models using Breeze's recommended one-many-one approach, and would like to assign these relationship using a checkbox approach. I'm using Angular for my data-binding, so this would be via the ngChecked directive.

I was wondering if anyone has attempted something similar and is able to post code snippets for getting, creating and deleting the one-many-one relationship, both at a controller and data service level.

I'm about to start on the requirement, and will gladly post my example as response to my question for those interested.

I've scanned through the Breeze samples, but couldn't find one fulfilling this requirement.

Many thanks!

Adding on some of my markup and scripts for this question, as I just cannot make sense of how I'm meant to approach this. Just to explain my use-case a bit. I provide users the capability at the top ofa page to create a list of "channels". Users can then also create a number of "business units". For each business unit, all channels get displayed and users can select which channels apply for each business unit.

Markup...

...
<div class="form-group"> /* portion of markup for adding channel */
    <label class="control-label">New Interaction Channel</label>
    <input class="form-control" placeholder="Channel Name..." data-ng-model="vm.newChannel.name" />
</div>
<div class="form-group">
    <button class="btn btn-primary" data-ng-disabled="!vm.newChannel.name"
            data-ng-click="vm.addChannel(vm.newChannel, $parent.vm.dboardConfig)">
        Add Channel</button>
....
<accordion>
    <accordion-group data-ng-repeat="bu in vm.busUnits"> /* business units listed in accordion groups */
        <accordion-heading>
            {{bu.name}}
            ...
            </accordion-heading>
                <h5>Channels</h5>
                <div class="well well-sm panel-body">
                     /* this is where I start getting stuck. */
                     /* just not sure how to allocate the item viewmodel from */
                     /* Ward's example in my scenario */
                    <div data-ng-repeat="buc in bu.buChannels">
                        <div class="col-md-6">
                            <input type="checkbox" data-ng-checked="?????"
                                   data-ng-model="buc.isSelected"/>
                            {{buc.name}}
                        </div>
                        ...

and in my controller...

function getBusUnits() {
    ...
    .then(function(data){
        vm.busUnits = data;
        vm.busUnits.forEach(function(bu){
            getBusUnitChannels(bu);
        });
    });
}

function getBusUnitChannels(busUnit) {
    datacontextSvc.dboardConfigs.getBusUnitChannelsById(busUnit.id)
        .then(function (data) {
            busUnit.busUnitChannelList = data;
        });

    busUnit.buChannels = [];
    vm.channels.forEach(function (channel) {
        busUnit.buChannels.push(channel);
        // how do I assign the isSelected for each buChannel?
        // how do I associate each buChannel with the BusUnitChannel obtained via breeze?
    });

Am I going even vaguely in the right direction? I haven't event dealt with saving back to the server yet, I'd just like to be able to populate my lists first :-)

zpydee
  • 4,487
  • 4
  • 16
  • 19
  • Please show some code on what you have tried so far and what is not working. – PW Kad Jan 31 '14 at 14:34
  • Ward's accepted answer to [this SO question](http://stackoverflow.com/questions/20638851/breeze-many-to-many-issues-when-saving) provides some excellent guidance. – Steve Schmitt Jan 31 '14 at 16:28
  • Having studied Ward's post, I am still struggling applying this to my requirement. Not sure if this is because I have nested elements, but I cannot understand how to apply the item viewmodel logic in my case. I think I'll post some of my mark-up and js to see if this helps someone give me guidance. – zpydee Feb 01 '14 at 11:13

2 Answers2

2

I've written a plunker to demonstrate the many-to-many checkbox technique I described previously. I'm hopeful it provides the essential insights for your case.

Ward
  • 17,793
  • 4
  • 37
  • 53
  • PS: I'm a huge comics fan, so even the example is well positioned :-) – zpydee Feb 03 '14 at 15:39
  • A question. If I'm using an accordion for my heros instead a drop-down, can I simply apply the ng-model="vm.currentHeroVm" to the accordion group? – zpydee Feb 04 '14 at 14:04
  • Hi Ward, First, thank you again for posting this Plunker. It has been immensely helpful, and it's almost working 100%. The only issue I have is that after deleting an existing HeroPowerMap, if I try load from cache, the power still appear checked, but yet I'm getting mull entity issues. I suspect this has to do with my datacontext. I've applied much of what John Papa describes in his latest 2 courses. One of the ways I thought to resolve this would be to get the data separately instead of using expand. If I do this, would I still be able to refer to hero.powerMap owing to metadata? – zpydee Feb 04 '14 at 18:07
  • Pls disregard my previous comment - got it working. So grateful for your code. I would never have achieved this result by myself, and I've learned a huge amount! – zpydee Feb 04 '14 at 18:27
  • 1
    Do you have a plunker fork so I can see your accordion. I'm curious. P.S.: I added animation of the message panel today for fun. – Ward Feb 05 '14 at 04:36
  • Sure will do. By the way, I ws wrong. I still have the issue with reloading HeroPowerVms from local storage, but I think I'll post that question with a Hottowel tag. I'll include a breeze tag as well though so that its on your radar... – zpydee Feb 05 '14 at 10:31
  • Can't get the Angular bootstrap directives working in my plunker. not sure why, i've got the CDN. Anyway, i'll let you know when its up. – zpydee Feb 05 '14 at 11:14
  • I'm encountering another complication now. When I try to delete a hero, I get the following EF error message - "Failed to save changes to server. The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value.... (etc.)" I've tried to use the same logic as you do before saving to trap and delete the map in the same transaction, but to no avail. – zpydee Feb 05 '14 at 16:07
  • 1
    Not sure of your specifics, particularly how you modeled in EF. Not even sure if it is really coming from EF or something else. You certainly need to be sure that you delete all the power maps **at the same time and in the same transaction** that you delete the hero. Have I told you how much I hate deletion? – Ward Feb 05 '14 at 20:00
  • I hear ya. Starting to hate it myself. – zpydee Feb 06 '14 at 07:11
  • Figured out the solution to this one and thought you'd be interested as it potentially has breeze implications. To succeed you first have to delete the maps, then save, and only then delete the hero and save again. It appears that if you try mark all for deletion and perform a single save, EF doesn't like it. – zpydee Feb 06 '14 at 08:31
  • 1
    Really? Really? I'm having a hard time believing that. For example, the `OrderDetails` class in the Northwind database is essentially the same as the `HeroPowerMap`; it maps `Order` (hero) to `Product` (power). I know for certain that I can delete an `Order` and its `OrderDetails` in the same transaction. If you can't do the same with `Hero` and `HeroPowerMap` I suspect something amiss with your EF mapping or you aren't including every one of the Hero's powerMaps in your delete request. – Ward Feb 07 '14 at 07:16
  • Perhaps it has to do with the 1-m-1 as opposed to just a 1-m. Initially I tried all the fluent API configs, providing for cascased delete, but that didn't work. All I know is that if I do the two stage delete, it works and without it fails. I'm no expert though, so the fault could be mine, but the pocos are so simple so... – zpydee Feb 07 '14 at 08:23
  • 1
    Order-OrderDetail-Product is 1-M-1. "The fault is not in the stars but in ourselves". I do not like the two step dance. In principle, the power maps should be inaccessible on their own but only through the hero and they should be deleted with the hero in a single transaction. – Ward Feb 07 '14 at 18:27
  • nicely put. i hear ya, but in the interest of keeping momentum, i've put this in the parking lot for refactoring whilst I finish my module. Gotta love deadlines, especially the "whooshing" sound they make as they fly past. – zpydee Feb 08 '14 at 08:46
  • well, i've tried everything I can think of to get deletions to work but not winning. please reply to my LinkedIn mail so we can pursue that conversation... – zpydee Feb 09 '14 at 15:31
  • Would eager-loading the maps at the same time as heros change this implementation at all? – zpydee Feb 11 '14 at 11:20
  • 1
    The question makes me wonder if you had the maps on the client side at the time that you were deleting the `Hero` (or its equivalent). Because you do not cascade delete (which scares me anyway), you either have to load them on the client before deleting or handle deletion on the server. I prefer the latter if possible, perhaps even with a stored proc. But first pass (not thinking of efficiency), I'd get them to the client and then delete the bunch. Whether loaded with the `Hero` or as part of delete process is your choice. Btw, are you using EF on the backend? #worriedAboutSaveOrder – Ward Feb 11 '14 at 17:17
  • I'm systematically going through a refactoring exercise and rebuilding the requirement one step at a time now that I have some knowledge of what I want to avoid upfront. Yes, EF on the backend (which is behaving very oddly) – zpydee Feb 11 '14 at 17:55
  • 1
    Be sure to look at my answer to your related question: http://stackoverflow.com/questions/21679073/entity-framework-object-graph-deletion-with-breeze – Ward Feb 12 '14 at 01:26
  • Ward, would it be correct to say that your solution in this Plunker is not something that's necessitated by Breeze, but rather because one simply has to display all potential powers for each hero regardless of whether each power is selected or not, and if one had to implement another data layer (e.g. simple $resource), the same approach would be required? – zpydee Mar 18 '14 at 07:59
0

Ok, this is the solution I've come up with. I'd really appreciate some thoughts on my approach and whether or not its the most efficient (or even correct) approach..

Markup...

...
<div class="form-group"> /* portion of markup for adding channel */
    <label class="control-label">New Interaction Channel</label>
    <input class="form-control" placeholder="Channel Name..." data-ng-model="vm.newChannel.name" />
</div>
<div class="form-group">
    <button class="btn btn-primary" data-ng-disabled="!vm.newChannel.name"
            data-ng-click="vm.addChannel(vm.newChannel, $parent.vm.dboardConfig)">
        Add Channel</button>
....
<accordion>
    <accordion-group data-ng-repeat="bu in vm.busUnits"> /* business units listed in accordion groups */
    <accordion-heading>
        {{bu.name}}
        ...
        </accordion-heading>
            <h5>Channels</h5>
            <div class="well well-sm panel-body">
                 <div data-ng-repeat="buc in bu.buChannels">
                    <div class="col-md-6">
                        <input type="checkbox" data-ng-model="buc.isSelected"/>
                        {{buc.name}}
                    </div>
                    ...

and in my controller...

function getBusUnits() {
    ...
    .then(function(data){
        vm.busUnits = data;
        vm.busUnits.forEach(function(bu){
            getBusUnitChannels(bu);
        });
    });
}

function getBusUnitChannels(busUnit) {
        datacontextSvc.dboardConfigs.getBusUnitChannelsById(busUnit.id)
            .then(function (data) {
                busUnit.busUnitChannelsList = data;

                busUnit.buChannels = [];
                vm.channels.forEach(function (channel) {
                    busUnit.buChannels.push(channel);
                });

                busUnit.busUnitChannelsList.forEach(function (buc) {
                    busUnit.buChannels.forEach(function (buCh) {
                        if (buc.channelId === buCh.id) {
                            buCh.buChannel = buc;
                            buCh.isSelected = true;
                        } else {
                            buCh.isSelected = false;
                        }
                    });
                });
            });
    }
zpydee
  • 4,487
  • 4
  • 16
  • 19