goroth
goroth

Reputation: 2610

LINQ to Entities. How to create "Reusable" methods that "wrap" entities?

SETUP
I have an "Order" class and an "OrderDetail" class.
The Order class has a property called "OrderDetails" of type IQueryable.
I have a method in each class called "GetData" and this method returns IQueryable of the class it is in.
The "GetData" methods are wrappers around the Entities that are used to transform the Entities into friendlier .NET classes / properties.
For example: The OrderDetail Entity has a "PullDate" in the database as Char(10) but I want the OrderDetail Class to have a property called "PullDate" that is of type DateTime in the class.

CODE

public class Order
{
    [Key]
    public int ID { get; set; }
    public IQueryable<OrderDetail> OrderDetails { get; set; }

    public static IQueryable<Order> GetData()
    {
        IQueryable<Order> orders;
        var db = new OrderEntities();

        // NOTE: This code will work
        try
        {
            var data = 
                (from O in db.Orders
                 where O.OrderID == 1
                 select new Order
                 {
                     ID = O.OrderID,
                     OrderDetails = 
                         (from OD in db.OrderDetails
                          where OD.OrderID == O.OrderID
                          select new OrderDetail
                          {
                              ID = OD.OrderDetailID,
                              OrderID = OD.OrderID,
                              PullDate = OD.PullDate == "00000000" ?
                                  (DateTime?)null : db.yyyyMMddToDate(OD.PullDate),
                              Description = OD.Desc
                          })
                 });

            orders = data.AsQueryable();
            var orderList = orders.ToList();
        }
        catch (Exception ex)
        {                
            throw;
        }

        // NOTE: This code will NOT work
        try
        {
            var data = (from O in db.Orders
                        where O.OrderID == 1
                        select new Order
                        {
                            ID = O.OrderID,
                            OrderDetails = (from OD in OrderDetail.GetData(db)
                                            where OD.OrderID == O.OrderID
                                            select OD)
                        });

            orders = data.AsQueryable();
            var orderList = orders.ToList();
        }
        catch (Exception ex)
        {

            throw;
        }

        return orders;
    }
}


public class OrderDetail
{
    [Key]
    public int ID { get; set; }
    public int OrderID { get; set; }
    public DateTime? PullDate { get; set; }
    public string Description { get; set; }

    public static IQueryable<OrderDetail> GetData(OrderEntities db)
    {
        IQueryable<OrderDetail> orderDetails;

            var data = (from OD in db.OrderDetails
                        select new OrderDetail
                        {
                            ID = OD.OrderDetailID,
                            OrderID = OD.OrderID,
                            PullDate = OD.PullDate == "00000000" ?
                                (DateTime?)null : db.yyyyMMddToDate(OD.PullDate),
                            Description = OD.Desc
                        });

            orderDetails = data.AsQueryable();
            var orderList = orderDetails.ToList();

            return orderDetails;
    }
}

ERROR
LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[Models.OrderDetail] GetData(Models.OrderEntities)' method, and this method cannot be translated into a store expression.

Request
I would like the Order.GetData method to call the OrderDetail.GetData method using LINQ.
I need to "join" the GetData methods together or "sub select" the OrderDetail.GetData while inside the Order.GetData class.
Both of the classes are querying EntityFramework inside of their GetData methods.
Projection is a requirement.
My goal is to create "Reusable" methods like "GetData" in my DTO classes that will contain specific SQL / Entity logic.
For example, I am using a lot of custom SQL functions like "db.yyyyMMddToDate" in my DTO classes to transform the Entities into something more object / .NET friendly and I don't want to "retype" all that logic each time I need to "join / sub select" data from entities.

In LINQ to Objects this would be the same as joining two different lists from different classes.
But it seems that LINQ to Entity does not know how to join methods from other classes even if the method is marked as Queryable.
I understand that LINQ to Entity is treating the "GetData" methods as if they were SQL functions but I need a way to tell LINQ to Entity that these are just more Entities to join together to get the results.

Upvotes: 0

Views: 1313

Answers (1)

Etienne Maheu
Etienne Maheu

Reputation: 3255

You get this error because, when dealing with LINQ to Entities, you are not passing in lambdas to the LINQ operators like you do in LINQ to Object. You are passing expression trees. The code that you write in those queries will never be executed. It will be interpreted by Entity Framework as a series of instruction to translate into an other language like SQL.

The GetData method doesn't mean anything in SQL. In fact, it doesn't mean anything outside of your own program. This is why LINQ to Entities do not recognize your method as a valid construct.

To access the details of an order in a scenario like yours, you need to build your query so that EF knows to include the OrderDetails entities inside the primary query using the Include operator. When doing this, EF expects your object to have a property similar to this one:

public virtual ICollection<OrderDetail> OrderDetails { get; set; }

This is how EF will know where to put the resulting objects from queries that includes those elements.

var ctx = new DatabaseContext();
var query = ctx.Orders
   .Where(o => o.OrderId == 1)
   .Include(o => o.OrderDetails);

This query will asks the remote server for all Orders, with an OrderId of 1, with all its matching OrderDetails. EF will then automatically create all of the related client side entities for you.

Here's the official documentation on EF about this topic: http://msdn.microsoft.com/en-ca/data/jj574232.aspx

Upvotes: 1

Related Questions