none
Passing an EntityCollection ByRef in VB.NET - EF4.0

    Question

  • Usually when programming .NET applications I use C#. This is the first time that I'm working on a project in VB.NET and I'm encountering a problem that I will explain in a bit in EF4.0. I know that my problem has to do with VB.NET ByRef copying a parameter twice (instead of 0 times in other languages like C#, C++, etc.). Anyone with ideas on how to best resolve my issue? Thanks for any help.

    I have an N-tier WCF application that uses EF4.0 to fetch and store data in the database. I do not wish to pass the entities themselves via the WCF application, so I have created a view model layer that is able to convert itself from an entity and to an entity. I wanted to keep my view model <=> model conversion code clean, so I made a couple of utility methods that generalise the conversion from view model to mode.

    The view model classes implement the following interfaces

    ''' <summary>
    
    ''' Implementors of this interface are able to convert themselves to an object of TEntity
    
    ''' </summary>
    
    Public Interface IConvertibleToModel(Of TEntity)
     Inherits IViewModelEntity
     ''' <summary>
     ''' Creates a new TEntity and fills it
     ''' </summary>
     Function ConvertToModel() As TEntity
    
     ''' <summary>
     ''' Fills the given TEntity. Overwrites existing values except ids
     ''' </summary>
     Sub ConvertToExistingModel(ByRef entityToFill As TEntity)
    End Interface
    
    Public Interface IViewModelEntity
     ReadOnly Property Id() As Integer
    End Interface
    

    The conversion code for the most part is simple left-hand right-hand code like the example with fictional AddressViewModel and Address

     ''' <summary>
     ''' Converts this AddressViewModel to an Address
     ''' </summary>
     Public Function ConvertToModel() As Address Implements IConvertibleToModel(Of Address).ConvertToModel
      Dim addressValue As Address = New Address()
      Me.ConvertToExistingModel(addressValue)
      Return addressValue
     End Function
    
     ''' <summary>
     ''' Converts this AddressViewModel to an Address
     ''' </summary>
     Public Overloads Sub ConvertToExistingModel(ByRef addressValue As Address) Implements IConvertibleToModel(Of Address).ConvertToExistingModel
      addressValue.Street = Me.Street
      addressValue.PostalCode = Me.PostalCode
      addressValue.City = Me.City
      addressValue.State = Me.State
      addressValue.CountryWrapper = Me.Country
     End Sub
    

    However, sometimes I need to set properties of EF entities. A Customer might have HomeAddress and BillingAddress as its properties. I want to overwrite the values in these properties if they already exist (ConvertToExistingModel()) or create a new object if it does not exist (ConvertToModel()). I created the following utility methods.

     ''' <summary>
     ''' Converts an TConvertible to its model class
     ''' </summary>
     Friend Shared Sub ConvertToModel(Of TConvertible As IConvertibleToModel(Of TModel), TModel)(
      ByVal convertible As TConvertible,
      ByRef model As TModel
     )
      If Not IsNothing(convertible) Then
       If IsNothing(model) Then
        model = convertible.ConvertToModel()
       Else
        convertible.ConvertToExistingModel(model)
       End If
      End If
     End Sub
    

    This method is then called as follows in for example a CustomerViewModel

     ''' <summary>
     ''' Converts this CustomerViewModel to a Customer
     ''' </summary>
     Public Sub ConvertToExistingModel(ByRef customer As Customer) Implements IConvertibleToModel(Of Customer).ConvertToExistingModel
      ' Snip 
      ViewModelUtility.ConvertToModel(Me.Address, customer.Address)
       ' Snip
     End Sub
    

    I wanted to generalise the same thing for 1-to-n properties, with the following method

     ''' <summary>
     ''' Converts a collection of IConvertibles into a model collection
     ''' </summary>
     Friend Shared Sub ConvertToModel(Of TConvertible As {IConvertibleToModel(Of TModel), IViewModelEntity}, TModel As {IEntity, EntityObject})(
      ByVal convertibles As ICollection(Of TConvertible),
      ByRef modelCollection As EntityCollection(Of TModel)
     )
      For Each convertibleValue As TConvertible In convertibles
       ' Check if the model already exist
       Dim modelValue As TModel = modelCollection.SingleOrDefault(
        Function(existingModelValue) existingModelValue.Id = convertibleValue.Id
       )
    
       ' If it does not yet exist
       If IsNothing(modelValue) Then
        ' Create new
        modelValue = convertibleValue.ConvertToModel()
        modelCollection.Add(modelValue)
       Else
        ' Otherwise update existing
        convertibleValue.ConvertToExistingModel(modelValue)
       End If
      Next
     End Sub
    
      When calling this method like so,  
     ''' <summary>
     ''' Converts this CustomerViewModel to a Customer
     ''' </summary>
     Public Sub ConvertToExistingModel(ByRef customer As Customer) Implements IConvertibleToModel(Of Customer).ConvertToExistingModel
      ' Snip 
      ViewModelUtility.ConvertToModel(Me.Addresses, customer.Addresses)
       ' Snip
     End Sub
    

    it fails with the following error at set_Addresses()

    The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.
    Sunday, October 10, 2010 3:01 PM

Answers

  • Ah reminds me this difference (for some reason I came accross this unfrequently perhaps only one time ?). In VB.NET passing  a property byref will automatically call the setter if you change the argument (actually I believe this is CLR feature). This is what you are experiencing here (i.e customer.Addresses is passed byref as the model argument, so any change to model will be actually a change to customer.Addresses). As a result you get this error message. In C# this is not allowed (basically C# handles properties as they really are i.e. methods while VB handles them as a full replacement for a field access).

    You could use ByVal instead (but then I'm not sure what you would change model anyway ?).

    You best bet could be to write this code using C# and using Reflector (or a forum or some online tool) to see how the VB translation would look like. Also as others said you could use available tools (RIA Services maybe ? or whatever that already does this job).


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    Hey thanks, this answer helped me out. I had a misunderstanding about the meaning of ByRef and ByVal in VB.NET. I didn't quite get the fact that ByVal on a reference type copies the pointer to the actual object into the method. I had some mistaken idea that it copies the actual object into the method (which seemed oddly odd to me before).

    Solution: Don't pass the EntityCollection ByRef, ByVal will do.

    Monday, October 11, 2010 5:21 PM

All replies

  • Hi,

    Not sure what you mean with byref "copying the parameters twice". Based only on the error message it seems that the EntityCollection can be initialized only one time. So rather than passing the collection byref and replacing it, I would pass the collection byval and would copy its members.

    In the worst case you could try to write this snippet using C# and use Reflector to see what gives the MSIL code when decompiled to VB...

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".
    Sunday, October 10, 2010 3:27 PM
  • Hi,

    Not sure what you mean with byref "copying the parameters twice". Based only on the error message it seems that the EntityCollection can be initialized only one time. So rather than passing the collection byref and replacing it, I would pass the collection byval and would copy its members.

    In the worst case you could try to write this snippet using C# and use Reflector to see what gives the MSIL code when decompiled to VB...

     


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".

    Thanks for your response.

    As far as I understand VB.NET, ByRef copies a parameter value twice: once when entering the method and once when returning from the method.
    As you can see from the code snippets, I don't (explicitly) replace the collection. The collection gets replaced because VB.NET is attempting to copy back the collection parameter when returing from the method.

    I don't think I would be able to pass the collection as ref/out parameter in C# because it's a property. In C# there would also be no need to pass as ref/out because I would just pass the collection into the method and it would work because the collection is a reference type.

    Sunday, October 10, 2010 3:42 PM
  • You should consider using ObjectMapper which is a very stable free product that let's you convert any object to another object. People have used ObjectMapper extensively to convert EF object to DTOs(Data Transfer Objects)
    Zeeshan Hirani Entity Framework 4.0 Recipes by Apress
    http://weblogs.asp.net/zeeshanhirani
    Sunday, October 10, 2010 5:18 PM
  • You should consider using ObjectMapper which is a very stable free product that let's you convert any object to another object. People have used ObjectMapper extensively to convert EF object to DTOs(Data Transfer Objects)
    Zeeshan Hirani Entity Framework 4.0 Recipes by Apress
    http://weblogs.asp.net/zeeshanhirani


    Thanks for the tip. I'm aware of mappers like AutoMapper and ObjectMapper but I am not considering these right now. I just want to know if there is any EF or VB workaround for the issue that I'm having right now, so that my knowledge of both increases.

    Currently I have 'derefactored' the code and that's good enough to produce something that works for now.

    Monday, October 11, 2010 7:34 AM
  • Ah reminds me this difference (for some reason I came accross this unfrequently perhaps only one time ?). In VB.NET passing  a property byref will automatically call the setter if you change the argument (actually I believe this is CLR feature). This is what you are experiencing here (i.e customer.Addresses is passed byref as the model argument, so any change to model will be actually a change to customer.Addresses). As a result you get this error message. In C# this is not allowed (basically C# handles properties as they really are i.e. methods while VB handles them as a full replacement for a field access).

    You could use ByVal instead (but then I'm not sure what you would change model anyway ?).

    You best bet could be to write this code using C# and using Reflector (or a forum or some online tool) to see how the VB translation would look like. Also as others said you could use available tools (RIA Services maybe ? or whatever that already does this job).


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".
    Monday, October 11, 2010 9:16 AM
  • Ah reminds me this difference (for some reason I came accross this unfrequently perhaps only one time ?). In VB.NET passing  a property byref will automatically call the setter if you change the argument (actually I believe this is CLR feature). This is what you are experiencing here (i.e customer.Addresses is passed byref as the model argument, so any change to model will be actually a change to customer.Addresses). As a result you get this error message. In C# this is not allowed (basically C# handles properties as they really are i.e. methods while VB handles them as a full replacement for a field access).

    You could use ByVal instead (but then I'm not sure what you would change model anyway ?).

    You best bet could be to write this code using C# and using Reflector (or a forum or some online tool) to see how the VB translation would look like. Also as others said you could use available tools (RIA Services maybe ? or whatever that already does this job).


    Please always mark whatever response solved your issue so that the thread is properly marked as "Answered".


    Hey thanks, this answer helped me out. I had a misunderstanding about the meaning of ByRef and ByVal in VB.NET. I didn't quite get the fact that ByVal on a reference type copies the pointer to the actual object into the method. I had some mistaken idea that it copies the actual object into the method (which seemed oddly odd to me before).

    Solution: Don't pass the EntityCollection ByRef, ByVal will do.

    Monday, October 11, 2010 5:21 PM