Изтриване на обектна графика на Entity Framework с Breeze

Сблъсквам се с повтарящ се проблем, който просто няма смисъл, и се надявам, че някой (от екипа на Breeze?) може да хвърли малко светлина.

Следният модел илюстрира въпросните обекти.

въведете описание на изображението тук

Както можете да видите, аз се придържам доста стриктно към конвенциите на Entity Framework в моите имена на свойства и в резултат на това, ако проверя в SQL, правилата за каскада за изтриване се задават първо от EF кода, когато създава db.

Сега, когато се опитам да изтрия BusUnit ръчно в SQL, каскадите за изтриване правилно и съответните BusUnitDimensions също се изтриват, както трябва да бъде. По същия начин, ако изтрия Dimension в SQL, съответните BusUnitDimensions също се изтриват.

Въпреки това, в моето приложение, ако маркирам BusUnit като setDeleted с Breeze и след това опитам saveChanges, получавам следната грешка.

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. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.

Странно обаче, ако маркирам Dimension за изтриване и след това запазя (в рамките на Breeze), каскадното изтриване работи правилно и както Dimension, така и съответстващото му BusUnitDimensions се изтриват.

И така, защо несъответствието? Защо правилата за каскадно изтриване в SQL не се прилагат за BusUnits, но въпреки това работят за Dimensions? Четох другаде, че Breeze не поддържа каскадно изтриване, но тогава защо моят случай Dimensions работи?


РЕДАКТИРАНЕ:

Премахнах предишните си редакции, тъй като не бяха уместни. Промените по-долу следват отговора на Уорд...

Моят модел сега изглежда така и BusUnitDims сега използва BusUnitId и DimId като съставен ключ и добавих bool, IsBud за целите на полезния товар.

въведете описание на изображението тук

Все още не съм внедрил изтривания за BusUnits, но вече, ако се опитам да изтрия Dim, получавам същото съобщение за грешка:

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. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.

Забелязах, че каскадното изтриване вече не е активирано и всъщност, за да накарам EF да изгради базата данни, трябва да добавя следната конфигурация:

modelBuilder.Entity<BusUnitDim>()
    .HasRequired(bud => bud.BusUnit)
        .WithMany(bu => bu.BusUnitDims)
        .HasForeignKey(bud => bud.BusUnitId)
        .WillCascadeOnDelete(false);

modelBuilder.Entity<BusUnitDim>()
    .HasRequired(bud => bud.Dim)
        .WithMany(d => d.BusUnitDims)
        .HasForeignKey(bud => bud.DimId)
        .WillCascadeOnDelete(false);

Така че, тъй като каскадирането сега изрично не е на място, мога да разбера защо възниква грешката. Това означава ли, че в контролера трябва специално да се маркира всяка карта за изтриване, когато се изтрива родителски Dim или BusUnit и преди да се извика saveChanges, или има някакъв начин да конфигурирате EF да се възползва от каскадните изтривания, тъй като това значително би опростило код в моя контролер?

(PS: става още по-сложно, защото BusUnitDims в крайна сметка има допълнителна собствена таблица за свързване, MetricBusUnitDims за приспособяване на още един обект в модела и техните взаимоотношения. Ето защо се опитвам да разбера правилно принципите от рано)


РЕДАКТИРАНЕ: (РЕШЕНИЕ ЗА КОНТРОЛЕР ЗА BUSUNITS)

И така, следният подход работи за BusUnits:

function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
    var busUnit = busUnitVm.busUnit;
    var mapVms = busUnitVm.dimMapVms;
    var dimHash = createBusUnitDimHash(busUnit);

    mapVms.forEach(function (mapVm) {
        var map = dimHash[mapVm.dim.id];
        if (map) {
            datacontext.markDeleted(map);
        }
    });
    datacontext.markDeleted(busUnit);
    save().then(function() { getDBoardConfig(); });
    }
}

Това ли е правилният подход? ако е така, все пак ще трябва да разбера следното:

  • Как да подходим към Dims. Те са различни, тъй като моделът на изглед на елемент е дефиниран за BusUnits.
  • Как да подходим към ситуацията, в която има таблица за присъединяване едно ниво надолу, напр. MetricBusUnitDIm.

РЕДАКТИРАНЕ: (РЕШЕНИЕ ЗА КОНТРОЛЕР ЗА DIMS)

function deleteDim(dim) {
    return bsDialog.deleteDialog(dim.name, true)
        .then(function () {
            vm.busUnitVms.forEach(function (busUnitVm) {
                busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
                    if (bud.dimId === dim.id) {
                        datacontext.markDeleted(bud);
                    }
                });
            });
            datacontext.markDeleted(dim);
            save().then(function () { getDboardConfig(); });
        });
}

person zpydee    schedule 10.02.2014    source източник
comment
Конкретното искане на потребител за помощ по даден въпрос обикновено се избягва - просто обмисляне натоварване, но ако търсите конкретна подкрепа от отделни лица, обмисляли ли сте платена поддръжка?   -  person PW Kad    schedule 11.02.2014
comment
@PWKad Благодаря. Сравнително нов за SO, така че се извинявам, ако съм нарушил неписан етикет. В момента водя дискусии със съответната компания, но разликата във времето забавя инерцията.   -  person zpydee    schedule 11.02.2014


Отговори (2)


Вярвам, че вашите проблеми са проследими до факта, че вашата таблица за съпоставяне BusUnitDimension има свой собствен първичен ключ, Id, за разлика от по-типичния подход, при който свойствата BusUnitId и DimensionId FK заедно съставляват съставния първичен ключ на BusUnitDimension.

Забележете, че OrderDetails в Northwind и HeroPoweMap в примера много към много от Breeze имат съставни ключове.

Вашият избор създава усложнения.

Първо, става възможно да се създадат множество BusUnitDimension обекти, представляващи една и съща връзка между BusUnit и Dimension (т.е. всички те имат една и съща двойка FK). Базата данни може да е в състояние да предотврати това (мина много време, откакто търсих), но независимо дали го прави или не, това няма да ви попречи да създадете тези дубликати в Breeze ... а може би не и в EF.

Второ, отваря ви към проблема, пред който сте изправени в момента. Ако тези съпоставящи обекти са в DbContext, когато извършвате изтриването, EF може (очевидно го прави) да се опита да нулира техните FK свойства, тъй като задава BusUnit или Dimension в изтрито състояние.

Можете да заобиколите това, както беше предложено, като направите свойствата BusUnitId и DimensionId FK nullable. Но това е в противоречие със семантиката, тъй като BusUnitDimension трябва да свързва реално BusUnit с реално Dimension; те не са по желание. Практическата последица може да бъде, че не получавате каскадно изтриване от гледна точка на EF, ако направите това (не съм сигурен дали DB ще наложи и това). Това означава, че бихте осиротели BusUnitDimension реда във вашата база данни, като единият или и двата FK са нулеви. Предполагам, защото не съм свикнал да се забърквам в подобни проблеми.

Друг подход би бил да зададете FK стойностите им на нула (мисля, че Breeze прави това вместо вас). Разбира се, това предполага съществуването на BusUnit и Dimension редове на таблица с Id == 0, макар и само по време на операцията за изтриване.

Между другото, всъщност бихте могли да имате такива „сентинелни обекти“ във вашата DB.

Трябва да се уверите, че тези BusUnitDimension са в изтрито състояние, или EF (и DB) или ще ги отхвърли (ограничение за референтна цялост), или ще ги остави сираци (ще имате BusUnitDimension реда във вашата база данни, като единият или и двата FK са нула).

Като алтернатива, ако знаете, че DB ще ги изтрие каскадно, можете просто да ги премахнете от DbContext (премахнете от EntityInfoMap в EFContextProvider). Но сега трябва да кажете на клиента на Breeze да се отърве и от тях, ако случайно се навъртат наоколо.

Стига вече!

Тези блуждаещи мисли трябва да ви кажат, че сте се забъркали тук с твърде много счетоводство ... и всичко това, защото сте дали на BusUnitDimension свой собствен Id първичен ключ.

Става много по-лесно, ако дадете на BusUnitDimension съставния ключ, {BusUnitId, DimensionId}. Трябва също така да му дадете свойство за полезен товар (всичко ще свърши работа), за да попречите на EF да го скрие в своята реализация „много към много“, защото Breeze не се справя с това. Добавянето на всяко безсмислено свойство ще свърши работа.

HTH

person Ward    schedule 11.02.2014
comment
@AdelSal Ward, благодаря ви както винаги за изчерпателния отговор. Зает съм с почистването на кода си в резултат на този проблем и в крайна сметка се оказа нещо добро, тъй като изчистих много други дребни проблеми по пътя. със сигурност ще изпълня съвета ви и ще публикувам всички проблеми, които срещам, в интерес на тези, които може да се интересуват. След като разреша този провал, мисля, че ще имам хубаво съдържание за блог по темата :-) - person zpydee; 12.02.2014
comment
Добре, приложих тази препоръка, както я разбирам, но все още получавам това ужасно съобщение, когато се опитвам да изтрия родителски обект, така че очевидно все още правя нещо нередно. Ще публикувам допълнително съдържание като редакция. - person zpydee; 14.02.2014
comment
Сега имам изтривания от двете страни на много-много работа, а за тези, които се интересуват, добавих редакция за втората страна, тъй като е малко по-различна, ако не искате EF грешките. Трябва да кажа обаче (след този опит), че липсата на естествена поддръжка на breeze за EF много-много отношения въвежда много сложност. В моя случай, например, ако искам да изтрия dboardConfig, ще трябва да добавя логика на контролера, която първо се занимава с busUnits and dims`. И ако добавя още обекти, ще трябва да прегледам отново. imho, това е нещо, с което трябва да се справим с лекота. - person zpydee; 15.02.2014

Това няма нищо общо с Breeze.. Изходното съобщение е от Entity Framework.. в свойството BusUnitDimension Актуализация на модела BusUnitId до:

 public Nullable<int> BusUnitId { get; set; }

Обърнете внимание на структурата Nullable..

person Adel Sal    schedule 10.02.2014
comment
здрасти Това също беше първият ми инстинкт, но не мога да разбера защо каскадното изтриване работи за едната страна на 1-m-1 при изтриване на родителския обект, но не и за другата страна. Както и да е, предложеното от вас решение не работи за съжаление, защото сега, когато изтривате BusUnit, BusUnitId в таблицата BusUnitDimensions просто се нулира. Резултатът, който търся, е, че всеки запис в BusUnitDimensions, където BusUnitId = BusUnit.Id в таблицата BusUnits, трябва да бъде изтрит всеки път, когато избран BusUnit бъде изтрит. - person zpydee; 11.02.2014
comment
Ако сте активирали каскадно изтриване в базата данни. Това трябва да свърши работа. Бил съм в това преди и това е, което работи за мен. Моля, обмислете добавянето на повече подробности във въпроса си (напр. класове на модели, команди за изтриване от страна на клиента), ако искате той да бъде разрешен. - person Adel Sal; 11.02.2014
comment
между другото, ако сте приложили отговора ми към предишния ви въпрос. Изтриването на BusUnit от страна на клиента трябва да изчисти и дъщерните колекции - person Adel Sal; 11.02.2014
comment
Ще добавя допълнителна информация като допълнителна редакция. Предишният ви съвет за съжаление не изтри деца. Ако се интересувате да разгледате това в дълбочина, моля, изпратете ми имейл (вижте моя профил) и може би можем да проведем бърза сесия за програмиране на двойки онлайн. Винаги имам желание да работя с другите и да споделям своите подходи. - person zpydee; 11.02.2014
comment
разбира се, но имейлите в SO са зададени като частни. - person Adel Sal; 11.02.2014
comment
не съм сигурен как да споделя тогава, мога ли да направя това в коментар или противоречи на някакви правила на SO? Може би да се свържете с мен чрез моя уебсайт? - person zpydee; 11.02.2014
comment
Благодаря за отделеното време. Изглежда ще трябва да изчакаме и да видим дали някой друг може да хвърли светлина върху това защо EF не действа според очакванията :) - person zpydee; 11.02.2014