Fluent NHibernate: Custom ForeignKeyConvention not working with explicitly specified table names - nhibernate

EDIT: for the tl;dr crowd, my question is: How do I access the mappings from inside the ForeignKeyConvention in order to determine the table name that a given type is mapped to?
The long version:
I am using Fluent NHibernate to configure NHibernate, and I have a custom foreign key convention that is failing when I alias tables and columns.
My tables use a convention where the primary key is always called "PK", and the foreign key is "FK" followed by the name of the foreign key table, e.g., "FKParent". For example:
CREATE TABLE OrderHeader (
PK INT IDENTITY(1,1) NOT NULL,
...
)
CREATE TABLE OrderDetail (
PK INT IDENTITY(1,1) NOT NULL,
FKOrderHeader INT NOT NULL,
...
)
To make this work, I've built a custom ForeignKeyConvention that looks like this:
public class AmberForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName( Member member, Type type )
{
if ( member == null )
return "FK" + type.Name; // many-to-many, one-to-many, join
return "FK" + member.Name; // many-to-one
}
}
This works so long as my entities are named the same as the table. But it breaks when they aren't. For example, if I want to map the OrderDetail table to a class called Detail, I can do so like this:
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Table( "OrderDetail" );
Id( o => o.PK );
References( o => o.Order, "FKOrderHeader" );
...
}
}
The mapping works for loading a single entity, but when I try to run any kind of complicated query with a join, it fails, because the AmberForeignKeyConvention class is making incorrect assumptions about how the columns are mapped. I.e., it assumes that the foreign key should be "FK" + type.Name, which in this case is Order, so it calls the foreign key "FKOrder" instead of "FKOrderHeader".
So as I said above: My question is, how do I access the mappings from inside the ForeignKeyConvention in order to determine a given type's mapped table name (and for that matter, their mapped column names, too)? The answer to this question seems to hint at the right direction, but I don't understand how the classes involved work together. When I look through the documentation, it's frightfully sparse for the classes I've looked up (such as the IdMapping class).

the idea is to load the mappings
public class AmberForeignKeyConvention : ForeignKeyConvention
{
private static IDictionary<Type, string> tablenames;
static AmberForeignKeyConvention()
{
tablenames = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => typeof(IMappingProvider).IsAssignableFrom(t))
.ToDictionary(
t => t.BaseType.GetGenericArguments()[0],
t => ((IMappingProvider)Activator.CreateInstance(t)).GetClassMapping().TableName);
}
protected override string GetKeyName( Member member, Type type )
{
return "FK" + tablenames[type]; // many-to-one
}
}

Related

#Index annotation leads to "This annotation is not applicable to target 'member property with backing field'"

I am trying to create an index on a foreign key using the #Index annotation. Unfortunately, the compiler complains with the following message:
This annotation is not applicable to target 'member property with backing field'
What am I doing wrong here?
#Entity
#Table(name = "my_entity")
class MyEntity(someValue: Long) : BaseEntity(someValue) {
// .. some fields
#OneToOne
#JoinColumn(name = "another_entity")
#Index(name = "ix_another_entity")
var anotherEntity: AnotherEntity? = null
}
The #Index cannot be used like this (neither in java nor in kotlin), instead you can use it e.g. as part of the #Table annotation:
#Table(name= "my_entity", indexes = [ Index(columnList = "another_entity") ])
Specifying an Index (Non-Unique Key) Using JPA
(note that e.g. MySQL auto creates an index for foreign keys: Does MySQL index foreign key columns automatically? )

JPA/Hibernate overlapping PK and FK Columns

we're using Postgres and JPA/Hibernate to import a lot of data on a biweekly basis (~50-100M rows per import). We're trying to partition our tables per import, which has us running into some Hibernate PK/FK column mapping problems. The setup is essentially this on the SQL side
CREATE TABLE row (
import_timestamp timestamp,
id uuid,
PRIMARY KEY (import_timestamp, id)
) PARTITION BY LIST (import_timestamp);
CREATE TABLE row_detail (
import_timestamp timestamp,
id uuid,
row_id uuid,
PRIMARY KEY(import_timestamp, id),
CONSTRAINT row_detail_row_fk FOREIGN KEY (row_id, import_timestamp) REFERENCES row (id, import_timestamp)
) PARTITION BY LIST (import_timestamp);
and this on the Java side:
#Entity(name = "row")
public class RowEntity {
#EmbeddedId
private PartitionedId id;
#OneToMany(cascade = ALL, mappedBy = "row")
private List<RowDetailEntity> details;
}
#Entity(name = "row_detail")
public class RowDetailEntity {
#EmbeddedId
private PartitionedId id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "row_id", referencedColumnName = "id"),
#JoinColumn(name = "importTimestamp", referencedColumnName = "importTimestamp")
})
private RowEntity row;
}
#Embeddable
public class PartitionedId implements Serializable {
private Instant importTimestamp;
private UUID id;
}
Hibernate then complains on boot that:
column: import_timestamp (should be mapped with insert="false" update="false")
I can silence that error by doing as it says, but that makes little sense, because I am forced to set insertable=false and updatable=false for both #JoinColumn()s, which would mean row_id isn't populated on insert.
I could go the #MapsId route, but only if I give the row_detail table a PK that includes all 3 properties (import_timestamp, id, row_id), and I don't really want or need that.
So the question is, how do I get Hibernate to understand my overlapping, but not entirely nested PK/FK?

GORM Domain Mapping Issue

I've got a bit of a complicated domain model I'm trying to implement and I'm having some trouble. (On top of that, I'm quite new to all this!)
I have a User domain which has multiple roles and multiple tests. The Role domain works great. The Test domain is a bit more compilciated though because it requires two foreign keys instead of just 1 like in the Role domain. The first foreign key is the user_id and the second is a uni_id (university ID).
The User domain model contains the following
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
...
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
tests joinTable:[name:'user_test', key:'user_id'] // Here is where I run into trouble
}
static constraints = {
}
}
The Test domain contains
class Test {
static belongsTo = User
static hasMany = [users:User]
static hasOne = [uni:Uni]
Integer testId // primary key
String testType
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
users joinTable:[name:'user_test', key:'test_id']
uni joinTable:[name:'user_test', key:'test_id'] // If I leave this out, everything is groovy
version false
}
static constraints = {
}
}
and the Uni domain contains
class Uni {
static belongsTo = Test
static hasMany = [tests:Test]
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
tests joinTable:[name:'user_test', key:'uni_id']
}
static constraints = {
}
}
If its not clear, what I'm trying to do is pull in the University ID, Test ID, and User ID to a table user_test to find based on the User ID which tests they have taken. Is there a simple way to do this?
The kinds of errors I'm getting lead me to believe that for some reason it is trying to perform all actions on the table test instead of user_test. For example,
Unsuccessful: alter table test add uni_id int not null
I'd like to be able to access the test and university information corresonding to the specific user via user.tests.testType and user.tests.uni.fullName or something to that extent. What am I doing wrong? More importantly, is there a better way to do this?! Thanks in advance!
Edit 1: something interesting I just thought of.. a user can have multiple tests, but the inverse isn’t true. A given test will never be shared among multiple people. So I think that changes things a bit.. I'll do some reading and post if I come up with anything new.
Edit 2: Here's the Role domain
class Role {
static belongsTo = User
static hasMany = [users:User]
Integer roleId
String shortName
String roleName
Integer roleLevel
static mapping = {
table 'role'
id generator: 'assigned', name: 'roleId', type: 'long'
roleId column: 'role_id'
users joinTable:[name:'user_role', column:'user_id', key:'role_id']
version false
}
static constraints = {
}
}
Edit 3: I am now trying to store all test information in the Test domain model and simply choose the Uni name to store as a field in Test, but am getting weird errors when I try this. My new files look like this
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
}
static constraints = {
}
}
and
class Test {
static belongsTo = User
Integer testId // primary key
Integer testTypeId
String testTypeName
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
but now I'm getting the following error when I try to run it Caused by: org.hibernate.MappingException: Missing type or column for column[tests_test] on domain[User] referencing[Test]
Any idea what that's about?
Ok, one issue you have is that you're trying to share the User-to-Test association join table with the Test-to-Unit association. That's not going to work.
Lets look at it in database terms. I'm not an ASCII art expert, so I hope this diagram doesn't make your eyes bleed.
user_data (userId) |---|< (user_id) user_test (test_id) >|---| (testId) test
The diagram above shows the database implementation of the many-to-many association between the User and Test domain classes. You can see that the user_data.userId links to user_test.user_id and user_test.test_id links to test.testId.
Now here's where it starts to get weird. There are two different associations between Test and Uni: a bidirectional one-to-one and a one-to-many. I just don't understand that. But I want to illustrate an important issue with your join tables, so here it is.
test (testId) |---|< (test_id) user_test (uni_id) >|---| (uniId) uni
Because you're using the same join table (user_test) for two different associations you're asking GORM to create a table like this:
USER_TEST
- USER_ID
- TEST_ID
- UNIT_ID
GORM won't do that because join tables are supposed to have only two fields. Not only that, but also you're defining a many-to-many in database terms, and yet a bidirectional one-to-one and a one-to-many in GORM terms. Ouch!
TODO
The first change I recommend is to use a different join table for the Test-Uni association.
Finally got everything working (after a bit of modification in terms of the domain model)
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', column:'role_id', key:'user_id']
}
static constraints = {
}
}
and
class Test {
User user
Integer testId // primary key
String testType
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
with
class Uni {
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
}
static constraints = {
}
}
So now what I'm doing is selecting the university from a drop down tab in my GSP and just saving it in Test as the string testUni. Then, the big change was removing all joinTables between the three and adding User user to Test. I'm still a little fuzzy on why what I was doing before didn't work, but I won't complain about a working app!

phalcon resultset complex direct use

I have two tables and I am using phalcon's phql to join them.
In my controller i have:
$oBuilder = $this->modelsManager->createBuilder();
$oBuilder->columns(['Tabone.*', 'Tabtwo.*']);
$oBuilder->from(['Tabone']);
$oBuilder->join('Tabtwo', 'Tabone.id = Tabtwo.id');
$oBuilder->where('Tabone.id = 1');
$aRecords = $oBuilder->getQuery()->execute();
/** #var Phalcon\Mvc\Model\Resultset\Complex $aRecords */
//this doesnt work as expected
$aRecords[0]->tabone->setVal(2);
echo "2 != ".$aRecords[0]->tabone->getVal()."<br>";
echo get_class($aRecords[0]->tabone).'<br>';
//this works as expected
$aRecords->getFirst()->tabone->setVal(2);
echo "2 == ".$aRecords->getFirst()->tabone->getVal()."<br>";
So, with the Phalcon's Complex Traversable resultset I am able to set properties using :
$resultset->getFirst()->tabone->setVal(2);
echo $resultset->getFirst()->tabone->getVal();
But when i try :
echo get_class($aRecords[0]->tabone); // Says tabone
$resultset[0]->tabone->setVal(2);
echo $resultset[0]->tabone->getVal();
the value remains unchanged. even though $aRecords[0]->tabone is the class Tabone.
These are my models
class Tabone extends \Phalcon\Mvc\Model
{
public $id;
public $val;
public function columnMap() {
return array( 'id' => 'id', 'val' => 'val' );
}
public function setVal($val) { $this->val = $val; }
public function getVal() { return $this->val; }
}
class Tabtwo extends \Phalcon\Mvc\Model
{
public $id;
public function columnMap() {
return array( 'id' => 'id' );
}
}
these are the mysql tables and values
CREATE TABLE tabone (
id INT(11) NOT NULL AUTO_INCREMENT,
val INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (id)
);
CREATE TABLE tabtwo (
id INT(11) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO tabone (id, val) VALUES (1, 1);
INSERT INTO tabtwo (id) VALUES (1);
Why are the setters/getters no working when using [0] ?
Am i doing something i shouldn't ? ...
because it is how it works. you have methods for these things available like:
offsetGet() // Gets row in a specific position of the resultset
getFirst() // Get first row in the resultset
getLast() // Get last row in the resultset
all methods are here: http://docs.phalconphp.com/en/latest/api/Phalcon_Mvc_Model_Resultset_Complex.html
it's good practice to not use array's key, to keep it simple imagine this:
you are using setters & getters, instead simply setting var's value. But when you want to implement new validation for some input field, you have to go through all the code where you set value, not only just edit your setter. i believe it has some same logic going on here, but i am not developing core of the phalcon, i if you want to get more details you should go check their C code here: https://github.com/phalcon/cphalcon
With information found on:
http://forum.phalconphp.com/discussion/945/why-properties-of-models-are-lost-
(...) when a resultset is traversed, only just one record is kept in memory,
if you modify a record changes will lost, because the record is freed
once it is not used anymore. This scheme is very efficient if you are
traversing big resultsets (...)
and on
Scala: What is the difference between Traversable and Iterable traits in Scala collections?
(...) complying with the Traversable interface does not require
keeping state
So, the reason why [0] does not set properties is because traversable means just
that, it only traverses the object, any values set directly in the traversed object
will be lost, because the object state is not kept.
This makes perfect sense especially when you are talking about large result sets
as it will save tons of memory.

Fluent NHibernate join table mapping

Reverse engineering an existing database to map with N-Hibernate using Fluent N-Hibernate.
How can I map this?
Address table
Id
Address1
Address2
Person table
Id
First
Last
Types
Id
TypeName
PersonAddress table (A person can have home, business etc addresses)
Id
PersonId (Id from person table)
AddressId (Id from address table)
TypeId (Id from types lookup table HOME, BUSINESS etc..)
Any help would be great. Thanks
Here's another tricky one in addition to above mapping. Don't know how easy it would be to map it.
Party Table
Id
Person Id points to Person
Identifiers Tables
Id
Party Id
Type Id
Identifier value
Employee table
Employee Id No party or person table has foreign key to this table. The employee id is stored in the
identifiers table. so for e.g. The identifier table is used store values for different types. The identifiers for a given party could DriverLicense, EmployeeId, SSN, Credit Card numeber etc, this could be many values.
Sample identifier data
Id, PartyId, TypeId, IdentifierValue
1 , 1, 1, EMPLID-1234
2 , 2, 1, EMPLID-4567
3 , 3, 1, EMPLID-34354
I am trying to get my head around this and just can't get it to mapped.
// this answer assumes you have functional Address, Person, Type, and PersonAddress objects.
public class AddressMap : ClassMap<Address>
{
public AddressMap()
{
Id(x=>x.Id);
Map(x=>x.Address1);
Map(x=>x.Address2);
}
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x=>x.Id);
Map(x=>x.First);
Map(x=>x.Last);
}
}
public class TypeMap : ClassMap<Type>
{
public TypeMap()
{
Id(x=>x.Id);
Map(x=>x.TypeName);
}
}
public class PersonAddressMap : ClassMap<PersonAddress>
{
public PersonAddressMap()
{
Id(x=>x.Id);
References(x=>x.Person, "PersonId");
References(x=>x.Address, "AddressId");
References(x=>x.Type, "TypeId");
}
}