How to bind select query as datasource in report? - axapta

I have a select statement that I want to bind as data source in a report.
I have not found a way to design an appropriate AOT query.
This is how it looks like in X++
public void insertData(date data = today())
{
BHNEmployeesOnDay ins;
EmplTable tbl;
CompanyInfo info;
BHNEmplAgreements Agreemnt;
BHNEmplAgreements Agreemnt2;
BHNEMPLHISTORYCOMPANY history;
BHNEMPLHISTORYCOMPANY history_test;
BHNDIVISIONTABLE division;
BHNPOSITIONTABLE position;
SysCompanyUserInfo sys1;
SysUserInfo sys2;
UserInfo usrInfo;
Date infinity = mkdate(1,1,1900);
;
delete_from ins;
while select * from tbl
join Info where info.dataAreaId == tbl.dataAreaId && info.BLX_companyForDW == 1
join sys1 where sys1.EmplId==tbl.EmplId && sys1.dataAreaId == tbl.dataAreaId
join sys2 where sys1.UserId==sys2.Id
join usrInfo where usrInfo.id==sys1.UserId
exists join history_test
where history_test.EmplId==tbl.EmplId && history_test.dataAreaId==tbl.dataAreaId
join Agreemnt where Agreemnt.HistoryId==history_test.HistoryId
&& (agreemnt.DateTo >= data || agreemnt.DateTo==infinity)
{
select firstonly *
from history order by history.DateFrom desc, Agreemnt2.DateFrom desc
where history.EmplId==tbl.EmplId && history.dataAreaId==tbl.dataAreaId
join Agreemnt2 where Agreemnt2.HistoryId==history.HistoryId
&& Agreemnt2.DateFrom<=data && (Agreemnt2.DateTo >= data || Agreemnt2.DateTo==infinity)
join division where division.DivisionId==agreemnt.DivisionId
join position where position.PositionId==agreemnt.PositionId;
ins.adddRecord(tbl.EmplId, tbl.Name_BHN, tbl.BirthDate, division.Name, position.FullName);
}
}
Currently I generate data into a table [during run() method of the report], then simply select from that table. So far only 1 person uses this report so it's not a problem, but if two people run the same report simultaneously, I'm gonna get dirty reads.
I know it's bad approach, but I'm out of ideas. I thought of making a View on T-SQL side and try to select from it - but I was told that it might not be detected or simply not transferred to other instances of our AX during export, so it has to be done on AX side.
How can I solve this?
Just in case this is query in T-SQL SQL query on pastebin

You could overwrite the report's fetch method and just use your X++ code as is to get the records and then use the report's send method to process them.
See here for an example.
The example uses a query object but you could easily swap that with your own X++ code - you just eventually have to call send for the records you want to be processed by the report.
Update:
For example you could just fetch any record of SalesTable and call send.
In this example a member variable salesTable is assumed so that you can access the current record in a display method in case you need it.
public boolean fetch()
{
boolean ret;
//ret = super();
;
select firstOnly salesTable;
this.send(salesTable);
return true;
}

Related

LINQ Entities Where clause not in correct place

Apparently I'm missing something with how LINQ to entities works. Hopefully one of you all can educate me.
Please try the below locally and let me know if you are seeing the same results. Something is really strange here...
Lets look at a very simple LINQ expression using navigation properties.
This was generated in LinqPad in a C# statement.
var result = (from ge in group_execution
where ge.automation_sequences.project.client_id == 1 && ge.parent_group_exec_id != null
select new
{
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
});
result.Dump();
OR, we can use joins...which will lead to the same bad results, but lets continue...
var result = (from ge in group_execution
join aseq in automation_sequences on ge.automation_sequence_id equals aseq.id
join p in project on aseq.project_id equals p.id
where p.client_id == 1 && ge.parent_group_exec_id != null
select new
{
ge.id,
ge.parent_group_exec_id,
p.client_id
});
result.Dump();
These very simple LINQ expressions generate the following SQL:
SELECT
[Filter1].[id1] AS [id],
[Filter1].[parent_group_exec_id] AS [parent_group_exec_id],
[Extent5].[client_id] AS [client_id]
FROM (SELECT [Extent1].[id] AS [id1], [Extent1].[automation_sequence_id] AS [automation_sequence_id], [Extent1].[parent_group_exec_id] AS [parent_group_exec_id]
FROM [dbo].[group_execution] AS [Extent1]
INNER JOIN [dbo].[automation_sequences] AS [Extent2] ON [Extent1].[automation_sequence_id] = [Extent2].[id]
INNER JOIN [dbo].[project] AS [Extent3] ON [Extent2].[project_id] = [Extent3].[id]
WHERE ([Extent1].[parent_group_exec_id] IS NOT NULL) AND (1 = [Extent3].[client_id]) ) AS [Filter1]
LEFT OUTER JOIN [dbo].[automation_sequences] AS [Extent4] ON [Filter1].[automation_sequence_id] = [Extent4].[id]
LEFT OUTER JOIN [dbo].[project] AS [Extent5] ON [Extent4].[project_id] = [Extent5].[id]
This baffles me. For the life of me I can't understand why LINQ is doing this. It's horrible, just look at the execution plan:
Now lets manually clean this up in SSMS and view the correct SQL and execution plan:
Much better, but how do we get LINQ to act this way?
Is anyone else seeing this? Has anyone else ever saw this and corrected it and if so how?
Thanks for looking into this.
UPDATE, attempting Chris Schaller fix:
var result = (from ge in group_execution
select new
{
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
}).Where(x=>x.client_id == 1 && x.parent_group_exec_id != null);
result.Dump();
Just so you all know I'm monitoring the SQL through SQL Server Profiler. If anyone knows of any issues doing it this way let me know.
UPDATE, a fix for JOINS, but not nav properties, and a cause, but why?
Here's your solution:
var result = (from ge in group_execution.Where(x=>x.parent_group_exec_id != null)
join aseq in automation_sequences on ge.automation_sequence_id equals aseq.id
join p in project on aseq.project_id equals p.id
where p.client_id == 1// && ge.parent_group_exec_id != null
select new
{
ge.id,
ge.parent_group_exec_id,
p.client_id
});
result.Dump();
Null checks shouldn't cause the framework to mess up like this. Why should I have to write it this way? This just seems like a defect to me in the framework. It will make my dynamic expressions a little bit more difficult to write, but maybe I can find a way.
Navigation Properties still mess up...so I'm still really sad. Picture below:
var result = (from ge in group_execution.Where(x=>x.parent_group_exec_id != null)
where ge.automation_sequences.project.client_id == 1// && ge.parent_group_exec_id != null
select new
{
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
});
result.Dump();
move your where clause to after you have defined the structure of the select statement
var result = (from ge in group_execution
select new
{
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
}).Where(x => x.client_id == 1 && x.parent_group_exec_id != null)
result.Dump();
Remember that Linq-to-entities flattens the results of queries to execute as SQL and then hydrates the object graph from those results.
When your query uses navigation properties or joins the query parser has to allow for zero results from those sub queries (Extents) to make sure that all columns that are required in the output and any interim processing are represented. By explicitly specifying a filter on a table for != null early in the query the parser knows that there is no further possibility that the field and any relationships linked by that field will be null, until then the parser prepares the query as if the joins will return null results
It is worth checking, but i wonder if UseDatabaseNullSemantics has anything to do with this?
Try:
dbContext.Configuration.UseDatabaseNullSemantics = false
In Linq, we can specify Where clauses as often as we like, improve the resulting SQL we should filter early in the query and granularly.
The parser engine is optimized to follow and implement your query sequentially, and generate good SQL at the end of it. Don't try to write linq-to-entities the same way that you structure your SQL, I know it's counter intuitive because the syntax is similar
A good technique is to assume that before each clause all the records from the previous statements have been loaded into memory, and that the next operation will affect all of those records. So you want to reduce the records before each additional operation by specifying a filter before moving on to the next clause
In general, if you have a filter condition based on the root table, apply this to the query before define all other joins and filters and even selects, you will get much cleaner sql.

Update multiple records in same table

I want to update multiple SalesQuotationLines the match Quotation Id X.
salesQuotationLine = salesQuotationLine::find(quotationId,true);
salesQuotationLine.selectForUpdate(true);
if(salesQuotationLine) {
ttsBegin;
SalesQuotationLine.Field = newFieldValue;
salesQuotationLine.update();
ttscommit;
The problem is, this is only updating the first record that is found within the find method.
How can I make sure, all records that match the QuotationID are being updated?
you can use this code:
while select forupdate salesQuotationLine
where salesQuotationLine.quotationId == quotationId
{
salesQuotationLine..Field = newFieldValue;
ttsbegin;
salesQuotationLine.update();
ttscommit;
}
Or can Use _update_recordset_
ttsbegin;
update_recordset salesQuotationLine
setting
Field = newFieldValue
where salesQuotationLine.quotationId == quotationId
ttscommit;
I hope to understock the question.
Dynanics AX 2012 provides way to use X++ SQL statements to enhance performance. This option is update_recordset which enables you to update multiple rows in a single trip to the server:
update_recordset salesQuotationLine
setting
Field = newFieldValue
where salesQuotationLine.quotationId == quotationId;

Perform method action based on duplicate values

I have a table, which i am creating a method on, i want run on action based on duplicatie values from 2 fields.
For example:
I have Table A which contains these fields:
IncidentDescription
Identifier
Now i want to run a certain action in the method based on the following criteria:
If the IncidentDescription already exists in another row in the same table, but only if the Identifier is different. (So it doesn't run the action if only 1 line with the IncidentDescription exists)
How can i solve this? Is it possible to accomplish this in an if statement?
Or is there a possibility/is it better to run a "while select" query, and (if it exists) run a count method based on the count result (>1).
EDIT:
I am trying to create the query as following:
select count (IncidentDescription) from TableA;
{
// I am trying to convert the result, because it gives me the error: "Operand types are not compatible with the operator, i am not sure how to make this.
serialCount = str2num(IncidentDescription);
if (IncidentDescription > 1)
//Action to go here;
}
I will build in the Identifier later.
Please use the following code as a hint and modify it according to your requirements:
TableA tableA;
TableA tableAJoin;
;
select firstOnly tableA
join tableAJoin
where tableA.IncidentDescription == tableAJoin.IncidentDescription
&& tableA.Identifier != tableAJoin.Identifier;
if (tableA.RecId)
{
info(strFmt("%1 - %2", tableA.Identifier, tableA.IncidentDescription));
info(strFmt("%1 - %2", tableAJoin.Identifier, tableAJoin.IncidentDescription));
}
If you need this check for displayOption method on a form datasource (where tableA is a datasource name), then modify it as follows
public void displayOption(Common _record, FormRowDisplayOption _options)
{
TableA tableACurrent;
TableA tableALocal;
;
tableACurrent = _record;
select firstOnly RecId from tableALocal
where tableALocal.IncidentDescription == tableACurrent.IncidentDescription
&& tableALocal.Identifier != tableACurrent.Identifier;
if (tableALocal.Identifier)
{
//record has dublicates
...
}
super(_record, _options);
}

Hive Query with UDF

I have some encrypted data in an HDFS csv, that I've created a Hive table for, and I want to run a Hive query that first encrypts the query param, then does the lookup. I have a UDF that does encryption as follows:
public class ParamEncrypt extends UDF {
public Text evaluate(String name) throws Exception {
String result = new String();
if (name == null) { return null; }
result = ParamData.encrypt(name);
return new Text(result);
}
}
Then I run the Hive query as:
select * from cc_details where first_name = encrypt('Ann');
The problem is, it's running encrypt('Ann') across every single record in the table. I want it do the encryption once, then do the matchup. I've tried:
select * from cc_details where first_name in (select encrypt('Ann') from cc_details limit 1);
But Hive doesn't support IN or select queries in the where clause.
What can I do?
Can I do something like:
select encrypt('Ann') as ann from cc_details where first_name = ann;
That also doesn't work because the query parser throws an error saying ann is not a known column
Finally got it with a right outer join as
select * from cc_details ssn_tbl
right outer join ( select encrypt('850-37-8230','ssn') as ssn
from cc_details limit 1) ssn_tmp
on (ssn_tbl.ssn = ssn_tmp.ssn);
I think what you are looking for is an annotation #UDFType(deterministic = true) on your UDF. It's definitely available on the Generic UDFs, you can check if it's available for regular UDF like you have created. If not, just convert your UDF to GenericUDF. You can read about it on this blog post that I wrote a while back.
Another way to do it (and actually the way I ended up going with), is by caching the result of the encryption. It's actually faster this way, because with the join, you get a separate set of map-reduce jobs, which slows down the overall execution time.
it's like this:
private static String result = null;
public Text evaluate(String data) {
if (result == null) {
result = Data.encrypt(data);
}
return new Text(result);
}

Linq and SQL query comparison

I have this SQL query:
SELECT Sum(ABS([Minimum Installment])) AS SumOfMonthlyPayments FROM tblAccount
INNER JOIN tblAccountOwner ON tblAccount.[Creditor Registry ID] = tblAccountOwner.
[Creditor Registry ID] AND tblAccount.[Account No] = tblAccountOwner.[Account No]
WHERE (tblAccountOwner.[Account Owner Registry ID] = 731752693037116688)
AND (tblAccount.[Account Type] NOT IN
('CA00', 'CA01', 'CA03', 'CA04', 'CA02', 'PA00', 'PA01', 'PA02', 'PA03', 'PA04'))
AND (DATEDIFF(mm, tblAccount.[State Change Date], GETDATE()) <=
4 OR tblAccount.[State Change Date] IS NULL)
AND ((tblAccount.[Account Type] IN ('CL10','CL11','PL10','PL11')) OR
CONTAINS(tblAccount.[Account Type], 'Mortgage')) AND (tblAccount.[Account Status ID] <> 999)
I have created a Linq query:
var ownerRegistryId = 731752693037116688;
var excludeTypes = new[]
{
"CA00", "CA01", "CA03", "CA04", "CA02",
"PA00", "PA01", "PA02", "PA03", "PA04"
};
var maxStateChangeMonth = 4;
var excludeStatusId = 999;
var includeMortgage = new[] { "CL10", "CL11", "PL10", "PL11" };
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment).ToList()
.Sum(minimumInstallment =>
Math.Abs((decimal)(minimumInstallment)));
return sum;
Are they equal/same ? I dont have records in db so I cant confirm if they are equal. In SQL there are brackets() but in Linq I didnt use them so is it ok?
Please suggest.
It is not possible for us to say anything about this, because you didn't show us the DBML. The actual definition of the mapping between the model and the database is important to be able to see how this executes.
But before you add the DBML to your question: we are not here to do your work, so here are two tips to find out whether they are equal or not:
Insert data in your database and run the queries.
Use a SQL profiler and see what query is executed by your LINQ provider under the covers.
If you have anything more specific to ask, we will be very willing to help.
The brackets will be generated by LINQ provider, if necessary.
The simplest way to check if the LINQ query is equal to the initial SQL query is to log it like #Atanas Korchev suggested.
If you are using Entity Framework, however, there is no Log property, but you can try to convert your query to an ObjectQuery, and call the ToTraceString method then:
string sqlQuery = (sum as ObjectQuery).ToTraceString();
UPD. The ToTraceString method needs an ObjectQuery instance for tracing, and the ToList() call already performs materialization, so there is nothing to trace. Here is the updated code:
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment);
string sqlQuery = (sum as ObjectQuery).ToTraceString();
Please note that this code will not perform the actual query, it is usable for testing purposes only.
Check out this article if you are interested in ready-for-production logging implementation.
There can be a performance difference:
The SQL query returns a single number (SELECT Sum...) directly from the database server to the client which executes the query.
In your LINQ query you have a greedy operator (.ToList()) in between:
var sum = (...
...
select account.MinimumInstallment).ToList()
.Sum(minimumInstallment =>
Math.Abs((decimal)(minimumInstallment)));
That means that the query on the SQL server does not contain the .Sum operation. The query returns a (potentially long?) list of MinimumInstallments. Then the .Sum operation is performed in memory on the client.
So effectively you switch from LINQ to Entities to LINQ to Objects after .ToList().
BTW: Can you check the last proposal in your previous question here which would avoid .ToList() on this query (if the proposal should work) and would therefore be closer to the SQL statement.

Resources