Hibernate Search query searches all tables instead of only the specified class' table - hibernate-search

I have an abstract class Product that are subclassed by ProductA, ProductB and ProductC.
And the classes ProductA, ProductB and ProductC are mapped to the database tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C respectively.
Now I want to perform a full-text search on ProductA entities by Hibernate Search.
I wrote some code to successfully get expected ProductA entities from database, but I found in the log (as follows) that the executed Hibernate Search query actually searched all the tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C instead of only the table PRODUCTS_A I expected.
I want to get only ProductA entities, why are ProductB and PRODUCTS_C tables also searched? Is there a way to fix this?
Log
You can see from the following working log outputted by Hibernate that besides the PRODUCTS_A table, the PRODUCTS_B and PRODUCTS_C tables are also searched.
Hibernate: select this_.ID as ID1_2_0_, this_.NAME as NAME2_2_0_, this_.FEATURE as FEATURE3_2_0_, this_.CREATED_DATE as CREATED_4_2_0_, this_.MODIFIED_DATE as MODIFIED5_2_0_, this_.FEATURE_A1 as FEATURE_1_3_0_, this_.FEATURE_A2 as FEATURE_2_3_0_, this_.FEATURE_B1 as FEATURE_1_4_0_, this_.FEATURE_B2 as FEATURE_2_4_0_, this_.FEATURE_C1 as FEATURE_1_5_0_, this_.FEATURE_C2 as FEATURE_2_5_0_, this_.clazz_ as clazz_0_ from ( select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, FEATURE_A1, FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 1 as clazz_ from PRODUCTS_A union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, FEATURE_B1, FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 2 as clazz_ from PRODUCTS_B union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, FEATURE_C1, FEATURE_C2, 3 as clazz_ from PRODUCTS_C ) this_ where (this_.ID in (?))
Code
Entity Classes
Here are the entity classes Product, ProductA, ProductB and ProductC.
public abstract class Product {
#Id
protected Long id;
#Field
protected String name;
#Field
protected String feature;
protected Date createdDate;
protected Date modifiedDate;
// Getters and setters...
}
#Entity
#Indexed
public class ProductA extends Product {
#Field
private String featureA1;
#Field
private String featureA2;
public ProductA() {
}
// Getters and setters...
}
The ProductB and ProductC classes are similar as the ProductA class.
Hibernate Mapping File
Product.hbm.xml
The union-subclass element is used to reflect the subclass relationship between the Product class and the ProductA, ProductB and ProductC classes.
<hibernate-mapping package="com.raychen518.study.hibernate">
<class name="Product" abstract="true">
<id name="id" column="ID">
<generator class="increment" />
</id>
<property name="name" column="NAME" />
<property name="feature" column="FEATURE" />
<property name="createdDate" type="timestamp" column="CREATED_DATE" />
<property name="modifiedDate" type="timestamp" column="MODIFIED_DATE" />
<union-subclass name="ProductA" table="PRODUCTS_A">
<property name="featureA1" column="FEATURE_A1" />
<property name="featureA2" column="FEATURE_A2" />
</union-subclass>
<union-subclass name="ProductB" table="PRODUCTS_B">
<property name="featureB1" column="FEATURE_B1" />
<property name="featureB2" column="FEATURE_B2" />
</union-subclass>
<union-subclass name="ProductC" table="PRODUCTS_C">
<property name="featureC1" column="FEATURE_C1" />
<property name="featureC2" column="FEATURE_C2" />
</union-subclass>
</class>
</hibernate-mapping>
Hibernate Configuration File
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost:5432/test</property>
<property name="connection.username">postgres</property>
<property name="connection.password">admin</property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">validate</property>
<!-- Setting for Hibernate Search -->
<property name="hibernate.search.lucene_version">LUCENE_CURRENT</property>
<property name="hibernate.search.default.directory_provider">filesystem</property>
<property name="hibernate.search.default.indexBase">hibernate.search.test/lucene/indexes</property>
<mapping resource="Product.hbm.xml" />
</session-factory>
</hibernate-configuration>
Application Launcher Class
The ProductManager class contains the main method, thus serves as the application launcher. It starts the Hibernate Search indexing process, clears the PRODUCTS_A, PRODUCTS_B and PRODUCTS_C tables and inserts some sample product data into them, and finally performs a full-text search using the Hibernate Search.
What confuses me is that I have specified the target entity as ProductA.class in the following statement Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class); in the method searchProducts(). Why does Hibernate Search also search ProductB and ProductC entities?
public class ProductManager {
public static void main(String[] args) throws InterruptedException {
ProductManager productManager = new ProductManager();
productManager.indexAllProducts();
productManager.deleteAllProducts();
productManager.generateSomeProducts();
productManager.searchProducts();
}
private void indexAllProducts() throws InterruptedException {
FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
fullTextSession.createIndexer().startAndWait();
}
public void deleteAllProducts() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
#SuppressWarnings("unchecked")
List<Product> results = session.createQuery("from Product").list();
for (Product result : results) {
session.delete(result);
}
session.getTransaction().commit();
}
public void generateSomeProducts() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
session.save(new ProductA("feature001", "featureA1001", "featureA2001", new Date()));
session.save(new ProductA("feature002", "featureA1002", "featureA2002", new Date()));
session.save(new ProductA("feature003", "featureA1003", "featureA2003", new Date()));
session.save(new ProductB("feature001", "featureB1001", "featureB2001", new Date()));
session.save(new ProductB("feature002", "featureB1002", "featureB2002", new Date()));
session.save(new ProductB("feature003", "featureB1003", "featureB2003", new Date()));
session.save(new ProductC("feature001", "featureC1001", "featureC2001", new Date()));
session.save(new ProductC("feature002", "featureC1002", "featureC2002", new Date()));
session.save(new ProductC("feature003", "featureC1003", "featureC2003", new Date()));
session.getTransaction().commit();
}
private void searchProducts() {
FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
fullTextSession.beginTransaction();
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(ProductA.class).get();
org.apache.lucene.search.Query luceneQuery = queryBuilder.keyword().onFields("feature").matching("feature002").createQuery();
// Set the 2nd method parameter using "Product.class" to get products of the types ProductA, ProductB and ProductC.
// Set the 2nd method parameter using "ProductA.class" to get products of the types ProductA.
Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class);
#SuppressWarnings("unchecked")
List<Product> queryResults = query.list();
for (Product queryResult : queryResults) {
System.out.println("queryResult: " + queryResult);
}
fullTextSession.getTransaction().commit();
}
}

I fixed it recently as https://hibernate.atlassian.net/browse/HSEARCH-2301 following another Stackoverflow question.
It hasn't been released yet but the patch is rather small and localized in only one file: https://github.com/hibernate/hibernate-search/pull/1122/files so you should be able to apply it locally on the 5.5 branch.
Use https://patch-diff.githubusercontent.com/raw/hibernate/hibernate-search/pull/1122.diff to get the raw diff file.
UPDATE we fixed it in 5.5.4.Final: http://in.relation.to/2016/06/29/Polishing-Polishing-And-More-Polishing-Hibernate-Search-5-5-4-Final/

Related

Testing a FieldSetMapper which reads by column names throws exception

The line mapping logic in the job config xml is as follows:
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="ID,NAME"/>
<property name="strict" value="false"/>
</bean>
</property>
<property name="fieldSetMapper">
<bean class="com.company.batch.mappers.EmpFieldSetMapper"/>
</property>
The field set mapper read logic is as follows:
fieldSet.readString("ID");
But while writing UT for EmpFieldSetMapper, I am getting below error:
java.lang.IllegalArgumentException: Cannot access columns by name without meta data
at org.springframework.batch.item.file.transform.DefaultFieldSet.indexOf(DefaultFieldSet.java:675)
at org.springframework.batch.item.file.transform.DefaultFieldSet.readString(DefaultFieldSet.java:169)
The UT is as follows:
#Before
public void setUp() throws Exception {
mapper = new EmpFieldSetMapper();
DefaultFieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
String[] inputValues = { "123", "RAJ" };
fieldSet = fieldSetFactory.create(inputValues);
}
#Test
public void testMapFieldSet() {
try {
Model model = mapper.mapFieldSet(fieldSet);
assertEquals("ID field mapping is wrong", "123", model.getId());
assertEquals("NAME field mapping is wrong", "RAJ", model.getName());
} catch (BindException e) {
fail("Exception during field set mapping");
}
}
I think I need to change the DefaultFieldSetMapper to something else but unsure about it. This issue can be resolved by replacing the column names with index but I want to retain the column names in EmpFieldSetMapper. So need suggestions.
I found the answer. Need to pass in column names as well.
String[] inputValues = { "123", "RAJ" };
String[] inputColumns = { "ID", "NAME" };
fieldSet = fieldSetFactory.create(inputValues, inputColumns);

Prevent unwanted cascaded table updates

I am creating my first JPA based project. My application features several tables with foreign key relationships for integrity purposes. Many related tables are normalized lookup tables.
Consider the tables Person and Account, having a fk relationship on accountid:
+-Person----+
| personid | +-Account---+
| accountid*|==============>| accountid |
| ... | | ... |
+-----------+ +-----------+
In the front end (JSF), I created a form for new Person objects containing a selectOneMenu list with all accounts, and the selected accountid is added to the new person which is inserted correctly into the database.
However, the Account table is also updated automatically and I cannot find a way to stop this from happening.
My classes are annotated as follows:
#Entity
#Table(name="usr_person")
#NamedQuery(name="Person.findAll", query="SELECT u FROM Person u")
public class Person implements Serializable
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="person_id")
private int personId;
//bi-directional one-to-one association to UsrAccount
#OneToOne
#JoinColumn(name="account_id",insertable=true,updatable=true)
private Account usrAccount;
...
#Entity
#Table(name="usr_account")
#NamedQuery(name="UsrAccount.findAll", query="SELECT u FROM Account u")
public class Account implements Serializable
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="account_id")
private int accountId;
//bi-directional one-to-one association to UsrPerson
#OneToOne(mappedBy="usrAccount")
private Person usrPerson;
...
The front end calls the below method which simply uses the merge() function of EntityManager to persist the new Person object:
public String savePerson()
{
try
{
em.getTransaction().begin();
person = em.merge( person );
em.getTransaction().commit();
}
catch (Exception e)
{
...
This leads to an UPDATE call for every object in the Account table.
UPDATE
I removed the EntityManager.merge() call from my code and the UPDATE statements are still there. Somehow the JSF form leads to automatic updates of the Account objects backing it. The lines below are from the JPA trace:
17448 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 1172878998 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) mvreijn, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 90, (int) 1]
17449 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
17449 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 386904456 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) afolmer, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 90, (int) 2]
17450 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
17450 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 1322606395 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) annuska, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 80, (int) 3]
17451 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
These statements are executed when I commit() the transaction. For some reason the query and subsequent transaction leads JPA to believe all Account objects from the selectOneMenu are modified; how do I prevent this? Detach them first one-by-one?
UPDATE 2
My persistence.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="matco">
<class>matco.model.Changelog</class>
<class>matco.model.Item</class>
<class>matco.model.ItemAssignment</class>
<class>matco.model.ItemMaintenance</class>
<class>matco.model.LstType</class>
<class>matco.model.LstColor</class>
<class>matco.model.LstCondition</class>
<class>matco.model.LstSize</class>
<class>matco.model.Team</class>
<class>matco.model.Account</class>
<class>matco.model.Person</class>
<class>matco.model.CatBrand</class>
<class>matco.model.CatItem</class>
<class>matco.model.CatPrice</class>
<class>matco.model.CatSupplier</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/matco" />
<property name="javax.persistence.jdbc.user" value="****" />
<property name="javax.persistence.jdbc.password" value="****" />
<!-- TODO remove this in favor of enhancement -->
<property name="openjpa.RuntimeUnenhancedClasses" value="supported"/>
<property name="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SCHEMA=TRACE, SQL=TRACE"/>
<property name="openjpa.ConnectionFactoryProperties" value="PrintParameters=true" />
</properties>
</persistence-unit>
</persistence>
Meanwhile, I have detached the Account objects after I query them in the backing bean, like this:
public List<Account> getUsrAccounts()
{
List<Account> accts = em.createNamedQuery( "Account.findAll", Account.class ).getResultList();
for (Account acct : accts)
em.detach(acct);
return accts;
}
Then when I commit the new Person object, only the Account that is linked to the new Person is updated by JPA. Even that is undesirable, and I get the feeling that I am misusing JPA in some way.
What is the normal way to use a non-editable lookup table with JPA?
Do not have <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> enabled in your persistence.xml. Figure out another enhancement strategy.
Try adding to your #OneToOne mappings cascade=CascadeType.REFRESH)
This will indicate to your ORM framework to cascade only on refreshes (while reading entity) and to not cascade on updates when trying to save only one of these entities.

Query : property name in parameter

With this query, I succeed to retrieve a phone number in database:
import java.util.List;
import org.springframework.data.jpa.repository.JpaReposit ory;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.mc.appcontacts.domain.hibernate.Contact;
public interface ContactRepository extends JpaRepository<Contact, Integer> {
#Query("SELECT c.phoneNumber from Contact c WHERE LOWER(c.name) = LOWER(:name)")
String find(#Param("name") String name);
But is it possible to specify dynamically name of the property i want to retrieve in parameter?
In all tuto i've read on the net, i learn we can pass the value of the property in parameter (In my exemple : #Param("name") String name )
but what i want to pass in parameter is the name of the property not the value !
I know the exemple below is not correct but it's to give the general idea :
#Query("SELECT c.(property) from Contact c WHERE LOWER(c.name) = LOWER(:name)")
String find(#Param("name") String name, #Param("property") String property);
With property = phoneNumber (or an other property of my table).
Thank you for your help !!
I don't understand how to do that (everything is new for me):
I have read (and try) that jpql is defined like this :
import com.mysema.query.jpa.impl.JPAQuery;
import com.mc.appcontacts.repository.ContactRepository; // managed by spring data
//jpa repository
public class ServicesC {
#Autowired
private ContactRepository repository;
#PersistenceContext // try
private EntityManager em; // try
JPAQuery query = new JPAQuery(em); // try
public Contact getOne(Integer id) {
return repository.findOne(id);
}
public String findAtt(String att, String key){
String jpql = "SELECT c." + att + " from Contact c WHERE LOWER(c.name) = LOWER(:key)"; // try
List<Contact> l = (List<Contact>) em.createQuery(jpql); // try
return "test";
}
}
But it doesn't work (i'm not surprised...) :
2014-02-24 18:18:34.567:WARN::Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appMapping': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mc.appcontacts.service.ServiceC com.mc.appcontacts.mvc.MappingService.service; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Service' defined in file [C:\Professional\Workspaces\Eclipse\ContactMain\ContactCore\target\classes\com\mc\appcontacts\service\ServiceC.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.mc.appcontacts.service.ServiceC]: Constructor threw exception; nested exception is java.lang.NullPointerException:
java.lang.NullPointerException
at com.mysema.query.jpa.impl.JPAProvider.get(JPAProvider.java:72)
at com.mysema.query.jpa.impl.JPAProvider.getTemplates(JPAProvider.java:80)
at com.mysema.query.jpa.impl.JPAQuery.<init>(JPAQuery.java:46)
Must i define a second EntityManager only for jpql ? (Is it possible ? is it the right way ? I don't think so...)
I have already a EntityManager defin for Spring-data in xml file :
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activate Spring Data JPA repository support -->
<jpa:repositories base-package="com.mc.appcontacts.repository" />
<!-- Declare a JPA entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:META-INF/contacts/hibernate/persistence.xml" />
<property name="persistenceUnitName" value="hibernatePersistenceUnit" />
<!-- <property name="dataSource" ref="dataSource" /> -->
<property name="jpaVendorAdapter" ref="hibernateVendor" />
</bean>
<!-- Specify our ORM vendor -->
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="${hibernate.showSql}" />
</bean>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
Please help me ... how does it work ?
No, it's not possible to do that. You'll have to implement it by yourself by dynamically generating the JPQL query.
Using query parameyters is not an option, because query parameters can only be values to replace in a given prepared statement, and can't alter the nature of the query itself. So you'll have to do something like
String jpql = "select c." + property + " from ...";
I think for this use case of building queries dynamically your best bet would be to explore Criteria API, which is very suitable for such things. http://docs.oracle.com/javaee/6/tutorial/doc/gjitv.html

Spring Batch : PassThroughFieldExtractor with BigDecimal formatting

I'm using Spring Batch to extract a CSV file from a DB table which has a mix of column types. The sample table SQL schema is
[product] [varchar](16) NOT NULL,
[version] [varchar](16) NOT NULL,
[life_1_dob] [date] NOT NULL,
[first_itm_ratio] [decimal](9,6) NOT NULL,
the sample Database column value for the 'first_itm_ration' field are
first_itm_ratio
1.050750
0.920000
but I would like my CSV to drop the trailing zero's from values.
first_itm_ratio
1.05075
0.92
I'd prefer not to have to define the formatting for each specific field in the table, but rather have a global object specific formatting for all columns of that data type.
My csvFileWriter bean
<bean id="csvFileWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
<property name="resource" ref="fileResource"/>
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter">
<util:constant static-field="org.springframework.batch.item.file.transform.DelimitedLineTokenizer.DELIMITER_COMMA"/>
</property>
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.PassThroughFieldExtractor" />
</property>
</bean>
</property>
</bean>
You can
Write your own BigDecimalToStringConverter implements Converter<BigDecimal, String> to format big decimal without trailing 0's
Create a new ConversionService (MyConversionService) and register into the custom converter
Extends DelimitedLineAggregator, inject MyConversionService, override doAggregate() to format fields using injected conversion service
public class MyConversionService extends DefaultConversionService {
public MyConversionService() {
super();
addConverter(new BigDecimalToStringConverter());
}
}
public class MyFieldLineAggregator<T> extends DelimitedLineAggregator<T> {
private ConversionService cs = new MyConversionService();
public String doAggregate(Object[] fields) {
for(int i = 0;i < fields.length;i++) {
final Object o = fields[i];
if(cs.canConvert(o.getClass(), String.class)) {
fields[i] = cs.convert(o, String.class);
}
}
return super.doAggregate(fields);
}
}

Entity Framework + Repository Pattern and foreign key update not updating related entity

I'm using Entity Framework 5 and the UnitOfWork + Repository pattern.
I am trying to create the following entity:
public partial class ViaggioAttivita
{
public System.Guid Id { get; set; }
public System.Guid IdViaggio { get; set; }
public virtual Viaggio Viaggio { get; set; }
}
public partial class Viaggio
{
public System.Guid Id { get; set; }
public virtual ICollection<ViaggioAttivita> ViaggiAttivita { get; set; }
}
I noticed that when i create the new ViaggioAttivita entity populating the IdViaggio, when i do
ViaggioAttivita attivita = new ViaggioAttivita();
attivita.IdViaggio = ParentId;
unitOfWork.ViaggiAttivitaRepository.Insert(attivita);
the navigation property attivita.Viaggio does not get updated.
If I directly update the attivita.Viaggio instead of the Id
ViaggioAttivita attivita = new ViaggioAttivita();
attivita.Viaggio = unitOfWork.ViaggiRepository.GetByID(ParentId);
unitOfWork.ViaggiAttivitaRepository.Insert(attivita);
The Viaggio of course get updated, but the IdViaggio key gets updated too.
What am I missing ?
Why am I getting this difference?
I tried calling a .Save() but nothing changes.
It seems that relations only get updated if I manually update the entity, but they don't get updated if I update the key only.
Thank you
Edit 1:
I'm on Sql Server 2008, MVC3, Entity Framework 5 (runtime v4.0.30319 of course). Database First mode. The two tables have the relationship (of course, otherwise it would not populate the Key using the second method).
Edit 2:
I try to past some EDMX information;
<EntityType Name="Viaggio">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Guid" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<NavigationProperty Name="ViaggiAttivita" Relationship="DatabaseModel.FK_ViaggiAttivita_Viaggi" FromRole="Viaggi" ToRole="ViaggiAttivita" />
</EntityType>
<EntityType Name="ViaggioAttivita">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Guid" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Name="IdViaggio" Type="Guid" Nullable="false" />
<NavigationProperty Name="Viaggio" Relationship="DatabaseModel.FK_ViaggiAttivita_Viaggi" FromRole="ViaggiAttivita" ToRole="Viaggi" />
</EntityType>
<AssociationSet Name="FK_ViaggiAttivita_Viaggi" Association="DatabaseModel.FK_ViaggiAttivita_Viaggi">
<End Role="Viaggi" EntitySet="Viaggi" />
<End Role="ViaggiAttivita" EntitySet="ViaggiAttivita" />
</AssociationSet>
The difference is:
a) Set Foreign Key Only. (Id) If this Entity is in loaded in the cache the Navigation property can be set. If it isnt loaded, then you would need to trigger the loading. You can search on how to or when this is done automatically. See topic lazy loading versus .include
b) Set navigation property with an entity.
the navigation property is supported by Foreign key Id field.
Now EF can see the Nav property and Its key. It can set the ID with data it already has. No need to load from DB. SO it is set.