I've been trying to create a DB model using GreenDAO. the problem started when I tried to create more than one relationship between a different tables.
basically, I have a Message table, a Conversation table and a User table.
the User has a list of messages, and the message has a parent conversation.
I tried writing this code for creating the DB:
private static void addUser(Schema schema) {
user = schema.addEntity("User");
userId = user.addIdProperty().getProperty();
user.addStringProperty("facebookId").unique().index();
user.addStringProperty("firstName");
user.addStringProperty("lastName");
user.addStringProperty("fullName");
user.addStringProperty("photoUrl");
}
private static void addMessage(Schema schema) {
message = schema.addEntity("Message");
messageId = message.addIdProperty().getProperty();
message.addStringProperty("messageId").primaryKey();
message.addDateProperty("date");
message.addStringProperty("content");
message.addStringProperty("typeString");
}
private static void addConversation(Schema schema) {
conversation = schema.addEntity("Conversation");
conversation.addIdProperty();
conversation.addStringProperty("conversationId");
// REST OF THE CODE
}
private static void fakeRelationship(Schema schema) {
Property author = message.addLongProperty("author").getProperty();
Property parent = message.addLongProperty("parent").getProperty();
message.addToOne(user, author);
message.addToOne(conversation, parent);
user.addToMany(message, author);
conversation.addToMany(message, parent);
}
after running this code, I got this error:
Exception in thread "main" java.lang.RuntimeException: Currently only single FK columns are supported: ToOne 'parent' from Message to Conversation
at de.greenrobot.daogenerator.ToOne.init3ndPass(ToOne.java:91)
at de.greenrobot.daogenerator.Entity.init3rdPassRelations(Entity.java:557)
at de.greenrobot.daogenerator.Entity.init3ndPass(Entity.java:550)
at de.greenrobot.daogenerator.Schema.init3ndPass(Schema.java:185)
at de.greenrobot.daogenerator.DaoGenerator.generateAll(DaoGenerator.java:94)
at de.greenrobot.daogenerator.DaoGenerator.generateAll(DaoGenerator.java:79)
at com.glidetalk.dao.generator.GlideDaoGenerator.main(GlideDaoGenerator.java:27)
does this actually meen that I can't create more than one relation for each table in my DB?!
do I have to write everything manually?
What you try to do is supported by greenDAO and your code looks good, too. I copied it into my workspace and it executed perfectly fine. So I guess something is wrong in the code you left out.
Related
I have this OColumn partner_name = new OColumn("Partner", OVarchar.class).setLocalColumn(); in my sale order model class with odoo functional method that depends on partner_id column. I would like to search the partner_name in my list using that column partner_name, but I'm a little confused on how to achieve this. Please needed some help.
This is what I've tried:
BaseFragment
#Override
public void onViewBind(View view, Cursor cursor, ODataRow row) {
getPartnerIds(row);
OControls.setText(view, R.id.partner_name, row.getString("partner_name")); // displays false
....
}
}
private void getPartnerIds(ODataRow row){
OValues oValues = new OValues();
oValues.put("partner_id", row.get("partner_id"));
saleOrder.storeManyToOne(oValues);
}
updated:
I noticed that even though I created
#Odoo.Functional(method = "storeManyToOne", store = true, depends = {"partner_id"})
OColumn partner_name = new OColumn("Partner", OVarchar.class).setLocalColumn();
no column was created.
Updated:
partner_name column with odoo functional
Edit: Just place the 'if (type.isAssignableFrom(Odoo.Functional.class)'
before the 'if (type.getDeclaringClass().isAssignableFrom(Odoo.api.class))' to have the correct values.
Define the partner_name field like below:
#Odoo.Functional(method="storePartnerName", store=true, depends={"partner_id"})
OColumn partner_name = new OColumn("Partner name", OVarchar.class)
.setLocalColumn();
public String storePartnerName(OValues values) {
try {
if (!values.getString("partner_id").equals("false")) {
JSONArray partner_id = new JSONArray(values.getString("partner_id"));
return partner_id.getString(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return "false";
}
You can simply get the partner_name using:
row.getString("partner_name")
EDIT:
Note that database is created when you first time run your application, or when you clean your data from app
setting. You need to clean application data everytime when you update your database column.
If the column was added after the database creation, it will not be added to the corresponding table. This is because the database is not upgraded. To fix this issue you can:
Clean application data to update your database column
Remove user account (This will delete database) or reinstall the application to recreate the database.
Or you can change DATABASE_VERSION in odoo/datas/OConstants then override onModelUpgrade method in sale order model and upgrade the table manually (alter sale order table and add the partner name column using SQL query: ALTER TABLE sale_order ADD partner_name VARCHAR(100)).
When a new sale order is created and synchronized, the partner name should be computed and stored automaticaly.
I noticed that the partner name was not set for existing records after synchrinization so I added another SQL query to compute and set the value of partner name for old records.
Example:
#Override
public void onModelUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE sale_order ADD partner_name VARCHAR(100)");
db.execSQL("UPDATE sale_order SET partner_name = (SELECT name from res_partner WHERE _id=partner_id) WHERE partner_name IS NULL AND partner_id IS NOT NULL");
}
Edit (config):
using the new configuration you will get the following error (which will prevent creating fields using annotations):
W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)' on a null object reference
W/System.err: at com.odoo.core.orm.OModel.compatibleField(OModel.java:349)
CODE:
if (type.getDeclaringClass().isAssignableFrom(Odoo.api.class)) {
Try to remove .getDeclaringClass()
Edit: not all partner names are shown
There is a org.json.JSONException error that happens when it try to convert partner_id string to a JSON array.
W/System.err: org.json.JSONException: Unterminated array at character 12 of [114.0, UH PARTNER]
The error happens when it try to convert names containing spaces. To avoid that you can cast partner_id string to a list of objects.
In partnerName method, replace the following code:
JSONArray partner_id = new JSONArray(values.getString("partner_id"));
return partner_id.getString(1);
With:
List<Object> partner_id = (ArrayList<Object>) values.get("partner_id");
return partner_id.get(1) + "";
My Problem:
I'm struggling to eliminate the compiling error on the following Room #Query statement in a Room DAO. As you can see, the SQLite query statement is joining various fields from different tables. The missing fields identified by the error are a part of the Notes class constructor identified in the List type for the method. I think I need to change the List type identified. If I'm right, I need some guidance/suggestion on how I should resolve it. Do I need to create a new Class and DAO with just those specific fields queried? Or maybe just a class since there is not table specific to these fields only. The error is:
error: The columns returned by the query does not have the fields [commentID,questionID,quoteID,termID,topicID,deleted] in com.mistywillow.researchdb.database.entities.Notes even though they are annotated as non-null or primitive. Columns returned by the query: [NoteID,SourceID,SourceType,Title,Summary]
List getNotesOnTopic(String topic);
#Query("SELECT n.NoteID, s.SourceID, s.SourceType, s.Title, c.Summary FROM Comments as c " +
"LEFT JOIN Notes as n ON n.CommentID = c.CommentID " +
"LEFT JOIN Sources as s ON n.SourceID = s.SourceID " +
"LEFT JOIN Topics as t ON n.TopicID = t.TopicID WHERE t.Topic = :topic AND n.Deleted = 0")
List<Notes> getNotesOnTopic(String topic);
What I'm trying to do:
I'm attempting to convert and existing Java desktop app with an embedded an SQLite database. The above query does work fine in that app. I only want to pass field data from these tables.
What I've tried:
I've done some googling and visited some forums for the last few days (e.g. Android Forum, Developer.Android.com) but most of the Room #Query examples are single table full field queries (e.g. "Select * From table"). Nothing I found yet (there is probably something) quite addresses how and what to do if you are joining and querying only specific fields across tables.
I think I may have fixed my issue. I just created a new class called SourceTable and designated the queried fields in the constructor. The only catch was I, according to a follow up error, was that the parameters had to match the field names.
public class SourcesTable {
private int NoteID;
private int SourceID;
private String SourceType;
private String Title;
private String Summary;
public SourcesTable(int NoteID, int SourceID, String SourceType, String Title, String Summary){
this.NoteID = NoteID;
this.SourceID = SourceID;
this.SourceType = SourceType;
this.Title = Title;
this.Summary = Summary;
}
}
and then I update my list method:
List<SourcesTable> getNotesOnTopic(String topic);
I want to use GreenDAO for persistence, but I cannot get it to persist my data.
The data is saved and loaded correctly as long as the application is not restarted.
Once i swipe the app away and reopen it from scratch, GreenDAO does not see the previous data (both on the emulator and real device).
This is my entity:
#Entity
public class TestSingleEntity {
#Id(autoincrement = true)
Long id;
int someNumber;
public TestSingleEntity(int someNumber) {
this.someNumber = someNumber;
}
#Generated(hash = 787203968)
public TestSingleEntity(Long id, int someNumber) {
this.id = id;
this.someNumber = someNumber;
}
#Generated(hash = 1371368161)
public TestSingleEntity() {
}
// ... some more stuff
}
This is how I insert entities to database:
Random rnd = new Random();
TestSingleEntity singleEntity = new TestSingleEntity();
singleEntity.setSomeNumber(rnd.nextInt());
DaoSession session = ((MyApp)getApplication()).getDaoSession();
TestSingleEntityDao dao = session.getTestSingleEntityDao();
dao.insert(singleEntity);
Log.d("tgd", "Inserted an entity with id " + singleEntity.getId());
And this is how I read them:
Query query = dao.queryBuilder().orderAsc(TestSingleEntityDao.Properties.SomeNumber).build();
StringBuilder builder = new StringBuilder();
List<TestSingleEntity> result = query.list();
Log.d("size", result.size());
for (TestSingleEntity testSingleEntity : result) {
Log.d("entity", testSingleEntity.toString());
}
As I have said, as long as I stay in the app (moving around in different activities is okay), everytime the insert is called, a new entity with a new ID is created. As soon as I relaunch the app, it goes back to square one.
The setup was taken directly from the GitHub page. What am I doing wrong? Thanks
Disclaimer: GreenDAO has gone through major changes since I last used it so this is purely based on reading their code on the github.
Apparently GreenDAO's poorly documented DevOpenHelper drops all tables on upgrade, so the real question is why is onUpgrade being called when clearly there hasn't been a change to the schema version. Try to look for the log line that mentions dropping the tables as described in the template for DevOpenHelper.
Regardless, using OpenHelper instead should fix the issue.
In the greendao FAQs it says "Starting from greenDAO there’s limited support for String primary keys." http://greendao-orm.com/documentation/technical-faq/
I can't find anywhere that says how to do this.
I am using Guids as my primary key in a server application, and want to be able to generate new data remotely from an android device and upload this back to the server. The database on the android device is in sqlite and uses greenDAO to generate POJOs and data access layer. I am using Guids to avoid primary key collisions when data is uploaded to the server. I am storing the Guids as strings.
There is some more advice on the greendao website that says I should create a secondary field holding the string and still use the long primary key favoured by greendao, but this means that I have to reconnect all my database relationships when I import data from the server to the app which is a pain. Would much rather just continue to use the string primary keys if that is possible.
Can anybody tell me how to do this?
Here is some example code...
In my generator (I've removed most of the fields for clarity):
private static void addTables(Schema schema)
{
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitIntId = forSale.addLongProperty("unitIntId").getProperty();
forSale.addToOne(unit, unitIntId);
}
private static Entity addForSale(Schema schema)
{
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addIdProperty();
thisEntity.addStringProperty("forSaleId");
thisEntity.addFloatProperty("currentPriceSqFt");
thisEntity.addStringProperty("unitId");
return thisEntity;
}
private static Entity addUnit(Schema schema)
{
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addIdProperty();
thisEntity.addStringProperty("unitId");
thisEntity.addStringProperty("name");
return thisEntity;
}
In my android application I download all the data from the server. It has relationships based on the GUID id's. I have to reattach these to the int Id's I created in the generator like this:
//Add relations based on GUID relations
//ForSale:Units
for(ForSale forSale:Globals.getInstance().forSales)
{
if (forSale.getUnitId() != null && forSale.getUnit() == null)
{
for(Unit unit:Globals.getInstance().units)
{
if (forSale.getUnitId().equals(unit.getUnitId()))
{
forSale.setUnit(unit);
break; //only need the first one
}
}
}
}
So I end up having two sets of Id's linking everything, the int one for greendao and the string (guid) one that will work when it gets uploaded back to the server. Must be an easier way!
Try this:
private static void addTables(Schema schema) {
Entity unit = addUnit(schema);
Entity forSale = addForSale(schema);
Property unitId = forSale.addStringProperty("unitId").getProperty();
forSale.addToOne(unit, unitId);
}
private static Entity addForSale(Schema schema) {
Entity thisEntity = schema.addEntity("ForSale");
thisEntity.addStringProperty("forSaleId").primaryKey();
thisEntity.addFloatProperty("currentPriceSqFt");
return thisEntity;
}
private static Entity addUnit(Schema schema) {
Entity thisEntity = schema.addEntity("Unit");
thisEntity.addStringProperty("unitId").primaryKey();
thisEntity.addStringProperty("name");
return thisEntity;
}
I don't know if the ToOne-Mapping will work with strings, though. If it doesn't you can add some methods for getting the related objects in the KEEP-SECTIONS.
I'm tring to make join in two tables and get all columns in both, I did this:
QueryBuilder<A, Integer> aQb = aDao.queryBuilder();
QueryBuilder<B, Integer> bQb = bDao.queryBuilder();
aQb.join(bQb).prepare();
This equates to:
SELECT 'A'.* FROM A INNER JOIN B WHERE A.id = B.id;
But I want:
SELECT * FROM A INNER JOIN B WHERE A.id = B.id;
Other problem is when taking order by a field of B, like:
aQb.orderBy(B.COLUMN, true);
I get an error saying "no table column B".
When you are using the QueryBuilder, it is expecting to return B objects. They cannot contain all of the fields from A in B. It will not flesh out foreign sub-fields if that is what you mean. That feature has not crossed the lite barrier for ORMLite.
Ordering on join-table is also not supported. You can certainly add the bQb.orderBy(B.COLUMN, true) but I don't think that will do what you want.
You can certainly use raw-queries for this although it is not optimal.
Actually, I managed to do it without writing my whole query as raw query. This way, I didn't need to replace my query builder codes (which is pretty complicated). To achieve that, I followed the following steps:
(Assuming I have two tables, my_table and my_join_table and their daos, I want to order my query on my_table by the column order_column_1 of the my_join_table)
1- Joined two query builders & used QueryBuilder.selectRaw(String... columns) method to include the original table's + the columns I want to use in foreign sort. Example:
QueryBuilder<MyJoinTable, MyJoinPK> myJoinQueryBuilder = myJoinDao.queryBuilder();
QueryBuilder<MyTable, MyPK> myQueryBuilder = myDao.queryBuilder().join(myJoinQueryBuilder).selectRaw("`my_table`.*", "`my_join_table`.`order_column` as `order_column_1`");
2- Included my order by clauses like this:
myQueryBuilder.orderByRaw("`order_column_1` ASC");
3- After setting all the select columns & order by clauses, it's time to prepare the statement:
String statement = myQueryBuilder.prepare().getStatement();
4- Get the table info from the dao:
TableInfo tableInfo = ((BaseDaoImpl) myDao).getTableInfo();
5- Created my custom column-to-object mapper which just ignores the unknown column names. We avoid the mapping error of our custon columns (order_column_1 in this case) by doing this. Example:
RawRowMapper<MyTable> mapper = new UnknownColumnIgnoringGenericRowMapper<>(tableInfo);
6- Query the table for the results:
GenericRawResults<MyTable> results = activityDao.queryRaw(statement, mapper);
7- Finally, convert the generic raw results to list:
List<MyTable> myObjects = new ArrayList<>();
for (MyTable myObject : results) {
myObjects.add(myObject);
}
Here's the custom row mapper I created by modifying (just swallowed the exception) com.j256.ormlite.stmt.RawRowMapperImpl to avoid the unknown column mapping errors. You can copy&paste this into your project:
import com.j256.ormlite.dao.RawRowMapper;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.table.TableInfo;
import java.sql.SQLException;
public class UnknownColumnIgnoringGenericRowMapper<T, ID> implements RawRowMapper<T> {
private final TableInfo<T, ID> tableInfo;
public UnknownColumnIgnoringGenericRowMapper(TableInfo<T, ID> tableInfo) {
this.tableInfo = tableInfo;
}
public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException {
// create our object
T rowObj = tableInfo.createObject();
for (int i = 0; i < columnNames.length; i++) {
// sanity check, prolly will never happen but let's be careful out there
if (i >= resultColumns.length) {
continue;
}
try {
// run through and convert each field
FieldType fieldType = tableInfo.getFieldTypeByColumnName(columnNames[i]);
Object fieldObj = fieldType.convertStringToJavaField(resultColumns[i], i);
// assign it to the row object
fieldType.assignField(rowObj, fieldObj, false, null);
} catch (IllegalArgumentException e) {
// log this or do whatever you want
}
}
return rowObj;
}
}
It's pretty hacky & seems like overkill for this operation but I definitely needed it and this method worked well.