How set foreign key to null when entity keys are of type bigint (ulong)? - codefluent

In my model I'm using bigint (ulong) as the type for entity keys. I want the database to enforce referential integrity, so I have set persistenceEnforce to true. Columns for foreign keys are nullable. With referential integrity an entity can only be deleted if no foreign key is referring to the entity, so before deleting an entity I must first set each foreign key for this associated entity to null. However, I don't know how to clear the foreign key.
Here is my model:
<cf:entity name="Order" cfom:bindingList="false">
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<cf:property name="Invoice" typeName="Invoice" persistenceEnforce="true" />
</cf:entity>
<cf:entity name="Invoice" cfom:bindingList="false">
<cf:property name="Id" typeName="ulong" key="true" persistenceIdentity="true" cfps:hint="CLUSTERED" />
<cf:property name="Name" typeName="string" />
</cf:entity>
Here is my code:
Invoice invoice = new Invoice();
invoice.Save();
Order order = new Order();
order.Invoice = invoice;
order.Save();
// We must clear the reference to the invoice before deleting the invoice,
// because the database enforces referential integrity.
order.InvoiceId = 0;
order.Save();
invoice.Delete();
The above code throws the following exception when saving the order for the second time:
The UPDATE statement conflicted with the FOREIGN KEY constraint \"FK_Ord_Ore_Inv_Inv\".
This is because the code produced by CodeFluent inserts the value 0 instead of null into the "Order_Invoice_Id" column. The following line in the Order.BaseSave method seems to be wrong:
persistence.AddParameter("#Order_Invoice_Id", this.InvoiceId, ((ulong)(0ul)));
I tried the settings persistenceDefaultValue="null" and usePersistenceDefaultValue="true" on the Invoice propery, but that did not solve the problem.

Note: A property of type UInt64 (unsigned) is translated to a column of type bigint (signed). So be careful with the conversions... FYI CodeFluent Entities uses CodeFluent.Runtime.Utilities.ConvertUtilities to convert values.
The overload AddParameter(string name, ulong value, ulong defaultValue) doesn't use the default value, so it does not translate the default value to NULL. As a workaround, you can create a PersistenceHook that changes the value of the parameter to match the expected behavior:
public class CustomPersistenceHook : BasePersistenceHook
{
public override void AfterAddParameter(string name, object value, IDbDataParameter parameter)
{
if (value is ulong && name == "#Order_Invoice_Id")
{
var defaultValue = (ulong)ContextData["defaultValue"];
if (defaultValue == (ulong)value)
{
parameter.Value = DBNull.Value;
}
}
base.AfterAddParameter(name, value, parameter);
}
}
Then, you need to register the persistence hook:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="Sample" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />
</configSections>
<Sample persistenceHookTypeName="Sample.CustomPersistenceHook, Sample" />
</configuration>

Related

How Mybatis returns a map with a key instead of a null

Mybatis set resultType is map,when result is null how to return Map containing key Map rather than null
<setting name="callSettersOnNulls" value="true" /> The setting is not used, return null

JPA OneToMany with same entity using JoinTable

I have a OneToMany relationship that I need to model in JPA. I have a class called CustomerItem that needs to have certain CustomerItems (list) related to it. Therefore, I modeled it using JoinTable to keep this in a separate table.
I've tried to model it like this:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "mp_related_item", joinColumns = {
#JoinColumn(name = "customer_item_id", referencedColumnName = "id")})
private Set<CustomerItem> relatedItems;
I've created my table using Liquibase:
<createTable tableName="MP_RELATED_ITEM">
<column autoIncrement="true" name="ID" type="BIGINT">
<constraints primaryKey="true"
primaryKeyName="PK_MP_RELATED_ITEMS" />
</column>
<column name="CUSTOMER_ITEM_ID" type="BIGINT" />
<column name="RELATED_ITEM_ID" type="BIGINT" />
</createTable>
However, this results in an exception when trying to load relatedItems for a specific CustomerItem:
com.microsoft.sqlserver.jdbc.SQLServerException: Ogiltigt kolumnnamn, 'relatedItems_ID'. (Illegal column name)
What am I doing wrong here? When I check the generated query it seems like the persistence framework thinks that relatedItems is one single entity, when in reality it's a list of multiple entities that I want to load.

Fetching multiple embedded objects mybatis

Class User{
private String employeeId;
private Country assignedCountry;
private Region employeeRegion;
//getter & setter
}
Class Country{
private String countryCode;
private Region countryRegion;
//getter & setter methods
}
Class Region{
private String regionCode;
}
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>
Employee is assigned to a country and also belongs to a region.
Country belongs to a region, which may or may not be the same as the employee's region.
The query will fetch the users assigned country and region.
select U.*, C.*, R.* from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
When I execute the query via mybatis and check the user object, I can see that the user region is set properly.
But I can also see that the Region object within the User's country is also set to the user's region. Which shouldn't be the case.
(I understand that I am not fetching the country region here. But if so, this object should not be set at all, rather than setting employee region to the country region)
Can some one please help me on how to map the Country's region within the country object?
I am very new to mybatis and ORM. Any help to shed some light into this would be appreciated.
In your query, you are joining the Region table, not with Country table, but with User table, which ultimately returns the region of the employee (and the countryRegion property of the Country object as well).
In your ResultMap, regionResultMap maps as follows:
region_cd -> regionCode
region_nm -> regionName
You are mapping region_cd column, both to the user.assignedCountry.countryRegion.regionCode and to the user.employeeRegion.regionCode in the same time that basically sets the same columns to same properties in different objects.
What you can do is to differentiate User's region and Country's region in your SQL and map in MyBatis accordingly:
Add another join to connect region with the country:
select U.*, C.*, R.*, CR.region_cd AS C_region_cd, CR.region_nm as C_region_nm from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
left outer join Region CR
on CR.belongsTo = C.country_cd
And, you need to change your ResultMap as follows in order to use the same ResultMap with a different column name. Here, belongsTo is the column in your Region table that shows which Country it belongs to :
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association columnPrefix="C_" property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>

Composite keys in MyBatis <collection> mappings

I am unable to pass a composite key to a MyBatis <collection> element (using version 3.2.7). The MyBatis documentation states:
Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
However, all my attempts to implement this produce the Exception
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class java.lang.Integer with invalid types () or values (). Cause: java.lang.NoSuchMethodException: java.lang.Integer.<init>()
The collection (which resides in another ResultsMap) is:
<collection property="foos" ofType="FooObject"
column="{param1=user_id,param2=foo_id}" select="getFoosByUser" >
<id property="userId" column="user_id" />
<id property="foo" column="foo_id" />
<result property="fooName" column="foo_name" />
</collection>
It should return an ArrayList of Foo objects. The composite key is user_id and foo_id. The select query is:
<select id="getFoosByUser" parameterType="Integer" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>
The query works correctly if I only use one parameter, e.g. removed foo_id=#{param2} and then use column=user_id in the collection, but I cannot work out how to structure the column attribute correctly for two keys. Any ideas?
MyBatis is confused by using parameterType when there are more than one parameter. Modify you query mapping like this:
<select id="getFoosByUser" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>

Change Primary Key of .edmx

I have a .edmx file generated from a database having table "table1".
table1 has following attributes.
ID: Int (Primary Key)
Name: String
Tag: String
Description: String
In my edmx file, I want to change primary key from ID to (Name, Tag). How can I do that?
In the .edmx file, CSDL section, look for a tag for the element you want to change:
<edmx:ConceptualModels>
<Schema Namespace="DatabaseFirst.BloggingModel" ... >
<EntityType Name="Blog">
<Key>
<PropertyRef Name="BlogId" />
</Key>
<Property Name="BlogId" Type="InT32" ... />
Change the Name attribute in the PropertyRef and Property tags to your new desired primary key name.