2

I have the following Data Transfer Objects defined:

Public Class MemberWithAddressesDTO
  Public Property Member_PK As Integer
  Public Property Firstname As String
  Public Property DefaultAddress As AddressDTO
  Public Property Addresses As IQueryable(Of AddressDTO)
End Class

Public Class AddressDTO
  Public Property Address_PK As Integer
  Public Property Address1 As String
  Public Property Address2 As String
End Class   

I have defined the following expression which maps my entity to my DTO so that I can reuse this in queries (the actual expression is generated by T4 template):

Public Shared AsAddressDTO As Expression(Of Func(Of Address, AddressDTO)) =
  Function(ent As Address) New AddressDTO With {.Address_PK = ent.Address_PK, _
                                                 .Address1 = ent.Address1,
                                                 .Address2 = ent.Address2}

I can use this expression in a LINQ-to-Entities query to convert lists of Address entities to list of AddressDTOs:

Using context As New DbContext
  Dim mem As MemberWithAddressesDTO = 
    context.Members _
      .Where(Function(m) m.Person_PK = 121) _
      .Select(Function(m) New MemberWithAddressesDTO With {
                .Member_PK = m.Person_PK, _
                .Firstname = m.Firstname, _
                .Addresses = ent.Addresses.AsQueryable.Select(AsAddressDTO)}
             ).FirstOrDefault()
End Using

This works fine, however if I want to use the same expression to convert a single field in member to an AddressDTO in the anonymous type, the only way I can get this to work is to put the field in a single item array, cast to queryable and then call select on that - this seems a bit circuitous and I am wondering if there is a better way:

Using context As New DbContext
  Dim mem As MemberWithAddressesDTO = _
    context.Members _
      .Where(Function(m) m.Person_PK = 121) _
      .Select(Function(m) New MemberWithAddressesDTO With {
        .Member_PK = m.Person_PK, _
        .Firstname = m.Firstname, _
        .DefaultAddress = {m.DefaultAddress}.AsQueryable.Select(AsAddressDTO).FirstOrDefault}
      ).FirstOrDefault()
End Using

Basically I want to know if there is better syntax to achieve this line in the above:

.DefaultAddress = {m.DefaultAddress}.AsQueryable.Select(AsAddressDTO).FirstOrDefault}

Note m.DefaultAddress is a single field of type Address - it is not a collection.

Any ideas?

James Close
  • 862
  • 9
  • 15

2 Answers2

0

NB Addresses is still an un-executed query, where as DefaultAddress is a real AddressDTO.

In any case, because you want to retain the Expression you need to keep the IQueryable. (Note the Queryable.Select has the Expression overload that the Enumerable.Select does not.)

And the "delayed execution" we're using is based upon IEnumerable, so a queryable .SelectOne will still need a .First called later.

Thus you need to turn the single value into an enumeration somewhere. And you end up with almost identical code to what you started with:

 <Extension> _
 Public Function SelectOne(Of TSource, TResult)(ByVal source As TSource, ByVal selector As Expression(Of Func(Of TSource, TResult))) As IQueryable(Of TResult)
     Return Enumerable.Repeat(source, 1).AsQueryable.Select(selector)
 End Function

I did attempt to return an expression that Invoke the selector with the source, but as I mentioned above AFAICT you need an IEnumerable.

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
0

Can't you do something like this:

Using context As New DbContext
  ' Get a delegate to the expression
  Dim func As Func(Of Address, AddressDTO) = AsAddressDTO.Compile()

  Dim mem As MemberWithAddressesDTO = _
    context.Members _
      .Where(Function(m) m.Person_PK = 121) _
      .Select(Function(m) New MemberWithAddressesDTO With {
        .Member_PK = m.Person_PK, _
        .Firstname = m.Firstname, _
        .DefaultAddress = func({m.DefaultAddress})}
      ).FirstOrDefault()
End Using
RobSiklos
  • 8,348
  • 5
  • 47
  • 77
  • No that doesn't work. The point here is that AsAddressDTO is an Expression not a Delegate, I need to keep it as an expression so that my query will be evaluated by LINQ-to-Entities and turned into a SQL query against my database, putting a delegate in there will lead to "not supported in LINQ-to-Entities" error... what I am after is someway of doing LINQ expressions against something that isn't IQueryable... – James Close Mar 28 '12 at 08:28
  • Hmm - I think my updated answer is also no good, since I'm using a delegate and you say you can't put a delegate in there. I'm no linq-to-entities expert, but I'm VERY surprised you can't put a delegate there. – RobSiklos Mar 28 '12 at 13:44