CodeSmith Community
Your Code. Your Way. Faster!

Join Table Issue

rated by 0 users
Answered (Verified) This post has 1 verified answer | 25 Replies | 2 Followers

Top 100 Contributor
54 Posts
Points 1,195
codrsmith posted on Thu, Jan 21 2010 10:15 AM

Please tell me what is the expected form of my database tables for a correct generation/work of NHibernate.

Actually I have entity datatables with:

- id

- foreign keys ids

- fields

and some join datatables:

- foreign keys ids

The problem seems if i don't create an id column for the join datatables, else their name appear nowhere in the generated code...

Also if my entity datatable has no field, it is not generated. This can be avoided with adding an extended property "cs_ManyToMany" with a value being "False" or by adding a field (nullable bit typed for smaller memory usage).

My question is: how are my datatables supposed to be so that the generated code works correctly (meaning the join datatables are filled (inserts) automatically when i save a entity object refering to it?

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35

Answered (Verified) Verified Answer

Top 50 Contributor
118 Posts
Points 3,545
Verified by blake05

Hello,

Yes, that sounds right. Smile
http://ayende.com/Blog/archive/2006/12/02/NHibernateCascadesTheDifferentBetweenAllAlldeleteorphansAndSaveupdate.aspx

Thanks,
Tom 

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 5

All Replies

Top 50 Contributor
118 Posts
Points 3,545

Hello,

The templates will not generate an entity for tables identified as being a many-to-many join table. This is because NHibernate does not require entities to manage the join rows.
If you have two tables, for example User and Role, and they are joined by UserRole, only the User and Role entities will be generated. If you add a Role object to the Role collection on the User object, NHibernate should add a row to the UserROle table automatically.

Thanks,
Tom

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 35
Top 100 Contributor
54 Posts
Points 1,195

I understood this ... but the problem is: it is not working.

So I try to understand why. Can you give me a sample with 2 entities tables "A" and "B" and 1 join table "ABs" that, when generated with codesmith nhibernate template, works?

I need :

- DB creation script

- codesmith C# code to create objects A and B

- code to put in A's list of Bs the B

- code to save the A so that it automatically also save the B and a "link" row in tale ABs

Actually I was unsuccessfull to make this work.

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 5
Top 100 Contributor
54 Posts
Points 1,195

Here's my actual test sample doing this.

1- the database

 

IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = N'accessrights')

EXEC sys.sp_executesql N'CREATE SCHEMA [accessrights] AUTHORIZATION [myadminuser]'

 

GO

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[accessrights].Beer') AND type in (N'U'))

BEGIN

CREATE TABLE [accessrights].Beer(

[Bid] [int] IDENTITY(1,1) NOT NULL,

[name] [varchar](10) NULL,

 CONSTRAINT [PK_B] PRIMARY KEY CLUSTERED 

(

[Bid] ASC

)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

END

GO

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[accessrights].Angel') AND type in (N'U'))

BEGIN

CREATE TABLE [accessrights].Angel(

[Aid] [int] IDENTITY(1,1) NOT NULL,

[name] [varchar](10) NULL,

 CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED 

(

[Aid] ASC

)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

END

GO

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[accessrights].[ABs]') AND type in (N'U'))

BEGIN

CREATE TABLE [accessrights].[ABs](

[Aid] [int] NOT NULL,

[Bid] [int] NOT NULL

) ON [PRIMARY]

END

GO

IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[accessrights].[FK_AB_A]') AND parent_object_id = OBJECT_ID(N'[accessrights].[ABs]'))

ALTER TABLE [accessrights].[ABs]  WITH CHECK ADD  CONSTRAINT [FK_AB_A] FOREIGN KEY([Aid])

REFERENCES [accessrights].Angel ([Aid])

GO

IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[accessrights].[FK_AB_B]') AND parent_object_id = OBJECT_ID(N'[accessrights].[ABs]'))

ALTER TABLE [accessrights].[ABs]  WITH CHECK ADD  CONSTRAINT [FK_AB_B] FOREIGN KEY([Bid])

REFERENCES [accessrights].Beer ([Bid])

2- my hibernate.cfg.xml (set in copy always) :

 

<?xml version='1.0' encoding='utf-8'?>

<configuration>

<configSections>

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />

</configSections>

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

<session-factory>

<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>

<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>

<property name="connection.connection_string">Data Source=mydatawsource;Initial Catalog=mycatalog;Persist Security Info=True;User ID=myadminuser;Password=mypassword</property>

<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>

<property name="show_sql">false</property>

<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>

<property name="use_outer_join">true</property>

<mapping assembly="ConsoleApplication1" />

</session-factory>

</hibernate-configuration>

<log4net>

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">

<layout type="log4net.Layout.PatternLayout, log4net">

<param name="ConversionPattern" value="%m" />

</layout>

</appender>

<root>

<priority value="WARN" />

<appender-ref ref="ConsoleAppender" />

</root>

</log4net>

</configuration>

3- the Program.cs file of my sample ConsoleApplication1 :
using System;
using ConsoleApplication1.BusinessObjects;
using ConsoleApplication1.ManagerObjects;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IManagerFactory mgrFactory = new ManagerFactory();
            IAManager aMgr = mgrFactory.GetAManager();
            IBManager bMgr = mgrFactory.GetBManager();
            A a = new A();
            a.Name = "a object";
            B b = new B();
            b.Name = "b object";
            a.Bs.Add(b);
            aMgr.Save(a);
            Console.WriteLine("complete");
            Console.ReadKey();
        }
    }
}
Problems..... when I save A, it saves B also.... but nothing in ABs table.

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 5
Top 100 Contributor
54 Posts
Points 1,195

I tried anything I could:

- turning on/off lazy

- playing with inverse option

- specifying full class name

- specifying the join table ("ABs") instead of entities ones (A or B) in the bag definition

- adding generic to the bag definition

 

Nothing worked... I hope you have a solution for me.

 

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35
Top 50 Contributor
118 Posts
Points 3,545

Hello,

Please try using the following...

static void Main(string[] args)
{
    IManagerFactory mgrFactory = new ManagerFactory();
    using (IAManager aMgr = mgrFactory.GetAManager())
    using (IBManager bMgr = mgrFactory.GetBManager())
    {
        A a = new A();
        a.Name = "a object";

        B b = new B();
        b.Name = "b object";

        a.Bs.Add(b);
        aMgr.Save(a);

        // Call CommitChanges to flush the session.
        aMgr.Session.CommitChanges();

        Console.WriteLine("complete");
        Console.ReadKey();
    }

Thanks,
Tom 

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 35
Top 100 Contributor
54 Posts
Points 1,195

First I am glad to announce you I have a license of CodeSmith 5.2 Pro! :o)

Nice reactivity from the team if you ask me!

By the way, it doesn't work out of the box yet. When you generate the bags in the mappings, there is an error. The table should not be

[accessrights].Angel or [accessrights].Beer

it should be instead

[accessrights].[ABs]

in both A and B mappings. Please fix it and I will try again generated code out of the box.

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35
Top 50 Contributor
118 Posts
Points 3,545
Answered (Not Verified) tdupont replied on Tue, Jan 26 2010 12:28 PM
Suggested by tdupont

Hello,

Ok, that was a total bonehead mistake on my part, sorry! The HBM should be fixed now!

Please download the latest nightly (1320), and let me know if that works for you.
http://community.codesmithtools.com/nightly/NHibernate/

If you thumb up this build I will turn it into NHibernate 1.1.6 Smile

Thanks,
Tom 

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 35
Top 100 Contributor
54 Posts
Points 1,195

It works on my simple A/B sample project. But when I try my real project I get some errors. It seems the Save cascade doesn't resolve the save order correctly. I get the following error:

"object references an unsaved transient instance - save the transient instance before flushing. Type: MyAssembly.BusinessObjects.MyDatatable, Entity:MyAssembly.BusinessObjects.MyDatatable"

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 5
Top 100 Contributor
54 Posts
Points 1,195

Here is my DB and here's what I do to get this error:

 

            IManagerFactory mgrFactory = new ManagerFactory();

            IRManager rMgr = mgrFactory.GetRManager();

            IUManager uMgr = mgrFactory.GetUManager();

            ISTManager stMgr = mgrFactory.GetSTManager();

            U u = uMgr.GetUniqueByQuery("from U where Name='uName'");

            R r = new R();

            r.F = fMgr.GetUniqueByQuery("from F where Name='fName'");

            S s = new S();

            s.Name = "sName";

            s.ST = stMgr.GetUniqueByQuery("from ST where Name='stName'");

            r.S = s;

            u.Rs.Add(r);

            // uncomment next line to make the CommitChanges() work

            // sMgr.Save(s);

            uMgr.Save(u);

            uMgr.Session.CommitChanges();

 

So, first I got an error saying I should make Sid nullable in R in the Database, and when i did that, i got the error stated, that my s object should be saved before saving u object.... while I was expecting it to be cascaded.

Am I doing something wrong?

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 5
Top 100 Contributor
54 Posts
Points 1,195

Ok, I made it working, here's the thing I had to modify in the mappings to make it work (it resolves both errors: the nullable field is no longer necessary and it's not necessary to save the S object in the code):

R.hbm.xml before:

....

 

<many-to-one name="F" column="[Fid]" class="F" />

<many-to-one name="S" column="[Sid]" class="S" />

...

 

R.hbm.xml after:

....

 

<many-to-one name="F" column="[Fid]" class="F" cascade="all" />

<many-to-one name="S" column="[Sid]" class="S" cascade="all" />

...

 

I don't know if it's intended or not, but no cascade here means problems at least for me :o)

 

 

 

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35
Top 50 Contributor
118 Posts
Points 3,545
tdupont replied on Wed, Jan 27 2010 11:26 AM

Hello,

I have made an update to the templates, in summary:

  • Your Many-To-One collections should now default to a cascade value of collapse.
  • Cascade is now configurable using the "cs_cascade" extended property. 

Please grab the latest nightly build (1330), and let me know how it goes.
http://community.codesmithtools.com/nightly/NHibernate/ 

For more details on the update, here is the code:

public static string GetCascade(ColumnSchema column, bool isOneToMany)
{
    var ep = column.ExtendedProperties["cs_cascade"];
    if (ep != null && ep.Value != null)
    {
        var eps = ep.Value.ToString();
        if (String.IsNullOrEmpty(eps))
            return eps;
    }

    if (isOneToMany)
        return column.AllowDBNull ? "all" : "all-delete-orphan";

    return column.AllowDBNull ? String.Empty : "all";
}

Thanks,
Tom 

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 35
Top 100 Contributor
54 Posts
Points 1,195

I tried 1332... and most unit tests fail now with:

 

Failed: AssertionException: NHibernate.ObjectDeletedException: deleted object would re-saves by cascade (remove deleted objec

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35
Top 50 Contributor
118 Posts
Points 3,545
tdupont replied on Fri, Jan 29 2010 12:14 AM

Hello,

Thank you for continuing to work with me on resolving these issues. Smile

I have reverted the default value for the ManyToOne cascade to none, so you will have to control your cascade associations using the cs_cascade extended property.
I have also made several much need updates to the Unit Tests, they should now be working better with the updated Session architecture, but there are still far from perfect.

In summary: the unit tests are still not perfect, and you will have to update the cascade using cascade properties.
Good luck with build 1338! http://community.codesmithtools.com/nightly/NHibernate/

Thanks,
Tom 

Tom DuPont
CodeSmith Tools, LLC. Software Development Engineer

  • | Post Points: 35
Top 100 Contributor
54 Posts
Points 1,195

Tests failed with 1332 but code worked. Objects should auto cascade. A temporary extended property might help but....

If it has to be manually set, please add it to the csp "edit output" window properties in "Database options". While you are at it, it might be a good idea to also add a similar options choice for the datatables to force as entities and not join tables.

Remember, we have often not enough privileges to add/modify the extended properties of a datatable.

 

I tried 1338 too, and the unit tests are better now, though it raised me again the 2 errors I had prior to 1332.

Also I can't get the cascade to be generated. If I add cs_cacade with a value of true or a column name.... it doesn't do anything. I can't find a way to add an extended property just to a column either..... can you do the modification as requested upper here? it would be easier i guess?!?

Proud owner of CodeSmith Pro 5.2 License!

  • | Post Points: 35
Page 1 of 2 (26 items) 1 2 Next > | RSS
Copyright © 2010 CodeSmith Tools, LLC