How can an indexed many-to-many set be mapped in NHibernate? - nhibernate

Consider an entity, Entry, that contains a collection of another entity (many-to-many), Category, such that the Entry can be associated with a given Category no more than once (implying a "set" and constrained by the database) and the ordering of the Category entities within the collection is fixed and is defined by an extra field on the mapping table (implying a "list"). What would be the proper mapping for such a case?
Additional details/specifics about the problem this question is trying to address:
Since a <set> doesn't use an <index>, and we can easily maintain uniqueness of the Category entities in the application layer (as opposed to using NHibernate for this), using <list> seems to make sense since it will automatically handle the update of the ordering value in the extra field on the mapping table (which is the biggest "feature" I'd like to use). Unfortunately, when updating the collection, we encounter constraint violations due to the manner in which NHibernate performs the updates (even when the new collection values wouldn't ultimately cause a constraint violation) since we have a unique constraint on the database between the related objects in the mapping table.
More specifically, consider the following database schema:
CREATE TABLE Category (
id int IDENTITY PRIMARY KEY,
name varchar(50)
)
CREATE TABLE Entry (
id int IDENTITY PRIMARY KEY,
data varchar(50)
)
CREATE TABLE EntryCategory (
categoryId int REFERENCES Category (id),
entryId int REFERENCES Entry (id),
index int,
PRIMARY KEY (categoryid, entryId)
)
And the following mappings:
<class name="Category">
<id name="Id">
<generator class="native">
</id>
<property name="name"/>
</class>
<class name="Entry">
<id name="Id">
<generator class="native">
</id>
<property name="data"/>
<list name="Categories" table="EntryCategory">
<key column="entryID"/>
<index column="index"/>
<many-to-many class="Category" column="categoryID"/>
</list>
</class>
Let's consider the initial database state containing the following data:
EntryId CategoryID Index
555 12 0
555 13 1
555 11 2
555 2 3
On load of Entry #555, its Categories list will contain the following elements:
Index Category ID
0 12
1 13
2 11
3 2
If we update this list to remove an element (category 13 at index 1) to look like the following:
Index CategoryID
0 12
1 11
2 2
NHibernate will first delete the entry with highest index value (since the size has reduced), executing the following:
DELETE FROM EntryCategory WHERE entryId = #p0 AND index = #p1;
#p0 = '555', #p1 = '3'
After this update the data in the database looks like this:
EntryId CategoryID Index
555 12 0
555 13 1
555 11 2
Next, it attempts to update the values with the right mappings and starts with this updaet statement:
UPDATE EntryCategory SET CategoryID = #p0 WHERE EntryId = #p1 AND Index = #p2;
#p0 = '11', #p1 = '555', #p2 = '1'
This fails with Violation of PRIMARY KEY constraint since it's attempting to make the data look like this:
EntryId CategoryID Index
555 12 0
555 11 1
555 11 2
It seems that what really needs to happen is that the EntryId and CategoryId are left alone and the Index values are updated instead of leaving the EntryId and Index values alone and updating the CategoryId values. But even in that case, there could be issue if, say, there were also a UNIQUE constraint on EntryID and Index (in addition to the primary key on EntryID and CategoryID).

I think that you'll have to loosen up your constraints to get this to work. Like you said, even if NHibernate updated the index instead of the categoryId, "there could be issue if, say, there were also a UNIQUE constraint on EntryID and Index". This is what I would suggest:
-- Looser constraints. This should work.
CREATE TABLE EntryCategory (
id int IDENTITY PRIMARY KEY,
categoryId int REFERENCES Category (id),
entryId int REFERENCES Entry (id),
index int
);
CREATE INDEX IX_EntryCategory_category ON EntryCategory (entryId, categoryId);
CREATE INDEX IX_EntryCategory_index ON EntryCategory (entryId, index);
I mean, ideally you would have something like the following - but it would be nearly impossible to run any UPDATE queries on it:
-- Nice tight constraints - but they are TOO tight.
CREATE TABLE EntryCategory (
categoryId int REFERENCES Category (id),
entryId int REFERENCES Entry (id),
index int,
PRIMARY KEY (entryId, categoryId),
UNIQUE KEY (entryId, index)
);
Whenever I'm stuck in a situation like this where what I really want is a unique key but for some reason I'm forced to do without it, I settle for an index instead.
In case it's helpful, Ayende has an example with a very similar mapping - a many-to-many list. He doesn't talk about the database schema, though.

Have a look at Oren Eini's blog about the <map> and <set> tags particularly the <map> blog. I think that is what you are after.
<map>
<set>

Related

Best approach cascade deleting related entity in MS SQL

Need advice of the best approach how to design DB for the following scenario:
Following below DB structure exmaple (it's not real just explain problem)
File
(
Id INT PRIMARY KEY...,
Name VARCHAR(),
TypeId SMALLINT,
...
/*other common fields*/
)
FileContent
(
Id INT PRIMARY KEY...,
FileId FOREIGN KEY REFERENCES File(Id) NOT NULL ON DELETE CASCADE UNIQUE,
Content VARBINARY(MAX) NOT NULL,
)
Book
(
Id INT PRIMARY KEY...,
Name VARCHAR(255),
Author VARCHAR(255)
...
CoverImageId FK REFERENCES File(Id),
)
BookPageType
(
Id TINYINT PRIMARY KEY...,
Name VARCHAR(50),
)
BookPage
(
Id INT PRIMARY KEY...,
TypyId TINYINT FOREIGN KEY REFERENCES BookPageType(Id),
BookId INT FOREIGN KEY REFERENCES Book(Id) ON DELETE CASCADE,
Name VARCHAR(100),
CreatedDate DATETIME2,
...
/*other common fields*/
)
BookPage1
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
FileId PRIMARAY KEY REFERENCES File(Id)
...
/* other specific fileds */
)
...
BookPageN
(
Id PRIMARAY KEY REFERENCES BookPage(Id) NOT NULL ON DELETE CASCADE,
ImageId PRIMARAY KEY REFERENCES File(Id),
...
/* other specific fileds */
)
Now question is I want to delete Book with all pages and data (and it works good with delete cascade), but how to make cascade delete the associated files also (1 to 1 relentionship).
Here I see following approaches:
Add file to every table when I use it, but I don't want to copy file
schema for every table
Add foreign keys to the File table (instead of page for example), but since I use file for e.g. in 10 tables I will have 10 foreign keys in file table. This also not good
Use triggers, what I don't wnat to do
Thanks in Advance
If such necessary is appeared maybe it seems you need refactor your base.
You said this example is not real and I'll not ask about N tables for pages though it's strange. If not all files have 1 to 1 relationship and so you need remove only a file that other book does not refer to, it's sounds like a job for a trigger.
So what you have defined is a many-to-many relationship between BookPage and File. this is a result of the one-to-many relationship between BookPage and BookPageN and then the one-to-many relationship between File and BookPageN. To get the relationships you say you want in the text, you need to turn the relationship around to point from BookPageN to File. Maybe instead of having so many BookPageN tables you could find a way to consolidate them into a single table. Maybe just use the BookPage table. Just allow nulls for the fields that are optional.

How to map a joined-subclass with a composite key with non-matching column names

I have two tables, say:
PAYMENT
------------------------------
OrderId INT PK
PaymentId INT PK
Amount FLOAT
ChildPaymentRowNum INT
CARD_PAYMENT
------------------------------
OrderId INT PK
PaymentRowNum INT PK
CardType STRING
CHEQUE_PAYMENT
------------------------------
OrderId INT PK
PaymentRowNum INT PK
CheckNumber INT
No, I didn't make this DB and no I can't change it. I want to map CARD_PAYMENT and CHEQUE_PAYMENT as joined-subclasses of PAYMENT. The difference in this model from the examples I've found is that I'm both using a composite key and one of the column names in the foreign table doesn't match.
I think if it were not a composite key I could do this:
<joined-subclass name="CardPayment" table="CARD_PAYMENT" extends="Payment">
<key column="PaymentRowNum" foreign-key="ChildPaymentRowNum">
</joined-subclass>
And if the names matched on the composite key I could do this:
<joined-subclass name="CardPayment" table="CARD_PAYMENT" extends="Payment">
<key>
<column="OrderId">
<column="PaymentRowNum">
</key>
</joined-subclass>
But, while I'd like to do something like this I'm pretty sure it's illegal:
<!-- NO GOOD -->
<joined-subclass name="CardPayment" table="CARD_PAYMENT" extends="Payment">
<key>
<column="OrderId" foreign-key="OrderId">
<column="PaymentRowNum" foreign-key="ChildPaymentRowNum">
</key>
</joined-subclass>
So, how would I do something like this?
BONUS POINTS: if you can tell me how to do it with NHibernate.Mapping.Attributes, but if not I can probably figure it out.
The foreign-key attribute is used to specify the name of a foreign key constraint (not column!) that should be created by the NHibernate schema generation tool. It has no affect on NHibernate during runtime.
Why do you want to specify the key column name of the base class in the subclass key? Even the NHibernate documentation for joined-subclass uses different column names in both tables: http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-joinedsubclass

How force ID generator for class "indentity" to increment for last inserted Id in SQL

I want ask for some advice about,why my <generator class="identity" /> generate id in table not as as last inserted + 1 but like e.g last insert ID is 5, and when save record then a new Id has be for example 7, so ID value 6 is skipped
(i need use identity not increment class due to advantage of this "identity" class for my mySql database)
because in my case i need same ID value for primary key and foreign key, must be equals,,but in this case i got Primary key 7 and Foreign key will generate also but not as incremented by last ID,
both class has Hibernate mappings, with this <generator class="identity" />
What i must use so that to obtain both incremented ID with new record by "identity"?
You have to create your table with a primary key using "AUTO_INCREMENT" such as :
CREATE TABLE person (
id BIGINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
)
Then your GenerationType.IDENTITY must do the job.

SQL Server + Composite key or Unique Id

I am fairly new to database design, for many to many relationship, what is the differences and implications of creating a composite key and a unique id for e.g.
Country table
CountryID
CountryName
Language table
LanguageID
LangugageName
Many to Many table - using composite:
CountryID Pkey
LanguageID Pkey
OR
Using unique Id:
AutoID Pkey
CountryID
LanguageID
Composite Key :
A composite key is a combination of more than one column to identify a unique row in a table.
composite key can be a primary key .
PRIMARY KEY
The PRIMARY KEY constraint uniquely identifies each record in a database table.
so its all depend on your requirement
in first design
Many to Many Table:
Using composite:
CountryID Pkey
LanguageID Pkey
if you use this desing than CountryID and LanguageID is composite primary key.i.e here
data of the table will be
CountryId LaguageID
1 1 //valid
1 2 //valid
1 3 //valid
1 1//not valid data as its form composite primary key
and in second design
Using Unique Id:
AutoID Pkey
CountryID
LanguageID
AutoID is become primary key so this will allow data lke thsi
AutoId CountryId LaguageID
1 1 1 //valid
2 1 2 //valid
3 1 3 //valid
4 1 1 //valid as AutoID is primary key
1 2 3 // invalid as AutoID is prinary key
hope this presentation help you to understand difference
what is the differences and implications of creating a composite key and a unique id for e.g.
You'll need to create a "natural" key on {CountryID, LanguageID} to avoid duplicated connections in any case. The only question is whether you'll also need a "surrogate" key on {AutoID}?
Reasons for a surrogate key:
There are child tables that reference this junction table (and you'd like to keep their FKs slim or prevent ON CASCADE UPDATE propagation).
You are using an ORM that likes simple PKs.
Unless some of these reasons apply, use only the natural key.
BTW, under a DBMS that supports clustering, a natural key like this is usually a good candidate for a clustering key. If you cluster the table, every other index (such as the one underneath the surrogate key) has extra overhead (compared to an index in a heap-based table) since it needs to keep the copy of clustering key data and can cause a double-lookup.
See also: A column as primary key or two foreign keys as primary key.

MS SQL Bridge Table Constraints

Greetings -
I have a table of Articles and a table of Categories.
An Article can be used in many Categories, so I have created a table of ArticleCategories like this:
BridgeID int (PK)
ArticleID int
CategoryID int
Now, I want to create constraints/relationships such that the ArticleID-CategoryID combinations are unique AND that the IDs must exist in the respective primary key tables (Articles and Categories).
I have tried using both VS2008 Server Explorer and Enterprise Manager (SQL-2005) to create the FK relationships, but the results always prevent Duplicate ArticleIDs in the bridge table, even though the CategoryID is different.
I am pretty sure I am doing something obviously wrong, but I appear to have a mental block at this point.
Can anyone tell me please how should this be done?
Greaty appreciated!
Don't use a BridgeId column.
Make a composite primary key (aka compound key) from your ArticleId and CateogryId, that will ensure each combination is unique.
Make each column a foreign key to the corresponding table, and that completes your set of constraints.
Ok first you do a unique index on ArticleID, CategoryID.
Then you set up a foreign key constraint on articleID linking it back to the Article table. and then do the same for CategoryID and Catgory table.
Your description sounds like you are creating the PK on the Bridge table and the FK on the other table which is why it wouldn't work.
Expanding on HLGEM's solution you would have something like:
Create Table ArticleCategories
(
Id int not null Primary Key Clustered
, ArticleId int not null
, CategoryId int not null
, Constraint UK_ArticleCategories_Unique ( ArticleId, CategoryId )
, Constraint FK_ArticleCategories_Articles
Foreign Key ( ArticleId )
References Articles( Id )
, Constraint FK_ArticleCategories_Categories
Foreign Key ( CategoryId )
References Categories( Id )
)