Recursive save not working?

Posts   
 
    
csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 07:32:09   

I have a simple entity (Question), which has a 1:1 relation with anothher entity (Answer).

If I create and save an "Answer", then set Question.Answer to the saved entity, then adapter.SaveEntity(question) works.

If I create a new Answer (not yet saved) then assign that to the Question.Answer property and try to save I get an error. I though recursive saves would handle this, but maybe I misunderstood. Am I required to save entities myself before associating them with another new entity?

The code is very simple:

    [TestMethod]
    public void BasicSaveQuestionTest()
    {
        QuestionBaseEntity questionBaseEntity = new QuestionBaseEntity();
        questionBaseEntity.Name = "MyTest";
        AnswerBaseEntity answerBaseEntity = new AnswerBaseEntity();
        answerBaseEntity.Value = new byte[1];
        questionBaseEntity.Answer = answerBaseEntity;
        DataAccessAdapter adapter = new DataAccessAdapter();
        adapter.SaveEntity(questionBaseEntity);

    }

here is the error:

Test method SerpentTests.SurveyRepositoryTest.BasicSaveQuestionTest threw exception: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException: An exception was caught during the execution of an action query: Cannot insert the value NULL into column 'AnswerBaseId', table 'Serpent.dbo.QuestionBase'; column does not allow nulls. INSERT fails. The statement has been terminated.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. ---> System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'AnswerBaseId', table 'Serpent.dbo.QuestionBase'; column does not allow nulls. INSERT fails. The statement has been terminated..

I had a lot of other problems with LLBLgen recently (crashes, etc. detailed in another post here) so now I'm not sure what problems are my fault, and what might be due to a corrupted model or something like that.

Can someone explain how basic recursive saves are supposed to work when all the objects in the graph are new?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 14-Sep-2008 10:43:42   

csmac3144 wrote:

I have a simple entity (Question), which has a 1:1 relation with anothher entity (Answer).

If I create and save an "Answer", then set Question.Answer to the saved entity, then adapter.SaveEntity(question) works.

If I create a new Answer (not yet saved) then assign that to the Question.Answer property and try to save I get an error. I though recursive saves would handle this, but maybe I misunderstood. Am I required to save entities myself before associating them with another new entity?

Recursive saves work similar to normal saves: the order is determined, and each entity is saved individually. So I don't understand why saving an Answer works, but doing it recursively doesn't.

The code is very simple:

    [TestMethod]
    public void BasicSaveQuestionTest()
    {
        QuestionBaseEntity questionBaseEntity = new QuestionBaseEntity();
        questionBaseEntity.Name = "MyTest";
        AnswerBaseEntity answerBaseEntity = new AnswerBaseEntity();
        answerBaseEntity.Value = new byte[1];
        questionBaseEntity.Answer = answerBaseEntity;
        DataAccessAdapter adapter = new DataAccessAdapter();
        adapter.SaveEntity(questionBaseEntity);

    }

here is the error:

Test method SerpentTests.SurveyRepositoryTest.BasicSaveQuestionTest threw exception: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException: An exception was caught during the execution of an action query: Cannot insert the value NULL into column 'AnswerBaseId', table 'Serpent.dbo.QuestionBase'; column does not allow nulls. INSERT fails. The statement has been terminated.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception. ---> System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'AnswerBaseId', table 'Serpent.dbo.QuestionBase'; column does not allow nulls. INSERT fails. The statement has been terminated..

Is the Answer you save here, the same as when you save it individualy? In other words: is answerBaseEntity.IsDirty true before the save in the code above? If not, answerBaseEntity isn't saved. If it's not saved, the PK value of answer isn't synced with the FK field in question. So the field in question then doesn't become 'set' and NULL is inserted into the field in Question, which results in the error.

Also, is the ID field in answer an identity field? Are you sure it is set as an identity field in the database?

I had a lot of other problems with LLBLgen recently (crashes, etc. detailed in another post here) so now I'm not sure what problems are my fault, and what might be due to a corrupted model or something like that.

I answered that question in the other thread so I think you haven't read that response, as you then would know it's not a corrupt project.

Frans Bouma | Lead developer LLBLGen Pro
csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 15:48:00   

First of all sorry for missing the answer to the other question on the other thread. Deadline looming and all that :-)

As for this problem, I am using Guid as PK in all tables, with default set to (newid())

When I save with the refetch set to True I noticed that the new Guid PK is not loaded into the entity. Here is table schema, and the test that fails on the final Assert. Is there something regarding Guids that I missed? Are these supported?

CREATE TABLE [dbo].[SurveyBase]( [SurveyBaseId] [uniqueidentifier] NOT NULL CONSTRAINT [DF_SurveyBase_SurveyBaseId] DEFAULT (newid()), [Title] nvarchar NOT NULL, [State] [int] NOT NULL CONSTRAINT [DF_SurveyBase_State] DEFAULT ((1)), [Type] [int] NOT NULL CONSTRAINT [DF_SurveyBase_Type] DEFAULT ((1)), [Version] [timestamp] NOT NULL, CONSTRAINT [PK_SurveyBase] PRIMARY KEY CLUSTERED

[TestMethod] public void BasicSaveSurveyTest() { DataAccessAdapter adapter = new DataAccessAdapter(); SurveyBaseEntity s = new SurveyBaseEntity(); s.Title = "MyTest"; adapter.SaveEntity(s, true); Assert.IsFalse(s.IsNew); Assert.IsFalse(s.IsDirty); Assert.AreNotEqual("00000000-0000-0000-0000-000000000000", s.SurveyBaseId.ToString()); }

Note that the entity "s" above did save. The PK is "47650cb3-3e63-4b36-82f5-d44e4c553909" in the database. I can retrieve it using FetchEntity. I just don't understand why the value is not present after the initial save, considering the flag to refetch is set to true.

csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 16:12:23   

Hmm...

Looks like maybe I should have read more support threads. I did look, but once I searched on "uniqueidentifier" I think I have answer.

You need to set the Guid manually before save.

Is this correct?

What are best practices around this?

Here is what I have done for now, but this does not feel very good. What is the simplest way to get this functionality as part of the generated code?

    public partial class SurveyBaseEntity
    {
        protected override void OnInitialized()
        {
            if (IsNew)
                SurveyBaseId = Guid.NewGuid();
        }
csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 17:29:56   

Well that didn't work.

OnInitialized is not the correct place to set that Guid. It was getting to a new guid with almost any interaction with the entity.

Setting it manually seems horrible.

Where do I set the value of the PK guid field so that new objects are initialized properly?

csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 17:36:35   

This seems better, but I'm still not happy overall. Do I need to use the template stuff? Has anyone created a small snippet or something which will deal with this issue?

        protected override void OnBeforeEntitySave()
        {
            if (Guid.Empty.Equals(AnswerBaseId))
                this.AnswerBaseId = Guid.NewGuid();
            base.OnBeforeEntitySave();
        }
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 14-Sep-2008 18:26:51   

csmac3144 wrote:

First of all sorry for missing the answer to the other question on the other thread. Deadline looming and all that :-)

As for this problem, I am using Guid as PK in all tables, with default set to (newid())

This is your problem simple_smile The thing is this: 'newid()' creates a new GUID, so that gets inserted. The INSERT query has to report back to the framework this new ID. This isn't possible, there's no way the just inserted GUID is retrievable like with identity fields.

Instead, if you're using sqlserver 2005, use NEWSEQUENTIALID() as default instead. It's also more efficient as it will generate sequential guid's so your clustered index isn't corrupted with each insert. See: http://www.llblgen.com/documentation/2.6/hh_goto.htm#Using%20the%20generated%20code/gencode_dbspecificfeatures.htm%23newsequentialidsupport

Another workaround is to set the ID manually. There's no other way.

Frans Bouma | Lead developer LLBLGen Pro
csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 19:46:59   

I changed everything to NEWSEQUENTIALID() and most of it works.

Am still having problems with recursive save.

Last LLBLgen project I did had none of these problems! I am forced to use Guid because this is part of a larger system which integrates with Microsoft CRM (which uses Guids for PK throughout).

I cannot understand why some of the generated code refuses to save recursively AND throws no error AND returns True from SaveEntity...

What could the problem be? Where do I look?

       var package = new QuestionPackageEntity { Title = "Test" };

        QuestionFreeformTextEntity question = new QuestionFreeformTextEntity();
        question.Name = "Test Question";
        AnswerStringEntity answer = new AnswerStringEntity();
        answer.Value = "Test Answer";
        question.Answer = answer;

        //adapter.SaveEntity(question, true); Works - 1 more ques and 1 more ans in db

        package.AddQuestion(question);

        Assert.AreEqual(1, package.Questions.Count); // works

        var result = adapter.SaveEntity(package, true);  // should recursively save question - DOES NOT SAVE!
        // but it DID save the package - new row in QuestionPackage table
        Assert.IsTrue(result);    // This passes    
csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 23:05:32   

OK so in my example above there is a m:n relationship. For some reason I thought these were handled automatically, but I guess not.

Is that the case? We need to manage many-to-many manually?

If so, how? I have a collection on QuestionPackage called Questions

there is an internal collection varialble called _questions, and I just added to that in order to add a question. When I save the join table has no rows in it.

This seems like something that should be automated, but not sure what I am doing wrong...

csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 14-Sep-2008 23:20:38   

OK so I added the record to the joining entity manually and it works now.

Still not sure if I am missing something...

If I have

QuestionPackage <--> QpQ <--> Question

do I have to add to the QpQ collection myself? Or is there a flag I'm missing so that when I add to the _questions collection in QuestionPackage this is done for me?

csmac3144
User
Posts: 74
Joined: 12-Sep-2007
# Posted on: 15-Sep-2008 01:07:09   

Well after spending most of the day on this, it seems I had toggled a relation off in the designer. I didn't realize this would stop the functionality from working.

Everything seems to work now.

One thing is for sure: don't change settings in designer unless you're sure of what you are doing :-)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39863
Joined: 17-Aug-2003
# Posted on: 15-Sep-2008 09:20:23   

Yes, sometimes things backfire on you if you don't realize the consequences wink

If you hide the FK -> PK relation side, the FK doesn't know about the PK side obviously, and therefore doesn't trigger any synchronization. I.o.w.: hte FK side will always be null.

Frans Bouma | Lead developer LLBLGen Pro