cslaforum
cslaforum copied to clipboard
TargetParameterCountException in dataportal child fetch after upgrading to 5.0.1
I'm in the process of trying to upgrade from 4.8.1 to 5.0.1 and I've run into an issue with the dataportal when trying to perform a child fetch passing a DataRow array as my criteria. I've basically got a read-only list that contains a child read-only list. I'm using old Ado.NET to call a stored procedure in the list dataportal and using datatables and relations to pull all the data back at once. So the dataportal tree looks something like this:
AppointmentInfocollection:
Protected Overloads Sub DataPortal_Fetch(ByVal crit As CriteriaGet)
Using ctx = ConnectionManager(Of SqlConnection).GetManager(Database.NDRNConnection, False)
Using cmd As New SqlCommand("procDesktop_AppointmentByEmployeeIDDateRange_Select", ctx.Connection)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("@StartDate", crit.StartDate.DBValue).DbType = DbType.DateTime
cmd.Parameters.AddWithValue("@EndDate", crit.EndDate.DBValue).DbType = DbType.DateTime
cmd.Parameters.AddWithValue("@EmployeeIDs", crit.EmployeeIDs.NullAsEmpty()).DbType = DbType.String
cmd.Parameters.AddWithValue("@AdvocacyGroupID", crit.AdvocacyGroupID).DbType = DbType.Int32
cmd.Parameters.AddWithValue("@TimeZone", crit.TimeZone).DbType = DbType.Int16
Dim args As New DataPortalHookArgs(cmd, crit)
LoadCollection(cmd)
End Using
End Using
End Sub
Private Sub LoadCollection(ByVal cmd As SqlCommand)
Dim ds As New DataSet()
Using da As New SqlDataAdapter(cmd)
da.Fill(ds)
End Using
CreateRelations(ds)
Fetch(ds.Tables(0).Rows)
End Sub
Private Sub CreateRelations(ByVal ds As DataSet)
ds.Tables(0).TableName = "AppointmentInfo"
ds.Tables(1).TableName = "AppointmentEmployeeInfo"
ds.Relations.Add("AppointmentInfoAppointmentEmployeeInfo", New DataColumn() {ds.Tables("AppointmentInfo").Columns("AppointmentID")}, New DataColumn() {ds.Tables("AppointmentEmployeeInfo").Columns("AppointmentID")}, False)
End Sub
''' <summary>
''' Loads all <see cref="AppointmentInfoCollection"/> collection items from given DataTable.
''' </summary>
Private Sub Fetch(ByVal rows As DataRowCollection)
IsReadOnly = False
Dim rlce As Boolean = RaiseListChangedEvents
RaiseListChangedEvents = False
For Each row As DataRow In rows
Dim appt As AppointmentInfo = AppointmentInfo.GetAppointmentInfo(row)
MyBase.Add(appt)
Next
RaiseListChangedEvents = rlce
IsReadOnly = True
End Sub
AppointmentInfo:
Friend Shared Function GetAppointmentInfo(ByVal dr As DataRow) As AppointmentInfo
Return DataPortal.FetchChild(Of AppointmentInfo)(dr)
End Function
Private Sub Child_Fetch(ByVal dr As DataRow)
If Not dr.IsNull("AppointmentID") Then
LoadProperty(AppointmentIDProperty, dr("AppointmentID"))
End If
FetchChildren(dr)
End Sub
Private Sub FetchChildren(Byval dr As DataRow)
Dim childRows As DataRow()
childRows = dr.GetChildRows("AppointmentInfoAppointmentEmployeeInfo")
LoadProperty(EmployeesProperty, AppointmentEmployeeInfoCollection.GetAppointmentEmployeeInfoCollection(childRows))
End Sub
AppointmentEmployeeInfoCollection:
Friend Shared Function GetAppointmentEmployeeInfoCollection(ByVal dr As DataRow()) As AppointmentEmployeeInfoCollection
Return DataPortal.FetchChild(Of AppointmentEmployeeInfoCollection)(dr)
End Function
Private Sub Child_Fetch(ByVal rows As DataRow())
IsReadOnly = False
Dim rlce As Boolean = RaiseListChangedEvents
RaiseListChangedEvents = False
For Each row As DataRow In rows
MyBase.Add(AppointmentEmployeeInfo.GetAppointmentEmployeeInfo(row))
Next
RaiseListChangedEvents = rlce
IsReadOnly = True
End Sub
Something is going on when it gets to the child fetch of the child list. The issue appears to happen in the ServiceProviderMethodCaller.cs file in the FindDataPortalMethod method. It's looking for a criteria object array, and the code is trying to find my Child_Fetch method using the following code:
// scan candidate methods for matching criteria parameters
int criteriaLength = 0;
if (criteria != null)
criteriaLength = criteria.GetLength(0);
var matches = new List<ScoredMethodInfo>();
if (criteriaLength > 0)
{
foreach (var item in candidates)
{
int score = 0;
var methodParams = GetCriteriaParameters(item);
if (methodParams.Length == criteriaLength)
{
var index = 0;
foreach (var c in criteria)
{
if (c == null)
{
if (methodParams[index].ParameterType.IsPrimitive)
break;
else if (methodParams[index].ParameterType == typeof(object))
score++;
}
else
{
if (c.GetType() == methodParams[index].ParameterType)
score += 2;
else if (methodParams[index].ParameterType.IsAssignableFrom(c.GetType()))
score++;
else
break;
}
index++;
}
if (index == criteriaLength)
matches.Add(new ScoredMethodInfo { MethodInfo = item, Score = score });
}
}
}
else
{
foreach (var item in candidates)
{
if (GetCriteriaParameters(item).Length == 0)
matches.Add(new ScoredMethodInfo { MethodInfo = item });
}
}
if (matches.Count == 0)
{
// look for params array
foreach (var item in candidates)
{
var lastParam = item.GetParameters().LastOrDefault();
if (lastParam != null && lastParam.ParameterType.Equals(typeof(object[])) &&
lastParam.GetCustomAttributes<ParamArrayAttribute>().Any())
{
matches.Add(new ScoredMethodInfo { MethodInfo = item, Score = 1 });
}
}
}
if (matches.Count == 0)
{
if (throwOnError)
throw new TargetParameterCountException($"{targetType.FullName}.[{typeOfT.Name.Replace("Attribute", "")}]{GetCriteriaTypeNames(criteria)}");
else
return null;
}
The above code is looking at the criteria array, and cycling through and trying to match up the parameter list with the parameter types of the method that it found in my code. However, instead of the criteria object being an array of DataRow array objects, it appears to be the DataRow array itself. Meanwhile the MethodParams object has the DataRow array object, so one of two things happens when I step through the code depending upon the underlying data.
-
If there is only one record returned then the DataRow array in the criteria object has the same length as the MethodParams object, but when cycling through the criteria object it is trying to check the DataRow in the Datarow array against the MethodParams DataRow Array itself, and it fails.
-
If there is more than one record returned then the length of the criteria DataRow Array ends up being greater than the length of the MethodParams object and it also fails.
To me it looks like the criteria is coming in as a DataRow array, when the code assumes that it should be an object array of DataRow arrays.
Is this a bug, or am I doing something wrong? This was never an issue before I tried upgrading, so I'm guessing it's an issue with the new methodology of not requiring hard coded dataportal names. And I know that my code is using older methodologies, but based on the change log I think it should still work in the new version. Any assistance will be greatly appreciated.
One of the drawbacks of using a param array as the last parameter for a method is that it becomes difficult if not impossible to pass an actual array as a parameter.
I'm certainly open to the idea that there's a solution to this, but I'm not confident that an answer exists (though a workaround does - see below).
There are unit tests for FindDataPortalMethod that test numerous combinations of parameter types, arrays, etc. If you'd like, you can try and establish a pattern that accepts your array scenario without breaking the other scenarios. If there is a solution I'm very open to implementing it.
As a workaround, you can pass your array within a "message object" - very possibly a List<DataRow>
which is easily created from an array, and easily converted back into an array.
var list = new List<DataArray>(myarray);
var array = list.ToArray();
Hi,
I too have just upgraded to the latest release and find that all my Child_Fetch methods fail. I use these to create a new instance of a phone number and address object. I'm not sure what to do, can someone provide some insight?
protected void Child_Fetch()
{
var result = DataPortal.Create<BankAddressInfoPersist>();
string[] args = { "OriginalValues" };
using (BypassPropertyChecks)
{
DataMapper.Map(result, this, args);
MarkAsChild();
MarkNew();
}
}
Then I get this:
Csla.DataPortalException
HResult=0x80131500
Message=ChildDataPortal.Fetch failed on the server
Source=Csla
StackTrace:
at Csla.Reflection.ServiceProviderMethodCaller.FindDataPortalMethod[T](Type targetType, Object[] criteria, Boolean throwOnError)
at Csla.Reflection.ServiceProviderMethodCaller.FindDataPortalMethod[T](Object target, Object[] criteria)
at Csla.Reflection.LateBoundObject.<CallMethodTryAsyncDI>d__12`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Csla.Server.DataPortalTarget.<FetchChildAsync>d__18.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Csla.Server.ChildDataPortal.<Fetch>d__9.MoveNext()
at Csla.Server.ChildDataPortal.Fetch(Type objectType)
at Csla.DataPortal.FetchChild[T]()
at AmicusBusinessConsole.Program.Main(String[] args) in C:\Users\kcabr\Source\Repos\AmicusBusiness\AmicusBusinessConsole\Program.cs:line 133
Inner Exception 1:
TargetParameterCountException: SI.Amicus.Business.ExternalAgentLibrary.BO.BankAddressInfoPersist.[FetchChild]()
Inner Exception 2:
TargetParameterCountException: SI.Amicus.Business.CommonLibrary.BO.InjectableBusinessBase`1[[SI.Amicus.Business.ExternalAgentLibrary.BO.BankAddressInfoPersist, SI.Amicus.Business.ExternalAgentLibrary, Version=1.0.7456.42805, Culture=neutral, PublicKeyToken=null]].[FetchChild]()
Inner Exception 3:
TargetParameterCountException: Csla.BusinessBase`1[[SI.Amicus.Business.ExternalAgentLibrary.BO.BankAddressInfoPersist, SI.Amicus.Business.ExternalAgentLibrary, Version=1.0.7456.42805, Culture=neutral, PublicKeyToken=null]].[FetchChild]()
Inner Exception 4:
TargetParameterCountException: Csla.Core.BusinessBase.[FetchChild]()
Inner Exception 5:
TargetParameterCountException: Csla.Core.UndoableBase.[FetchChild]()
Inner Exception 6:
TargetParameterCountException: Csla.Core.BindableBase.[FetchChild]()
Inner Exception 7:
TargetParameterCountException: Csla.Core.MobileObject.[FetchChild]()
Inner Exception 8:
TargetParameterCountException: System.Object.[FetchChild]()
Have you tried flagging your method with the [FetchChild] attribute? It should work without it, so this may be a workaround to a bug.
[FetchChild] protected void Child_Fetch()
Hi Brinawebb, gave it try, still nothing but the error.
First, I want to point out that this forum is now obsolete - please move to the new discussion forum at https://github.com/marimerllc/csla/discussions
Second, it looks like CSLA is trying to find a FetchChild
method on the type BankAddressInfoPersist
. Is that the type where you've implemented the method?