EF4.0 - Вмъкване на свързани обекти в база данни

Опитвам Entity Framework 4.0 и тук е най-опростената версия на случая -

Имам следните две свързани таблици -

Адрес
ID
Град

Клиент
Id
AddressId
Име

Заредих адресите в ComboBox. Всичко, което искам, е да въведа името на клиента в текстовото поле, да избера адрес от разгъващия се списък като адрес на клиента и да натисна бутона Запазване. Искам моят клиент да бъде записан в таблицата Client. Ето какво опитах -

    /*loads Addresses to the ComboBox*/
    private void LoadData()
    {
        using (CAEntities ctx = ModelAccess.GetContext())
            this.AddressList = ctx.Addresses.ToList();
    }

    /*(tries) to insert the Client to the database*/
    private void Save()
    {
        /*here this.Client is the Client object that is instantiated & initialized   
          by previous code and this.Address is the SelectedItem of the ComboBox*/
        this.Client.Address = this.Address;
        using (CAEntities ctx = ModelAccess.GetContext())
        {
            ctx.Clients.AddObject(this.Client);
            ctx.SaveChanges();
        }
    }

Джули Лерман в Programming Entity Framework казва, че ...Because the entities are joined, you can add either entity, and it will bring along the rest of the graph... Но това, което имам, е InvalidOperationException, което казва "Свойството EntityKey може да бъде зададено само когато текущата стойност на свойството е нула."
Ако използвам -

this.Client.AddressId = this.Address.Id;  

вместо -

this.Client.Address = this.Address;  

клиентът е вмъкнат перфектно в базата данни. Но мисля, че също така трябва да мога да свързвам обектите директно един с друг, нали?
Предположих, че проблемът е свързан с отделния контекст, който създавам. Така че опитах това -

    private void Save()
    {
        this.Client.Address = this.Address;
        using (CAEntities ctx = ModelAccess.GetContext())
        {
            ctx.Addresses.Attach(this.Address);
            ctx.SaveChanges();
        }
    }  

но този път получавам изключение InvalidOperationException, което казва „Обект с временна стойност на EntityKey не може да бъде прикачен към контекст на обект.“ И така, какво правя грешно тук? Благодаря предварително.


person atiyar    schedule 31.08.2012    source източник
comment
защото това, което грабвате с this.Client.Address, е стойност от разгъващия се списък, а не адресния обект. той се опитва да създаде нов адресен обект в базата данни и не можете да го присвоите на клиента, докато не бъде създаден. ето защо this.Client.AddressId работи, ще трябва да инстанциирате адреса с нещо като var Address = GetById(this.Client.AddressId и след това да зададете Client.Address = Address.   -  person Brian    schedule 31.08.2012
comment
@Brian: знам, че това, което предлагаш, работи, но не разбирам как това, което вземам от разгъващия се списък, е стойност, а не адресен обект? задавам списък с адресен обект като негов източник на данни и вземам текущия SelectedItem като this.Address и получавам свойствата за идентификатор и град от това.   -  person atiyar    schedule 31.08.2012
comment
защото това е източникът на данни. Разгъващото се поле съдържа двойка ключ стойност, а не обект. Така че, като присвоите тази двойка ключ стойност като адресен обект обратно на клиента, вие казвате на контекста на db да създаде нов адрес в db, но той трябва да съществува, за да го запише на клиента. Това е мястото, откъдето идва съобщението за временния ключ на обекта.   -  person Brian    schedule 31.08.2012
comment
@Brian: наистина не те разбрах. от xaml зададох обвързване на свойството SelectedItem на combobox към този адрес в моя ViewModel. така че когато SelectedItem се променя, не трябва ли да получавам адресен обект? SelectedItem, както е споменато тук msdn.microsoft.com/en-us/library/, връща System.Object и можете да го преобразувате към персонализирания си обект, прав ли съм?   -  person atiyar    schedule 31.08.2012
comment
До известна степен сте, но имайте предвид какво е combobox и какво ще върне и носителя, където го използвате (страницата за преглед). Той просто ще предаде обратно двойка ключ стойност от падащия списък. Това е, с което трябва да работите.   -  person Brian    schedule 31.08.2012
comment
@Brian: на същата страница ли сме, че е в WPF? защото звучиш повече като asp.net :P   -  person atiyar    schedule 31.08.2012


Отговори (1)


Това трябва да реши проблема:

    using (CAEntities ctx = ModelAccess.GetContext())
    {
        ctx.Addresses.Attach(this.Address);
        this.Client.Address = this.Address;
        ctx.Clients.AddObject(this.Client);
        ctx.SaveChanges();
    }

Защо това работи?

DbContext следи обектите, които е изтеглил или които са изрично прикачени към него. Във вашия случай вие задавахте this.Client.Address на обект, за който DbContext не знаеше. В някои случаи това ще накара рамката на обекта да вмъкне както нов клиентски ред и нов адресен ред, но във вашия случай, поради някои семантични подробности, в които не съм запознат, това хвърли неясен изключение.

Като прикачите this.Address към DbContext, рамката на обекта разбира, че адресът вече съществува в базата данни и че създавате асоциация със съществуващ обект, когато го присвоявате на this.Client.Address.

IMO, семантиката на Entity Framework трябва да бъде по-добре документирана. Има много случаи като тези, при които е хвърлено подвеждащо или неясно изключение. Например, когато прикачвате обекти, важно е да имате предвид, че те се прикачват рекурсивно. Ако трябваше да прикачите this.Client в нов контекст на данни, той рекурсивно ще прикачи и this.Client.Address, както и всички други обекти this.Client, които може да препращат. Поради тази причина, ако прикачите изрично this.Client и след това this.Address, ще получите изключение, тъй като адресът е бил неявно прикачен, когато сте прикачилиthis.Client.

person w.brian    schedule 31.08.2012
comment
благодаря МНОГО Брайън, реши проблема :D но можеш ли да разясниш малко какво точно се обърка, когато задам this.Client.Address = this.Address; преди да прикача адреса. - person atiyar; 31.08.2012
comment
знам, че ObjectServices следи само EntityObjects, за които знае. в моя случай само обектът Address. и тъй като клиентският обект е създаден в рамките на кода, контекстът не е наясно с него. но когато присвоя this.Client.Address = this.Address; не създавам ли пълна обектна графика, свързваща обектите един с друг? и след това, когато прикача обекта Address, не трябва ли контекстът да осъзнава и обекта Client, тъй като сега е свързан с обекта Address? така че все още не получавам това ...temporary EntityKey value... изключение. както и да е, благодаря ти отново. - person atiyar; 01.09.2012
comment
и аз НАПЪЛНО съм съгласен с вашето better documentation мнение. Виждам много хора, които използват EF, но много малко от тях имат представа какво се случва под капака. - person atiyar; 01.09.2012
comment
Ако искате да видите как DbContext вижда обекта, предайте обекта на метода dataContext.Entry() и проверете неговото EntityState. Ако е Detached, тогава DbContext не знае за него, дори ако първоначално е бил изтеглен от предишен DbContext. На DbContext трябва да се каже чрез прикачване на обекта, Хей, този обект вече съществува в базата данни. По този начин, когато записва промените, DbContext знае, че обектът This Address вече съществува в базата данни, няма нужда да създавате нов, преди да вмъкнете реда Client. - person w.brian; 01.09.2012