Child_Fetch parameter is null

Child_Fetch parameter is null

Old forum URL: forums.lhotka.net/forums/t/8410.aspx


Sameer posted on Tuesday, January 26, 2010

Hi,

I have the following situation:

I have an object model with an Editable Root object which has 2 Editable Child Collections. In the Editable Root I call a bunch of stored procedures and use the returned values to populate a strongly typed data container. I then pass the rows of the data container down the hierarchy to populate the object model.

It seems like if there is no rows for the editable child collection lists, in the Child_Fetch(parameter), the parameter is set to null. Example code follows:

This is the Get factory method for the NotificationList class. At this point the 'rows' parameter has 0 rows and is passed to the DataPortal.FetchChild method.

internal static NotificationList GetNotificationList(DataTransferMgmtContainer.NotificationRow[] rows)
{
return DataPortal.FetchChild(rows);
}

The above code calls this method below and when this piece runs, the rows parameter is automatically set to null.

private void Child_Fetch(DataTransferMgmtContainer.NotificationRow[] rows)
{
RaiseListChangedEvents = false;
foreach (DataTransferMgmtContainer.NotificationRow row in rows)
{
Notification dt = Notification.GetNotification(row);
this.Add(dt);
}
RaiseListChangedEvents = true;
}

Do I have to do null checks in the Child_Fetch? I did not have to do this in previous CSLA versions. The sample codes in the book does not check for the parameter == null. Any thoughts on this?

Thanks

RockfordLhotka replied on Tuesday, January 26, 2010

What version of CSLA are you using?

CSLA ultimately emulates method overloading to find the correct target method to invoke. The Child_XYZ methods accept a params parameter - which is an array.

There's some really tricky behavior around identifying and dealing with array parameters being passed to a params parameter - primarily because the whole params concept doesn't actually exist at the CLR level - it is all compiler magic.

There was a bug in this area at one point, which I believe was fixed, and so 3.8.1 may not have the issue. Or 3.8.2? I don't remember when I looked into this.

And of course it is possible you've found another edge case - though your scenario looks familiar, so I think it is the one I fixed.

rxelizondo replied on Tuesday, January 26, 2010

He is right, I was able to reproduce the problem (CSLA 3.8.1)

using System;
using System.IO;
using Csla;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Foo.FetchChildTest();
Console.Read();
}
}

class Foo : BusinessBase<Foo>
{
public static Foo FetchChildTest()
{
string[] emptyString = new string[0];
return DataPortal.FetchChild<Foo>(emptyString);
}

private void Child_Fetch(string[] ms)
{
Console.WriteLine(ms == null);
}
}
}

RockfordLhotka replied on Tuesday, January 26, 2010

That's unfortunate. As I said, this is tricky code, so I'll have to block out a day or two to go fight with this some more... Looking at my schedule, don't count on any progress for 3-4 weeks at this point :(

I wonder if a workaround would be to wrap the array in a list. Convert it from a value type to a ref type - that might avoid the casting issue.

RockfordLhotka replied on Tuesday, January 26, 2010

As I was watching my younger son's orchestra concert this evening I remembered the reason this was (to my knowledge) unfixable.

  class Program
  {
    static void Main(string[] args)
    {
      Test(null);
      object[] a = null;
      Test(a);
      Console.ReadLine();
    }

    static void Test(params object[] e)
    {
      if (e == null)
        Console.WriteLine("e is null");
      else
        Console.WriteLine(e.GetType().Name);
    }
  }

The output of this test is that both calls to Test() have a null value for e.

The child data portal methods (like Child_Update(), etc) accept a params array just like Test() does here, thus allowing you to pass any parameters you want through to the target method.

Of course there's a problem here, because null and an uninitialized array both appear as a null. It is impossible for the code in Test() to determine that the caller provided an actual null in one call and an unitialized (and thus null) array in the second call.

If you can see a way by which these can be differentiated, I'm sure I can call the target method correctly - but I don't know how you'd do such a thing.

Sameer replied on Thursday, January 28, 2010

You are right about the null array business. However, if you look at the code snippet above, the rows parameter in GetNotificationList is not null but an empty array with Count = 0. When the DataPortal.FetchChild(rows) is called inside GetNotificationList method, the Child_Fetch method is called and its rows parameter is automatically being set to null. I would think the rows parameter in Child_Fetch would continue to be a empty array with Count = 0. Something between DataPortal.FetchChild() and Child_Fetch() the parameter is being set to null if an empty (not null) array is passed in.

ihate replied on Tuesday, March 16, 2010

I think this change happened in 3.8.x, I noticed when I moved from 3.7.something.  I just changed my Child_Fetch methods to start with a null check but it was still annoying.

You can see this failure for yourself in the Project Tracker sample (I downloaded it in the last week or two)

In that case, it is passing a 0 element array as Sameer described, which is being turned into null in Csla.Reflection.MethodCaller.CallMethod(object, DynamicMethodHandle, params object[])  

This only happens on methods whose single argument is an array... so there are some workarounds possible.

I wish the template type on the Csla.DataPortal approach mentioned in the second bullet didn't require manually specifying both type params.  Presumably DataPortal could be changed so that just this one method could have one type parameter (and thus, it could be implied by the calling code)... which would make the calling code look like:

internal static ResourceAssignments GetResourceAssignments(ProjectTracker.DalLinq.Assignment[] data) { 
return DataPortal<ResourceAssignments>.FetchChild(data); 

Notice now how DataPortal has a type param, and FetchChild has another (which is implied)

Now that I think about it, I can create my own static class which does exactly that, and doesn't break code expecting all the other things Csla.DataPortal offers now.  Here's the beginnings of said class:

static class MyDataPortal<T> {
  public static T FetchChild<TA>(TA[] stuff) {
    return DataPortal.FetchChild<T>(new object[] {stuff});
  }

  public static T FetchChild(params object[] fetchParams) {
    return DataPortal.FetchChild<T>(fetchParams);
  }
}

 

Well, I can see this kind of change not happening in a future version of Csla... about 2/3rds of the exposed methods on DataPortal aren't typed, and it wouldn't be fun to fix existing code even for this simple (possibly mechanical) change... and it's probably not worth making redundant overloads like static class DataPortal<T>(params object[]){ public static T FetchChild<T>() just to not break existing code that have the method template params specified but not the class ones... if that would even work..

ihate replied on Wednesday, March 17, 2010

It seems I was wrong about DataPortal_FetchChild.  Changing it as follows does seem to behave like the templated overload version.

 

    public static T FetchChild<T>(params object[] parameters)

    {

// HACK? Fix issue where single Foo[] parameter is turned into apparent object[] of parameters

if (parameters != null && parameters.GetType() != typeof(object[])) {

parameters = new object[] {parameters};

}

      Server.ChildDataPortal portal = new Server.ChildDataPortal();

      return (T)(portal.Fetch(typeof(T), parameters));

 

    }

ihate replied on Wednesday, March 17, 2010

Here's another kludge that doesn't use GetType().  I found it interesting that FetchChild(null) resolves to FetchChild(object[] list).

 

Note that the behavior of the code below is different from the previous code when the passed list is actually an object[], it is now wrapped in another object[].

 

// a kludge to act like params object[], but only when there's 1 or more than one params, and that one param can't implicitly be cast to object[]

public static T FetchChild<T>(object param0, params object[] otherParamaters) {

if (otherParamaters == null || otherParamaters.Length == 0)

return PrivateFetchChild<T>(new []{param0});

object[] fullparamarray = new object[otherParamaters.Length + 1];

fullparamarray[0] = param0;

Array.ConstrainedCopy(otherParamaters, 0, fullparamarray, 1, otherParamaters.Length);

return PrivateFetchChild<T>(fullparamarray);

}

   public static T FetchChild<T>(object[] list) {

return PrivateFetchChild<T>(new object[] { list });

 }

public static T FetchChild<T>() {

return PrivateFetchChild<T>(new object[0]);

}

ihate replied on Wednesday, March 17, 2010

 

Below is an approach that solves the problem you presented. Note: the constraints/consequences are different if my Foo methods below are changed to not have params[] overloads (though the below examples remain relatively unchanged).  Also, the "Automatic Type selection" behavior isn't possible on the current Child_Fetch<T> method, the type parameters are (to my understanding) either all chosen by the compiler, or none of them are.

 

But to respond directly to your particular point, you are correct, a method taking MyMethod(params object[] stuff) can't tell various things apart very well.  Notably MyMethod(null), MyMethod((List<int>)null), MyMethod((int[])null) and MyMethod() are the same, and also MyMethod(5) and MyMethod(new int[]{5}) seem the same (but in only the latter latter case, stuff.GetType() != typeof(object[])... MyMethod(5) turns into MyMethod(new object[]{5}) and MyMethod(new int[]{5}) turns into MyMethod((object[])(new int[]{5})))

 

Also, the current (params object[] stuff) approach doesn't let you consistently distinguish between methods to call if there are more than one, for example what if I have two different Child_Fetch methods that look like Child_Fetch(string, string) and Child_Fetch(someobject, someotherobject), and call DataPortal.FetchChild<T>(null, null)?

That said, I can see why it's disgusting to have to limit the number of parameters you pass to things, or put other constraints on them. (below looks like "you can't have more than 2 parameters of different types").  Although, you and I apparently agree on one limit, Child_Fetch itself can't be templated.

If extending this further I would probably make a class to represent the method signature, and create it something like new MethodSignature(true, typeof(T1), typeof(T2)); and the constructor for MethodSignature would be MethodSignature(bool lastIsParamsArray, params Type[] types)

All of this work and there are still cases where various calls would be ambiguous from the code's point of view... In the example below something compiled as (params int[]) may need to call something whose parameters are (int,int,int,int) merely because we don't have explicit overloads for that many parematers. Also your code would have to figure out that (lastIsParamsArray=false, int, string, int, DateTime) are best suited for calling a method that is (params object[]) but I think you've thought about things like that already (or else the caller will have to explicitly cast each parameter to object first, in which case you would still need to notice that (lastIsParamsArray=false, object, object, object, object) can call (params object[])).

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace MethodCallerStuff {

class Program {

static void Main(string[] args) {

string str1 = "Hello";

string str2 = null;

int[] intArray1 = new int[1];

int[] intArray2 = null;

object[] objArray1 = new object[1];

object[] objArray2 = null;

 

Example.Foo(); //Foo()

Console.WriteLine();

 

// Examples with one argument

//Example.Foo(null); // ambiguous, null can be a T1 or a T1[]

Example.Foo(1); //Foo<System.Int32>(System.Int32 param1)

Example.Foo(str1); //Foo<System.String>(System.String param1)

Example.Foo(str2); //Foo<System.String>(System.String param1)

Example.Foo(intArray1); //Foo<System.Int32>(params System.Int32[] param1)

Example.Foo(intArray2); //Foo<System.Int32>(params System.Int32[] param1)

Example.Foo(objArray1); //Foo<System.Object>(params System.Object[] param1)

Example.Foo(objArray2); //Foo<System.Object>(params System.Object[] param1)

 

Console.WriteLine();

 

// Examples with two arguments

//Example.Foo(null, 1); // ambiguous, null can be a T1 or T1[]

//Example.Foo(1, null); // ambiguous, null can be a T2 or T2[]

Example.Foo(1, 1); //Foo<System.Int32,System.Int32>(System.Int32 param1, System.Int32 param2)

Example.Foo(1, str1); //Foo<System.Int32,System.String>(System.Int32 param1, System.String param2)

Example.Foo(1, str2); //Foo<System.Int32,System.String>(System.Int32 param1, System.String param2)

Example.Foo(1, intArray1); //Foo<System.Int32,System.Int32>(System.Int32 param1, params System.Int32[] param2)

Example.Foo(1, intArray2); //Foo<System.Int32,System.Int32>(System.Int32 param1, params System.Int32[] param2)

Example.Foo(1, objArray1); //Foo<System.Int32,System.Object>(System.Int32 param1, params System.Object[] param2)

Example.Foo(1, objArray2); //Foo<System.Int32,System.Object>(System.Int32 param1, params System.Object[] param2)

Example.Foo(intArray1, 1); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, System.Int32 param2)

Example.Foo(intArray1, str1); //Foo<System.Int32[],System.String>(System.Int32[] param1, System.String param2)

Example.Foo(intArray1, str2); //Foo<System.Int32[],System.String>(System.Int32[] param1, System.String param2)

Example.Foo(intArray1, intArray1); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, params System.Int32[] param2)

Example.Foo(intArray1, intArray2); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, params System.Int32[] param2)

Example.Foo(intArray1, objArray1); //Foo<System.Int32[],System.Object>(System.Int32[] param1, params System.Object[] param2)

Example.Foo(intArray1, objArray2); //Foo<System.Int32[],System.Object>(System.Int32[] param1, params System.Object[] param2)

Example.Foo(intArray2, 1); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, System.Int32 param2)

Example.Foo(intArray2, str1); //Foo<System.Int32[],System.String>(System.Int32[] param1, System.String param2)

Example.Foo(intArray2, str2); //Foo<System.Int32[],System.String>(System.Int32[] param1, System.String param2)

Example.Foo(intArray2, intArray1); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, params System.Int32[] param2)

Example.Foo(intArray2, intArray2); //Foo<System.Int32[],System.Int32>(System.Int32[] param1, params System.Int32[] param2)

Example.Foo(intArray2, objArray1); //Foo<System.Int32[],System.Object>(System.Int32[] param1, params System.Object[] param2)

Example.Foo(intArray2, objArray2); //Foo<System.Int32[],System.Object>(System.Int32[] param1, params System.Object[] param2)

 

Example.Foo(1, 2, 3, 4); //Foo<System.Int32>(params System.Int32[] param1)

//Example.Foo(1, DateTime.Now, "Hello"); //The type arguments can't be inferred by usage... I would need an overload with T1,T2, and T3

}

}

static class Example {

public static void Foo() {

Console.WriteLine("Foo()");

}

public static void Foo<T1>(T1 param1) {

Console.WriteLine("Foo<{0}>({0} param1)", typeof(T1));

}

public static void Foo<T1>(params T1[] param1) {

Console.WriteLine("Foo<{0}>(params {0}[] param1)", typeof(T1));

}

public static void Foo<T1,T2>(T1 param1, T2 param2) {

Console.WriteLine("Foo<{0},{1}>({0} param1, {1} param2)", typeof(T1), typeof(T2));

}

public static void Foo<T1, T2>(T1 param1, params T2[] param2) {

Console.WriteLine("Foo<{0},{1}>({0} param1, params {1}[] param2)", typeof(T1), typeof(T2));

}

}

}

 

apparently I'm having too much fun.  local time it's 1:29AM.  Hopefully the nested parenthesis in my prose and template abuse in the code haven't been too bad.

There's probably a concise way of solving this particular problem using linq and maybe expression trees. The only ways that come to mind right now essentially are DataPortal.Fetch_Child<T>((obj)=>obj.Child_Fetch(stuff)) which is "wrong" in new and interesting ways... for example

public static class MoreInferredTypeAbuse {

private class Something {

public void SomeMethod(int a, string b) {

}

}

public static void Main(string[] args) {

Example.Foo(

// cheesy way to implicitly set T type parameter

(Something)null, 

// method to call

(thing, a, b) => thing.SomeMethod(a, b), 

// params

1, "Hello");

}

public static class Example {

public delegate void MethodDelegate0<T>(T thing);

            public delegate void MethodDelegate1<T, TP1>(T thing, TP1 param1);

            public delegate void MethodDelegate2<T, TP1, TP2>(T thing, TP1 param1, TP2 param2);

            public delegate void MethodDelegate3<T, TP1, TP2, TP3>(T thing, TP1 param1, TP2 param2, TP3 param3);

            public delegate void MethodDelegate4<T, TP1, TP2, TP3, TP4>(T thing, TP1 param1, TP2 param2, TP3 param3, TP4 param4);

 

public static void Foo<T>(T ignored, MethodDelegate0<T> md) {

}

public static void Foo<T, TP1>(T ignored, MethodDelegate1<T, TP1> md, TP1 p1) {

}

public static void Foo<T, TP1, TP2>(T ignored, MethodDelegate2<T, TP1, TP2> md, TP1 p1, TP2 p2) {

}

public static void Foo<T, TP1, TP2, TP3>(T ignored, MethodDelegate3<T, TP1, TP2, TP3> md, TP1 p1, TP2 p2, TP3 p3) {

}

public static void Foo<T, TP1, TP2, TP3, TP4>(T ignored, MethodDelegate4<T, TP1, TP2, TP3, TP4> md, TP1 p1, TP2 p2, TP3 p3, TP4 p4) {

}

}

}

Copyright (c) Marimer LLC