6

I have a simple class:

[DataContract]
public class Book
{
    [DataMember]
    public Int32 ID { get; set; }

    [DataMember]
    public Nullable<Int32> AuthorID { get; set; }     
}

Which is retrieved via a WCF Service like:

public class BookService : IBookService
    {
        IService _service;

        public BookService()
        {
            _service = new BookService();
        }

        public IEnumerable<Book> GetBooks()
        {
            var Books = _service.GetBooks();
            return Books;
        }                
    }

I now want to extend this to include an additional function that would allow a LINQ query to include additional properties on the Book class (not shown in the cut down example above). The function for this (which works when included and called from within an MVC app) is:

    public IEnumerable<Book> GetBooks(params Expression<Func<Book, object>>[] includes)
    {
        var Books = _service.GetBooks(Expression<Func<Book, object>>[]>(includes));
        return Books;
    }

However, when ran from an WCF Service this generates an error as it can't be serialized:

System.ServiceModel.Description.DataContractSerializerOperationBehavior contract: http://tempuri.org/:IBookService ----> System.Runtime.Serialization.InvalidDataContractException: Type 'System.Linq.Expressions.Expression1[System.Func2[Foo.Book,System.Object]]' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute.

I added the DataContract/DataMember attributes to the class so that it could be serialized, however I'm not sure what I need to do to allow the function parameter to be serialized.

Anyone have ideas on what I can do to allow this function to be used in the WCF service?

ca8msm
  • 1,170
  • 3
  • 15
  • 37
  • https://social.msdn.microsoft.com/Forums/sqlserver/en-US/538e9e09-dec0-4fbf-bba1-745ab9e4f51f/systemlinqexpressionsexpression1systemfunc2systemobjectsystemboolean-cannot-be?forum=wcf – Alexandru Clonțea Mar 08 '18 at 15:15
  • You will probably need to serialize the string representing the expression and parse the string back to an expression when you deserialize. – Matt Burland Mar 08 '18 at 15:17
  • @Matt - You mean something like: "public IEnumerable GetBooks(string includesSerialized)" and force the calling application to create the serialized string before calling the function? – ca8msm Mar 08 '18 at 15:37
  • Great question. I've seen plenty of comments on StackOverflow where someone says "it can't be done", and then someone shows how it can be done; and I don't want that to be me (although I'd love it if there was a solution); but... I don't think it can be done. A "Func" is a delegate (i.e. a reference to a method), and an "Expression" is a LambdaExpression, which "under the hood" gets compiled into a method. The problem is that you're trying to pass a delegate reference to the method that has been compiled into the client side to the server side, which is why I don't think its possible. – Richardissimo Mar 12 '18 at 18:05
  • An alternative approach would be to pass all of the values that you might want to filter on as properties (nullable if you want them to be optional) of a simple object, and pass that object as the parameter to GetBooks. This does have a slight advantage. For example, you're probably fetching those books from a database of some kind, and with the lambda approach, you would have fetched *all* the records and *then* filtered them. With this approach, you can pass the parameters to the thing which does fetching from your database, which may allow it to be more efficient (e.g. filter by Author). – Richardissimo Mar 12 '18 at 18:13
  • 1. Can you post the extended function that was not included in the original examples. 2. Can you give the code example of the call the function from an MVC application that works? 3. Do you have the web services publically exposed so anyone trying to solve you problem can test their solution before posting? I have some ideas but would like to test them to make sure they are valid solutions before posting. – smehaffie Mar 13 '18 at 06:40
  • 1
    it sounds like you're interested in a kind of odata protocol. try to use it instead – Igor Gnedysh Mar 13 '18 at 09:54
  • I'm looking at the code and I'm a bit lost. Why is there a BookService class instantiated in the constructor of the BookService class? – Ionut Ungureanu Mar 15 '18 at 08:07
  • 2
    You probably need this library: https://github.com/esskar/Serialize.Linq – Evk Mar 17 '18 at 12:39

2 Answers2

0

Could it maybe have something to do with the use of Expression?:

Why would you use Expression<Func<T>> rather than Func<T>?

Benjamin Basmaci
  • 2,247
  • 2
  • 25
  • 46
0

generic list like array are not Serializable, so that is why are you getting the error. Change your code so that you are passing the list of fields you want to return is in an object that can be serialized. Once you are passing parameters in an object that can be serialized you function should work when in web services.

[Data Contract] 
public class Field
{
   [DataMember]
   public string FieldName {get; set;}
}

Then you can pass it to the functions

List<Fields> fieldList = new List<Field>();
fieldList.Add(new Field("Field1");
fieldList.Add(new Field("Field2");
_service.GetBooks(Expression<Func<Book, object>>[]>(fieldList));

** Not sure of syntax of the last line, I have not work with expressions a lot

smehaffie
  • 379
  • 1
  • 10