views:

375

answers:

3

I'm having trouble saving a record in Subsonic 3 using Active record. I've generated my objects using the DALs and tts and everything seems fine because the following test passes. I think that my connection string is correct or the generation wouldn't have succeeded.

[Test]
        public void TestSavingAnEmail()
        {
            Email testEmail = new Email();
            testEmail.EmailAddress = "[email protected]";
            testEmail.Subscribed = true;
            testEmail.Save();
            Assert.AreEqual(1, Email.All().Count());
        }

On the live side, the following code fails:

protected void btEmailSubmit_Click(object sender, EventArgs e)
        {
            Email email = new Email();
            email.EmailAddress = txtEmail.Text;
            email.Subscribed = chkSubscribe.Checked;
            email.Save();
        }

with a message of: Need to specify Values or a Select query to insert - can't go on! at the following line repo.Add(this,provider); line in my ActiveRecord.cs:

public void Add(IDataProvider provider){


            var key=KeyValue();
            if(key==null){
                var newKey=_repo.Add(this,provider);
                this.SetKeyValue(newKey);
            }else{
                _repo.Add(this,provider);
            }
            SetIsNew(false);
            OnSaved();
        }

Am I doing something horribly wrong here? The save and add methods have parameterless overloads that I thought were safe to use. Do I need to pass a provider? I've googled around for this for a while and was unable to come up with anything specific to my situation. Thanks in advance for any kind of answer.

The schema for the table is:

USE [xxxx]
GO
/****** Object:  Table [dbo].[Emails]    Script Date: 03/11/2010 13:15:08 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Emails](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [V_EmailAddress] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [B_Subscribed] [bit] NOT NULL,
    [DT_CreatedOn] [datetime] NOT NULL,
    [DT_ModifiedOn] [datetime] NOT NULL,
 CONSTRAINT [PK_Emails] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

There is only 1 warning during generation and that is

Multiple template directives were found in the template. All but the first one will be ignored. Multiple parameters to the template directive should be specified within one template directive.

Settings.ttinclude

A: 

This should generally work.

A few possible points of failure come to my mind:

  • Are you using a standard MSSQL provider (database)?
  • Did you provide a connection string in the web.config (website) or app.config (class library project)?
  • Did you set a primary key column in the database?
  • Is your table using multiple primary key columns? Subsonic can't handle that. Use a single artificial ID column (uniqueidentifier or int) in that case.
  • If the primary key value is an integer field: does it auto-increment the id values? Otherwise you'd have to set the primary key value on your email object before saving it.
SimonW
pagetailor,Thanks for your answers. I'm using a SqlServer 2005 DB instance. I have a solution with a class library with the connection string that generates the Object Models and I reference that DLL in a Web Application project that has the same connection string set in a web.config.I didn't have a primary key on that table (the rest have them) so I recreated the table migration with a primary key and the models and I still get the same error.Only 1 PK,int type. It is an identity and should auto-increment. Attempts to set the id on the object before the save result in the same error.
singfoom
Here's the relevant part of the stack trace:" at SubSonic.SqlGeneration.Sql2005Generator.BuildInsertStatement()\r\n at SubSonic.Query.Insert.BuildSqlStatement()\r\n at SubSonic.Query.Insert.GetCommand()\r\n at SubSonic.Repository.SubSonicRepository`1.Add(T item, IDataProvider provider)\r\n
singfoom
Phew, honestly I am overstrained then. This is exactly how I do it with SqlServer2008 and that works just fine. Hopefully someone else can help you - sorry.Besides: If your PK ist an identity, you don't even have to set the ID on the object before saving it. The field is updated by SubSonic automatically once the object has been saved to the DB.
SimonW
+2  A: 

The select error you're seeing is SubSonic trying to pull out the newly-created PK, and it can't. So, be sure you have a Primary Key defined for your table. Next - make sure it's set to Auto Increment :).

If that doesn't do it - kick up SQL Profiler and see what's happening. Also - if you could put the schema of your table here so I could see it, that would be helpful (just edit your message).

Rob Conery
Rob,Thanks for your reply. I do have a primary key defined for the table and it is an identity and therefore set to auto-increment. The schema has been added to the above message. I'm still receiving the error. Maybe it's because the primary key is clustered? I'll try to monkey with it and see if that's the issue.
singfoom
Rob,I've been unable to get any useful information out of SQL Profiler. Using the standard template I don't see any hits when I try to run the test hitting the real DB. I ran the custom tool on the templates again and saw all of those transactions. I'm pretty sure the table is set up correctly, but when I try to save the object, the key that comes back is 0. I traced it up to the _repo.Add(this,provider) and the value of the key variable is 0 as returned by KeyValue();. The table is set up to start incrementing at 1, so maybe generation didn't work correctly as I thought?
singfoom
The value of the key that gets returned is always 0. I tried inserting a row manually and that had an Id of 2. So the next time I run the test it should get a 3. But the id returns as 0. It seems like the DB isn't even being hit....
singfoom
What collation are you running under? Also - flip open your query tool and manually run an INSERT into that table. Then, add the following lines and tell me what happens:SELECT SCOPE_IDENTITY as new_idAlso - grab the latest templates up at Github and see if this fixes it.
Rob Conery
Rob,Collation for that database is SQL_Latin1_General_CP1_CI_AS. I added a couple records manually, ,last was given the ID 5 and the return of SELECT NEWID = SCOPE_IDENTITY() returned 5 as well. I'll check out the github templates.
singfoom
I grabbed the templates from the template example project on github and now I've got a compilation issue with GeneratedTextTransform in Structs.tt and an error in the SQLServer.ttinclude because it can't find CleanUp. Am I using the right templates?
singfoom
There seems to be a mismatch between the SQLServer.ttinclude and Settings.ttinclude. SQLServer.ttinclude uses a Cleanup method while Settings provides a CleanUpColumnName and CleanUpTableName.
singfoom
Rob,I removed all traces of Subsonic 3 from my project. Then I re-added the DLL and then the template files from the Subsonic 3 Final from the website. When I generate the code files without modifying the cleanup method (I have to deal with type prefixing and I hate it in my models), everything works perfectly. If I add all the rules to cleanup my columns, I get the error I've been dealing with. If I change just one type, say "V_", "", I get a SQL error yelling about the V_EmailAddress not allowing nulls. I'm not sure what's going on here, but I'm going to stick dealing with prefixes.
singfoom
Ahhhh - that's the problem. It's a naming collision. It sounds like a recent commit borked our t4's - I need to go in there and fix them I think and I can't get to that for a week or so.I'm sorry you have to deal with the prefixes - I'll get this worked on as soon as I can.
Rob Conery
A: 

singfoom,

Are you still having this problem? If not, what did you do to solve? I downloaded the latest templates from Git and I still have the issue. Same as you, I get 0 back as the new KeyValue every time. After a bit of debugging I was able to trace where the problem occurs. It happens when SS is saving the new record (in the Add() method). A test is done using the value returned from the KeyValue() method something like this: if(keyValue==null) {generate new key} else { use existing value }. The class generated for your table when you run SS makes the primary key field a non nullable int type variable which means that the keyValue tested during the Add method will NEVER be null and SS won't try to insert a new row and get the generated key back from the DB (which is why you're not seeing anything in SQL Profiler). Now... knowing all that, I still can't fix this because I don't know how code generation works in Visual Studio. : ( I've tried contacting Rob, but he seems to have gone silent sadly.

Can anyone tell me if this is a bug or if I just have my logic goofed somewhere?

Mike, I just dealt with the nastiness of Type Prefixing. If I didn't rename anything, everything generated just fine. Rob seemed to think it was a bug but he didn't say he'd be getting to it anytime soon.
singfoom