- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Arithmetic operation resulted in an overflow
Joined: 28-Nov-2008
3.0 - Oracle ODP - Adapter
Calling GetScalar with an aggregate function of Avg. The database type is Number. In LLBL Designer, it's decimal 38.
The following query is being generated and executed:
Query: SELECT * FROM (SELECT TRUNC(AVG("TS"."DASHBOARD_V"."AGE"), 2 AS "Age" FROM "TS"."DASHBOARD_V" WHERE ( "TS"."DASHBOARD_V"."ASSIGNED_TO" = 1)) WHERE rownum <= 1
Getting an exception of "Arithmetic operation resulted in an overflow" as soon as we call GetScalar.
If we run the query manually, we get back the correct avg.
Joined: 28-Nov-2005
Please post the full exception message and stack trace. Also we need to know the runtime library version (http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=7722).
What do you mean by "If we run the query manually, we get back the correct avg"?
Joined: 28-Nov-2008
SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException was caught
Message=An exception was caught during the execution of a retrieval query: Arithmetic operation resulted in an overflow.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception.
Source=SD.LLBLGen.Pro.ORMSupportClasses.NET20
RuntimeBuild=05262010
RuntimeVersion=3.0.0.0
QueryExecuted=
Query: SELECT * FROM (SELECT TRUNC(AVG("TS"."DASHBOARD_V"."AGE"), 28) AS "Age" FROM "TS"."DASHBOARD_V" WHERE ( "TS"."DASHBOARD_V"."ASSIGNED_TO" = :p1)) WHERE rownum <= 1
Parameter: :p1 : String. Length: 30. Precision: 0. Scale: 0. Direction: Input. Value: "PAPP".
StackTrace:
at SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar()
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.ExecuteScalarQuery(IRetrievalQuery queryToExecute)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityFields2 fields, IPredicate filter, IGroupByCollection groupByClause, IRelationCollection relations)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityField2 field, IExpression expressionToExecute, AggregateFunction aggregateToApply, IPredicate filter, IGroupByCollection groupByClause, IRelationCollection relations)
at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityField2 field, IExpression expressionToExecute, AggregateFunction aggregateToApply, IPredicate filter)
at CTS.RemotingImplementationClasses.GeneralFunctions.GetScalar(RemotingSession& session, IEntityField2 entityField, AggregateFunction aggegrateFunction, RelationPredicateBucket predicate) in D:\Development\BOLI\Projects\CTS\Source\BusinessObjects\RemotingImplementationClasses\Utility\GeneralFunctions.cs:line 35
InnerException: System.OverflowException
Message=Arithmetic operation resulted in an overflow.
Source=Oracle.DataAccess
StackTrace:
at Oracle.DataAccess.Types.DecimalConv.GetDecimal(IntPtr numCtx)
at Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i)
at Oracle.DataAccess.Client.OracleDataReader.GetValue(Int32 i)
at Oracle.DataAccess.Client.OracleCommand.ExecuteScalar()
at SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar()
Also, what I meant was that if we run the generated query directly in oracle using toad, it works and returns an avg number with decimal places.
Joined: 28-Nov-2005
If you look closely into the InnerException you will see the errors comes from ODP.Net trying to unboxing the decimal (Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i)).
What large are your DB values actually? (Do you have over estimate the precision but you don't need it?). From pasts threads, there is no much the framework can do about it. Using TypeConverters is not of much help here as the exception is raised in ODP side. See this for example: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=13264
Possible workarounds: - Look into your data to see if you actually use that large precision. See if you can truncate those values. - See if you can modify your schema to use more realistic precision. - You can override DataAccessAdatper's HandleValueReadErrors method in a partial class and put Oracle specific code to handle this. In that method you get the Exception, the dataReader and the values you should fill into.
Joined: 28-Nov-2008
The number that oracle is returning is
379.0439858454766241651487553126
So it must be a precision thing. How do I go about getting Oracle to limit the precision?
Joined: 28-Nov-2005
Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.
Second of all, I want to explain some things: As the OracleDecimal type is larger than the .Net System.Decimal (38 vs. 28 precision), you will receive potentially decimal overflows in two places: entity fetches and aggregates. For instance, if you have a value 379.0439858454766241651487553126 in your database, this test will fail:
[TestMethod]
public void ReproduceDecimalOverflowForEntityFetch()
{
TableaEntity t31 = new TableaEntity(1);
using (var adapter = new DataAccessAdapter())
{
adapter.FetchEntity(t31);
}
// at db: 379.0439858454766241651487553126, we expect an overflow exception
Assert.AreEqual( 379.0439858454766241651487553126, t31.B);
}
Note that the precision of that number is 31, that is why ODP.Net will raise an overflow, because it can't unbox in the IDataReader.
The other situation is when you fetch an Aggregate (like AVG). As you saw, LLBLGen Framework tries to avoid this emitting a TRUNC(..., 28 ). Nevertheless this doesn't eliminate the potential overflow, because it only trunc the decimal part, but no adjust the precision (I don't know any PL/SQL function that does that). So if the AVG returns 379.0439858454766241651487553126, the TRUNC(...,28 ) will return the same number, however it still has a precision=31. So that will raise an overflow exception from ODP as well. That is what you are experiencing. This is an example of that expected exception:
[TestMethod]
public void ReproduceDecimalOverflowForGetScalar()
{
decimal avg = decimal.Zero;
using (var adapter = new DataAccessAdapterOverflowSafe())
{
avg = (decimal)adapter.GetScalar(TableaFields.B, AggregateFunction.Avg);
}
// test the avg. We already get an exception at this point due to an ODP.Net decimal overflow
Assert.AreEqual(379.0439858454766241651487553126M, avg);
}
So, now go to the solutions. For the first scenario (entity fetches) you should override the DataAccessAdapter's **HandleValueReadErrors **method. I'm not that expert at ODP.Net but this worked for me:
protected override bool HandleValueReadErrors(IDataReader openReader, object[] toFill, Exception ex)
{
// get the actual OracleDataReader
OracleDataReader oraReader = (OracleDataReader)openReader;
// iterate the values and fillt them
for (int i = 0; i < toFill.Length; i++)
{
string typeAsString = oraReader.GetDataTypeName(i);
switch (typeAsString)
{
case "Decimal":
// get the decimal avoiding the unboxing overflow
decimal d = (decimal)(OracleDecimal.SetPrecision(oraReader.GetOracleDecimal(i), 28));
toFill[i] = d;
break;
default:
toFill[i] = openReader.GetValue(i);
break;
}
}
// this means that we handled the exception
return true;
}
For the second scenario (Aggregate fetches) it's different because the error comes directly from a command execute, and not from an IDataReader. That's why the TRUNC is emitted. So, the solution is emit a smaller value for the decimalPlaces parameter of the TRUNC function. For that you have create a custom derived OracleSpecificCreator, DynamicQueryEngine and DataAccessAdatper classes, and override the OracleSpecificCreator's **ConstructCallToAggregateWithFieldAsParameter **method. Similar to this:
protected override string ConstructCallToAggregateWithFieldAsParameter(AggregateFunction function, string fieldName)
{
string toReturn = string.Empty;
// this is the custom part. We will trunc all decimal results to only have 10 decimal places
int decimalPlaces = 10;
switch (function)
{
case AggregateFunction.Avg:
toReturn = string.Format("TRUNC(AVG({0}), {1})", fieldName, decimalPlaces);
break;
case AggregateFunction.AvgDistinct:
toReturn = string.Format("TRUNC(AVG(DISTINCT {0}), {1})", fieldName, decimalPlaces);
break;
...
}
return toReturn;
}
}
Sounds difficult? Well, I already have this coded for you. I'm attaching one file to this post. The only you have to do is: 1. Copy it to your DBSpecific project. 2. Change the first namespace of the file to your own DBSpecific one. 3. Add a reference to the Oracle.DataAccess assembly into your DBSpecific project. 4. Now you can use it in your code, it's pretty easy:
[TestMethod]
public void ReproduceDecimalOverflowForEntityFetch()
{
TableaEntity t31 = new TableaEntity(1);
using (var adapter = new DataAccessAdapterOverflowSafe())
{
adapter.FetchEntity(t31);
}
// at db: 379.0439858454766241651487553126, we expect the truncated value
Assert.AreEqual(379.0439858454766241651487553M, t31.B);
}
[TestMethod]
public void ReproduceDecimalOverflowForGetScalar()
{
decimal avg = decimal.Zero;
using (var adapter = new DataAccessAdapterOverflowSafe())
{
avg = (decimal)adapter.GetScalar(TableaFields.B, AggregateFunction.Avg);
}
// test the avg. As we trunc at our custom adapter, we expect only 10 decimal digits
Assert.AreEqual(379.0439858454M, avg);
}
Hope helpful
Filename | File size | Added on | Approval |
---|---|---|---|
OracleSpecificCreatorOverflowSafe.cs | 7,010 | 04-Aug-2011 21:49.07 | Approved |
Joined: 28-Nov-2008
daelmo wrote:
Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.
What do you mean by this? The column in the database is now a number(3). LLBL is generating this in the .net layer as Int16.
But when LLBL generates the query, it's inserting the trunc( ,28 ).
Who/what is turning this into a number with two much precision?
Joined: 28-Nov-2005
happyfirst wrote:
daelmo wrote:
Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.
What do you mean by this? The column in the database is now a number(3). LLBL is generating this in the .net layer as Int16.
I was just guessing coz I don't know what is the precision/scale of your columns. I was just pointing that in case you have that problem as well in entity fetches. So, don't worry about that.
happyfirst wrote:
But when LLBL generates the query, it's inserting the trunc( ,28 ).
Who/what is turning this into a number with two much precision?
The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).
Joined: 28-Nov-2008
daelmo wrote:
The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).
And where exactly is this failing? LLBL getting out the number or Oracle trying to get the number out to give to LLBL?
So how does somebody get an avg via an aggregrate function? What am I doing differently than others?
Do I need to actually UP the precision of my age so that it's a decimal?
Joined: 28-Nov-2005
happyfirst wrote:
daelmo wrote:
The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).
And where exactly is this failing? LLBL getting out the number or Oracle trying to get the number out to give to LLBL?
The second: ODP.Net trying to load the decimal. If you inspect the InnerException it comes to:
Oracle.DataAccess.Types.DecimalConv.GetDecimal(IntPtr numCtx) Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i) Oracle.DataAccess.Client.OracleDataReader.GetValue(Int32 i) Oracle.DataAccess.Client.OracleCommand.ExecuteScalar() SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar() in C:\Program Files\Solutions Design\LLBLGen Pro v3.1\SourceCode\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\Query\RetrievalQuery.cs
happyfirst wrote:
So how does somebody get an avg via an aggregrate function? What am I doing differently than others?
This actually happened before: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=17508 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=14441
In fact, the trunc workaround comes from this thread: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=4543
Some people just emit an additional DBFunctionCall to the GetScalar call to generate a custom trunc. some others never came into values so large, some others uses the code I gave you. So this is something we should live with since the new version of ODP.Net can unbox larger decimal values (oracledecimal precision > .net-decimal precision). You can search on the web to see that this is a common issue. People that uses directly ODP.Net (without DBProviderFactories) can place custom Oracle code to overcome this (like the one I posted in my earlier post).
happyfirst wrote:
Do I need to actually UP the precision of my age so that it's a decimal?
No, as I said the problem is that the OracleDecimal precision (38 ) is larger than the .Net decimal (28 ) and when the ODP IDataReader tries to load it, it fails due to the overflow. So the trunc in the sql is indeed needed. Have you tried my code in the above post?
Joined: 28-Nov-2008
I haven't tried your fixes yet because I'm still trying to grasp what the real issue is. The sql call is being generated already with a trunc ( , 28 ). So where is the 38 number coming from?
One of your fixes let's me customize that trunc, but if the current trunc 28 isn't working, it seems anything I do wouldn't work either.
We return decimals from Oracle all the time with no problems. Is the oracle average command what always returns a 38 number?
Joined: 28-Nov-2005
happyfirst wrote:
I haven't tried your fixes yet because I'm still trying to grasp what the real issue is. The sql call is being generated already with a trunc ( , 28 ). So where is the 38 number coming from?
Answered above:
daelmo wrote:
No, as I said the problem is that the OracleDecimal precision (38 ) is larger than the .Net decimal (28 ) and when the ODP IDataReader tries to load it, it fails due to the overflow. So the trunc in the sql is indeed needed. Have you tried my code in the above post?
happyfirst wrote:
One of your fixes let's me customize that trunc, but if the current trunc 28 isn't working, it seems anything I do wouldn't work either.
Please givie it a try and you will see. It isn't working coz, even if the trunc-28 is emited the number that comes from oracle has a precision of 31 (your result) so there are three digits remaining (overflowing). Do you get the idea? Even if you set the trunc to 15 could be a potential error if the integer part has a length greater than 13 for instance.
happyfirst wrote:
We return decimals from Oracle all the time with no problems. Is the oracle average command what always returns a 38 number?
Yes. As I said before, the problem is not Oracle directly, nor LLBLGen, it's ODP.Net unboxing the OracleDecimal to .Net decimal.
Joined: 28-Nov-2008
I'm trying your attached file but I'm getting a compile error
Error 1 'SD.LLBLGen.Pro.DQE.Oracle.OracleSpecificCreatorOverflowSafe. ConstructCallToAggregateWithFieldAsParameter(SD.LLBLGen.Pro.ORMSupportClasses .AggregateFunction, string)': no suitable method found to override D:\Development\Project\BusinessObjects\DatabaseSpecific\DataAccessAdapterOverflowSafe.cs 88 35 TSDBSpecific
Is this code LLBL 3.0 compatible?
Joined: 28-Nov-2005
My mistake. That is for v3.1 which changed that method actually. Attached is a version for v3.0.
Filename | File size | Added on | Approval |
---|---|---|---|
OracleSpecificCreatorOverflowSafe.cs | 7,470 | 06-Aug-2011 00:05.23 | Approved |
Joined: 28-Nov-2008