I've searched and debugged the last couple of days how to obtain the
translated version of a DomainModel object in a CommandController in Typo3 v8.7.
In Typo3 4.5/4.7 I've done the following:
- input: DomainModel in default language
- build a query that finds the record with the l10n_parent matching the
given domain model
- obtain a new domain model with the desired sys_language_uid
Unfortunately this does not work in Typo3 v8.7 any more. I always get
the domain model for the default language.
I've traced this down to the method Typo3DbBackend::doLanguageAndWorkspaceOverlay
called via Typo3DbBackend::getObjectDataByQuery
The query returns the correct (translated) row (seen in the debugger and also the mysql query log), but then the variable
$row gets overwritten in doLanguageAndWorkspaceOverlay no matter how I
set the querySettings setLanguageOverlayMode and setLanguageMode.
So what is the correct way to get a translated domain model in a
CommandController?
UPDATE:
I think I'm a step further. If I add ->setQueryLanguage(1) to the query settings, doLanguageAndWorkspaceOverlay() tries to fetch the translated record for language = 1. But in order to succeed I need to trick the FrontendGroupRestriction class by setting $GLOBALS['TSFE']->gr_list = "0,-2";.
The array returned by doLanguageAndWorkspaceOverlay() now contains all translated entries, except the uid, which is still the uid from the record in the main language. The uid of the translated record is stored in _LOCALIZED_UID.
Now my problem now is that I still get the record in the main laguage because DataMapper->mapSingleRow() (called via DataMapper->map()) has some kind of object cache and thus returns the object in the default language (because the uid is still the one of the record in the main language).
All this seems a little hackish. So again my question: what is the correct way to get a translated domain model in a CommandController?
thanks,
mika
p.s.: I've set up the second language in the backend and creating a translated record works just fine. My question is just how to I get an existing translated record in a CommandController.
alternative solution:
based on the solution above, I decided that I can do almost everything
on my own. So what I do now is
i) create an independend querybuilder for the according table:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);`
ii) select the record with the desired l10n_parent and sys_language_uid
$query = $queryBuilder->select('*')
->from($tableName)
->where($queryBuilder->expr()->eq('sys_language_uid', $langId))
->andWhere($queryBuilder->expr()->eq('l10n_parent', $parentUid))
->execute();
iii) fetch all records into an array
$rows = $query->fetchAll();
iv) invoke the DataMapper manually to get the object
$dataMapper = $this->objectManager->get(DataMapper::class);
$translated = $dataMapper->map($className, $rows);
I know it has nothing to do with the ModelRepository any more, but it
works quite fine for now...
that's all folks
My solution to the issue described above:
To avoid invoking the DataMapper as part of the query from
Typo3DbBackend, I used a raw query (argument for ->execute()) and get
back an array, that already went through language overlay etc.
BUT: in
the array the '_LOCALIZED_UID' is still available. So I overwrite the
uid with the value from '_LOCALIZED_UID' and invoke the DataMapper
manually. Quite cumbersome and very hackish to overcome the Typo3
backend shortcomings...
Related
I have been struggling for some time now and I can't really find anyone having done the same thing before.
I'm creating a backend module in TYPO3 7.6 which belongs to a shop extension.
The shop extension with the backend module was created with the extension builder. The shop has the following three models:
Product (products which can be ordered through the shop)
Productsorder (link to the customer)
ProductsorderPosition (the ordered product, the ordered amount and size and the link to the Productsorder)
The customers are of a model type from a different extension. These customers are linked to fe_users.
Now what I wanna do in my backend module is getting an overview to all these orders listed with the customer, some information about the fe_user and of course the product. I have created a sql-query, which does exactly that:
SELECT p.productname, p.productpriceperpiece,
pop.amount, pop.size,
h.name, h.address, h.zipcode, h.city, h.email, h.phone,
f.first_name, f.last_name, f.email
FROM `tx_gipdshop_domain_model_productorderposition` AS pop
JOIN `tx_gipdshop_domain_model_product` AS p ON pop.products = p.uid
JOIN `tx_gipdshop_domain_model_productsorder` AS po ON pop.productorder = po.uid
JOIN `tx_gipleasedisturbhotels_domain_model_hotel` AS h ON po.hotel = h.uid
JOIN `fe_users` AS f ON h.feuser = f.uid
If I use this query from the product repository it gives back the right amount of data records but they're of type product and the products are all "empty" (uid = 0 etc).
I've added an additional action for this in the product controller (getOrdersAction) and in the repository containing the query I've added a method findAllOrders.
I'm still rather a beginner in TYPO3 but I can somehow understand why it returns data sets of type Product when the query is called from the ProductRepository. But what I do not know is how I can get all the information from the query above and list it in the backend module.
I've already thought about moving the query to the ProductsorderPositionRepository but I would probably be faced with a similar problem, it would only return the information from the ProductsorderPosition and everything else would be left out.
Can someone point me to the right direction?
Would I need to create another model with separate repository and controller? Isn't there an easier way?
If you need more information, just ask! ;)
First of all, you are doing a joined query with subsets of data mixed from multiple tables. There is nothing against this.
Because of this, there is no "model" which has the mixed datasets.
If you are using the default query thing in a repository, the magic behind the repository assumes that the result of the query statement reflects the defined base model for this repository.
Moving the query function to another repository does not solve the problem.
You have not provided the code snippet you are executing the sql statement, so I assume you have used the query thing in the repository to execute the statement. Something like this:
$result = $query->statement('
SELECT p.productname, p.productpriceperpiece,
pop.amount, pop.size,
h.name, h.address, h.zipcode, h.city, h.email, h.phone,
f.first_name, f.last_name, f.email
FROM `tx_gipdshop_domain_model_productorderposition` AS pop
JOIN `tx_gipdshop_domain_model_product` AS p ON pop.products = p.uid
JOIN `tx_gipdshop_domain_model_productsorder` AS po ON pop.productorder = po.uid
JOIN `tx_gipleasedisturbhotels_domain_model_hotel` AS h ON po.hotel = h.uid
JOIN `fe_users` AS f ON h.feuser = f.uid', NULL);
or have used the query building stuff.
First solution
The first and simpliest solution would be to retrieve the result as plain php array. Before TYPO3 7.0 you could have done this by using this:
$query->getQuerySettings()->setReturnRawQueryResult(TRUE);
With TYPO3 7.0 this deprecated method was removed from the core.
The only way is to define the query and call $query->execute(TRUE); for now.
This should return the data in pure array form.
This is the simpliest one, but as we are in the extbase context this should not be suffering enough.
Second Solution - no, just an idea that I would try next
The second solution means that you have some work to do and is for now only a suggestion, because I have not tried this by myself.
Create a model with the properties and getter/setters for the result columns of your query
Create a corresponding repository
Third solution
Not nice, but if nothing else works, fall back to the old TYPO3 v4 query methods:
$GLOBALS['TYPO3_DB']->exec_SELECTgetRows([...]))
and replace this with the QueryBuilder in/for TYPO3 v8.
This is really not nice.
I hope I could direct you to the right way, even if not giving a full solving solution.
Query Hints not working in Eclipse Link 2.3.2/2.6.1 when used to fetch data from second level Cache
Used Hints,
#QueryHint(name = "javax.persistence.cache.retrieveMode", value = "USE"),
#QueryHint(name = "javax.persistence.cache.storeMode ", value = "USE")
Tried with below options.
1. Added JPA Hints to Named query itself
#NamedQuery(
name = TestEntity.FIND_BY_CODE,
query = "select t from Test t where t.code = :code",
hints = {
#QueryHint(name = "javax.persistence.cache.retrieveMode", value = "USE"),
#QueryHint(name = "javax.persistence.cache.storeMode ", value = "USE") })
2. Adding hints to the Entity Manager Itself after injecting it
em.setProperty("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE);
em.setProperty("javax.persistence.cache.storeMode", CacheRetrieveMode.USE);
3. Added JPA hints at the time of Query execution
em.createNamedQuery(TestEntity.FIND_BY_CODE,
AlertCategoryType.class).setHint("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE)
.setHint("javax.persistence.cache.storeMode", CacheStoreMode.USE)
.setParameter("code", code).getSingleResult();
None of the above usage of hints worked. Then i tried debugging on three different options what i found is,
the Data Base Query which formed after setting these hints is passing hints as key/value pair below.
eclipselink.query.hints => {javax.persistence.cache.retrieveMode=USE, javax.persistence.cache.storeMode=USE}
Where eclipselink.query.hints is the key even when we have set the JPA hints. This is something we don't have control over to change this.
But when i pass Eclipse Link provided hints as below , It started working as expected and the Results are fetched from Cache and not from the DB.
eclipselink.query.hints => {eclipselink.query-results-cache.size=500, eclipselink.query-results-cache=true}
It means That When we use Eclipse Link it only recognizes Eclipse Link provided hints according to the key[ above shown] we see in Query.
Please suggest any work around to get the JPA Hints working
Environment I'm using is
Eclispe Link 2.3.2/2.6.1
Runnin fin serve Glassfish 4.1[payara]
Java8/JEE7
The query hint you state is working (eclipselink.query-results-cache) is completely unrelated - it creates a new cache for the query results so that next time you execute that same query, the results are already there, so it does not need to execute the query again. This is outside (above an beyond) the second level cache.
The settings you refer to as not working affect the second level cache. Without more information, I'm going to state they are likely working as expected. Just because your query goes to the database does not mean the cache isn't being used. Caching entities is very different than caching the results to a query. If the query results are not cached, unless you have in-memory querying enabled, most read-all type queries must go to the database to determine what entities need to be built and returned. EclipseLink will then use those results to check the cache- if the entities already exist, they are returned as is - this avoids the overhead of rebuilding entities from the data.
You can check if your entity has been cached by using a em.find() or read query that uses the ID value. The cache is indexed by ID, so it will not need to go to the database to figure out which entities you want.
I have a Microsoft Access form that is being supplied (somehow) by a query. The query contains three tables linked together via their respective primary and foreign keys, and the form displays data quite happily.
The strange thing (as far as I'm concerned) is the ability of the form to then allow data entry using the query.
However, if the user creates a new record, the whole thing seems to have problems due to a required field in the [table2] table.
SELECT [table1].*
,[table2].JobNo
,[table2].PlannedDateOC
,[table3].DateJobStarted
,[table1].PlanNo
FROM (
[table1] LEFT JOIN [table2] ON [table1].PlanNo = [table2].PlanNo
)
LEFT JOIN [table3] ON [table2].JobNo = [table3].JobNo
ORDER BY [table2].PlannedDateOC
,[table1].PlanNo;
According to the users, this form worked perfectly prior to conversion from Access 97/2003 format to Access 2010 (2007 file format).
Could anyone clarify whether this (the fact that it should work) is legitimate, and if this process would work in either version of Access, if so? The concept of being able to use a query for data entry is quite alien to me.
Let me know if you need further clarification.
NOTE:
One thing of note, here, is that I did move some of the fields into the form header so that they were always visible as the rest of the form scrolls. I don't know if this will have any side effects on the performance of the form.
Above query will allow you to insert data into [table1] when all not null fields have their values and make sure only table1.fields are getting dirty when new record is inserted.
I am writing an application that exports data and serializes it to file for archiving old data.
There may be occasions where for some reason select data needs to be re-imported. This has been causing me a problem because of an identity column.
To get around this I am performing the work inside a transaction scope. Setting the Identity Insert On for that table and then updating my transaction e.g.
using (TR.TransactionScope scope = new TR.TransactionScope(TR.TransactionScopeOption.RequiresNew))
{
// allow transaction nbr to be inserted instead of auto generated
int i = context.ExecuteStoreCommand("SET IDENTITY_INSERT dbo.Transactions ON");
try
{
// check if it already exists before restoring
var matches = context.Transactions.Where(tr => tr.transaction_nbr == t.transaction_nbr);
if (matches.Count() == 0)
{
Transaction original = t;
context.Transactions.AddObject(original);
context.SaveChanges();
restoreCount++;
But I receive an exception saying:
Explicit value must be specified for identity column in table either when IDENTITY_INSERT >is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity >column.
I am assuming the entity framework is trying to do some sort of block insert without specifying the columns. Is there anyway to do this in the entity framework.
The object is large and has a number of associated entities that are also deserialized and need inserting so I want to let the entity framework do this if possible as it will save me a lot of extra work.
Any help is appreciated.
Gert Arnold - that was the answer to my question. Thank you.
I did read that elsewhere and had set the value in the object browser so thought this was suffice. I also double checked the value by right clicking the .edmx and Open With to see the details in an XML editor as suggested in another post.
When I checked the value in the XML editor initially it too was "None" so assumed this wasn't my problem. But I guess just going in there and saving rectified the problem first time around.
The second time round after I must have updated the model from the database I had to repeat this step upon your suggestion. But the second time round the StoreGeneratorPattern was different to the one set in the object browser so I needed to manually change it in the XML.
In my circumstance this is fine since normally the records are inserted via another mechanism so the identity is just getting in the way for me as the identity will always be inserted as an old (used to exist) identity value which is being temporarily restored.
Thanks for your help.
How can I write a query with ormlite instead of using .create or any other thing like that? Can you please show me how for this simple example :
SELECT name FROM client
EDIT since I can't answer myself :
I guess I had to search a little more , anyway I found how to do it with the QueryBuilder like this :
newDao.query(newDao.queryBuilder().where.eq("name",valueofname)
If someone knows how to write the full query that would be great , otherwise , I'll stick with this solution
How can I write a query with ormlite instead of using .create or any other thing like that?
Goodness, there are tons of documentation about how to do this on the ORMLite site. Here's the section on the query builder.
I'm not sure what you mean by "full query" but your example will work with some tweaks:
List<...> results = newDao.queryBuilder().where().eq("name",valueofname).query();
It does not make sense to just return the name since the Dao hierarchy is designed to return the specific Client object. If you just want the name the you can specify the name column only to return:
... clientDao.queryBuilder().selectColumns("name").where()...
That will return a list of Client objects with just the name field (and the id field if it exists) extracted from the database.
If you just want the name strings then you can use the RawResults feature.