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

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.

Related

How to do migrations with EF Core 2.2 using Spatial Data?

I am trying to do migrations in Entity Framework Core 2.2 but I get some strange errors.It should work since the documentation does not say anything about mapping code or so.
This command:
dotnet ef migrations add InitialCreate
results in this error:
The property 'Point.Boundary' is of an interface type ('IGeometry'). If it is a navigation property manually configure the relationship for this property by casting it to a mapped entity type, otherwise ignore the property using the NotMappedAttribute or 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I do not understand it. I have an enity, a context and all the required dependencies, including EF Core 2.2 . How do I solve it?
Project File:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.0" />
<PackageReference Include="NetTopologySuite" Version="1.15.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
</ItemGroup>
Model File
using System.ComponentModel.DataAnnotations;
using NetTopologySuite.Geometries;
namespace WebApplication1.Models
{
public class Item
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Point Location { get; set; }
}
}
Context File
using System;
using Microsoft.EntityFrameworkCore;
using WebApplication1.Models;
namespace WebApplication1
{
public class ItemContext : DbContext
{
public ItemContext(DbContextOptions<ItemContext> options) : base(options)
{
Console.WriteLine("Context created");
}
public DbSet<Item> Items { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
Console.WriteLine("OnModelCreating");
}
}
}
The console:
Basically, the link you shared is a blog that only introduces the new features. At the end of every topic, you would find a link to the entire documentation. It seems like this suite requires an additional library based on which database you are using.
According to this documentation, you need to add the respective Spatial NuGet Package based on what your database is. (check the install section. For instance, add the Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite package if you are using SQL server. Once this is done, in your startup class, within your AddDbContext function, you can use something like this config.UseSqlServer("", x => x.UseNetTopologySuite()).

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

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/

Getting Data from Multiple tables in Liferay 6.0.6

i'm trying to get data from multiple tables in liferay 6.0.6 using custom sql, but for now i'm just able to display data from one table.does any one know how to do that.thanks
UPDATE:
i did found this link http://www.liferaysavvy.com/2013/02/getting-data-from-multiple-tables-in.html but for me it's not working because it gives an error BeanLocator is null,and it seems that it's a bug in liferay 6.0.6
The following technique also works with liferay 6.2-ga1.
We will consider we are in the portlet project fooproject.
Let's say you have two tables: article, and author. Here are the entities in your service.xml :
<entity name="Article" local-service="true">
<column name="id_article" type="long" primary="true" />
<column name="id_author" type="long" />
<column name="title" type="String" />
<column name="content" type="String" />
<column name="writing_date" type="Date" />
</entity>
<entity name="Author" local-service="true">
<column name="id_author" type="long" primary="true" />
<column name="full_name" type="String" />
</entity>
At that point run the service builder to generate the persistence and service layers.
You have to use custom SQL queries as described by Liferay's Documentation to fetch info from multiple databases.
Here is the code of your fooproject-portlet/src/main/ressources/default.xml :
<?xml version="1.0"?>
<custom-sql>
<sql file="custom-sql/full_article.xml" />
</custom-sql>
And the custom request in the fooproject-portlet/src/main/ressources/full_article.xml:
<?xml version="1.0"?>
<custom-sql>
<sql
id="com.myCompany.fooproject.service.persistence.ArticleFinder.findByAuthor">
<![CDATA[
SELECT
Author.full_name AS author_name
Article.title AS article_title,
Article.content AS article_content
Article.writing_date AS writing_date
FROM
fooproject_Article AS Article
INNER JOIN
fooproject_Author AS Author
ON Article.id_author=Author.id_author
WHERE
author_name LIKE ?
]]>
</sql>
</custom-sql>
As you can see, we want to fetch author's name, article's title, article's content and article's date.
So let's allow the service builder to generate a bean that can store all these informations. How ? By adding it to the service.xml ! Be careful: the fields of the bean and the fields' name returned by the query must match.
<entity name="ArticleBean">
<column name="author_name" type="String" primary="true" />
<column name="article_title" type="String" primary="true" />
<column name="article_content" type="String" />
<column name="article_date" type="Date" />
</entity>
Note: defining which field is primary here does not really matter as there will never be anything in the ArticleBean table. It is all about not having exceptions thrown by the service builder while generating the Bean.
The finder method must be implemented then. To do so, create the class com.myCompany.fooproject.service.persistence.impl.ArticleFinderImpl. Populate it with the following content:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> {
}
Use the correct import statements and run the service builder. Let's make that class implement the interface generated by the service builder:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
}
And populate it with the actual finder implementation:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
// Query id according to liferay's query naming convention
public static final String FIND_BY_AUTHOR = ArticleFinder.class.getName() + ".findByAuthor";
public List<Article> findByAuthor(String author) {
Session session = null;
try {
session = openSession();
// Retrieve query
String sql = CustomSQLUtil.get(FIND_BY_AUTHOR);
SQLQuery q = session.createSQLQuery(sql);
q.setCacheable(false);
// Set the expected output type
q.addEntity("StaffBean", StaffBeanImpl.class);
// Binding arguments to query
QueryPos qpos = QueryPos.getInstance(q);
qpos.add(author);
// Fetching all elements and returning them as a list
return (List<StaffBean>) QueryUtil.list(q, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS);
} catch(Exception e) {
e.printStackTrace();
} finally {
closeSession(session);
}
return null;
}
}
You can then call this method from your ArticleServiceImpl, whether it is to make a local or a remote API.
Note: it is hack. This is not a perfectly clean way to retrieve data, but it is the "less bad" you can do if you want to use Liferay's Service Builder.

liferay-6.1 - Implement own service

Hey I have create my own service.xml with student. Now o want to add my own searchByName method for student. can you please explain me what to write in StudentLocalServiceImpl.
public class StudentLocalServiceImpl extends StudentLocalServiceBaseImpl {
/*
* NOTE FOR DEVELOPERS:
*
*/
public List<Student> getAll() throws SystemException {
return studentPersistence.findAll();
}
public Student getStudentByName(String name) {
return studentPersistence.
}
// I have created one method getAll. I need help for the another one.
Thanks in Advance.
You would first declare this as a "finder" element in the service.xml within the entity you defined.
e.g.
<finder name="Name" return-type="Student">
<finder-column name="name" />
</finder>
The return-type could also be Collection if wanting a List<Student> as the return type, if name is not unique.
<finder name="Name" return-type="Collection">
<finder-column name="name" />
</finder>
You can also state a comparison operator for the column:
<finder name="NotName" return-type="Collection">
<finder-column name="name" comparator="!=" />
</finder>
A finder can actually declare a unique index as well to be generated on this relation (will be applied to the DB table) by specifying the unique="true" attribute on the finder:
<finder name="Name" return-type="Student" unique="true">
<finder-column name="name" />
</finder>
With this definition and after re-runing ant build-service the studentPersistence will contain new methods using the name of the finder found in the xml element appended with a prefix: countBy, findBy, fetchBy, removeBy, etc.
Finally, your serice method would only need to contain the following (based on the above):
public Student getStudentByName(String name) throws SystemException {
return studentPersistence.findByName(name);
}
HTH

Two distinct associations instead of one

I have two related entities (I've added all possible data annotations, but it seems that ODataConventionModelBuilder ignores them):
public class Examination
{
[InverseProperty("Examination")]
public virtual ICollection<Variable> Variables { get; set; }
}
public abstract class Variable
{
[Required]
public int? ExaminationId { get; set; }
[Required]
[ForeignKey("ExaminationId")]
[InverseProperty("Variables")]
public virtual Examination Examination { get; set; }
}
Here is an OData model generation (it seems, that HasMany() and HasRequired() doesn't affect resulting model):
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Examination>("Examinations");
modelBuilder.EntitySet<Variable>("Variables");
modelBuilder.Entity<Examination>().HasMany(e => e.Variables);
modelBuilder.Entity<Variable>().HasRequired(v => v.Examination);
return modelBuilder.GetEdmModel();
Resulting model:
<EntityType Name="Examination">
<NavigationProperty Name="Variables" Relationship="YoStat.Models.YoStat_Models_Examination_Variables_YoStat_Models_Variable_VariablesPartner" ToRole="Variables" FromRole="VariablesPartner"/>
</EntityType>
<EntityType Name="Variable" Abstract="true">
<NavigationProperty Name="Examination" Relationship="YoStat.Models.YoStat_Models_Variable_Examination_YoStat_Models_Examination_ExaminationPartner" ToRole="Examination" FromRole="ExaminationPartner"/>
</EntityType>
<Association Name="YoStat_Models_Examination_Variables_YoStat_Models_Variable_VariablesPartner">
<End Type="YoStat.Models.Variable" Role="Variables" Multiplicity="*"/>
<End Type="YoStat.Models.Examination" Role="VariablesPartner" Multiplicity="0..1"/>
</Association>
<Association Name="YoStat_Models_Variable_Examination_YoStat_Models_Examination_ExaminationPartner">
<End Type="YoStat.Models.Examination" Role="Examination" Multiplicity="1"/>
<End Type="YoStat.Models.Variable" Role="ExaminationPartner" Multiplicity="0..1"/>
</Association>
<EntityContainer Name="Container">
<EntitySet Name="Examinations" EntityType="YoStat.Models.Examination"/>
<EntitySet Name="Variables" EntityType="YoStat.Models.Variable"/>
<AssociationSet Name="YoStat_Models_Examination_Variables_YoStat_Models_Variable_VariablesPartnerSet" Association="YoStat.Models.YoStat_Models_Examination_Variables_YoStat_Models_Variable_VariablesPartner">
<End Role="VariablesPartner" EntitySet="Examinations"/>
<End Role="Variables" EntitySet="Variables"/>
</AssociationSet>
<AssociationSet Name="YoStat_Models_Variable_Examination_YoStat_Models_Examination_ExaminationPartnerSet" Association="YoStat.Models.YoStat_Models_Variable_Examination_YoStat_Models_Examination_ExaminationPartner">
<End Role="ExaminationPartner" EntitySet="Variables"/>
<End Role="Examination" EntitySet="Examinations"/>
</AssociationSet>
</EntityContainer>
As you can see, it generates two distinct associations instead of one. How to fix it? Thanks!
This looks like a bug in odata model builder, which always creates unidirectional navigation for each navigation property, while the better way is to look into the model to check if it's a bidirectional navigation first. I filed the bug at: http://aspnetwebstack.codeplex.com/workitem/623
However, I didn't find anything broken in client side with current behavior. WCF DS client will still generate correct proxy classes. Do you have any specific user scenario which is blocked by it? The information will help the bug to be better triaged. Thanks.