FransBouma / LinqToSQL2

Official Linq to SQL fork. A complete ORM which is backwards compatible with Linq to SQL but with new features.
Other
57 stars 22 forks source link

Add field exclude/include support for queries #21

Open FransBouma opened 9 years ago

FransBouma commented 9 years ago

(Related to #20) It might be required to exclude some fields from a fetch, e.g. because these fields are very big (data wise) and not needed for the use case the data is obtained.

typical syntax:

var employees = ctx.Employees.Where(e=>e.City=="The Hague").ExcludeFields(e=>e.Photo);

ExcludeFields only works on the final projection as it otherwise has little effect.

To avoid long lists of fields to exclude, an IncludeFields is added too, it allows the user to specify only the fields required.

PK and FK fields are always fetched and not excludable, to make sure graph management of the entities is preserved.

MikaelEliasson commented 9 years ago

Yes yes! I was about to comment in #20 about this. I like this approach more because it's more flexible (you can have many different "Fetch profiles" with this solution).

But there is a problem and that is that an careless user might end up in an inconsistent state. (Exclude fields at fetch, then do an update). And if you are in a MVC view for example you don't really know which properties that are populated and which are excluded without browsing through the call chain.

An approach that might be more viable for larger projects is to support

  IQueryable<T>.SelectAs<TSuper>().

I'm not sure how to best design the details as I just came up with the idea. But the basic goal is to make it easy to select a subset of properties with minimal mapping needed but still have object where all properties are populated properly.

My first thought was that TSuper should be a base class. It works quite fine in a simple Demo scenario but ofc it's not composable and leaves you with a bunch of partial classes. The good thing would be that it would be very easy to implement and no extra mapping needed. Just make the query use the mapped properties on T that comes from the base class and return the base class.

To get the same API simplicity but more more flexibility it could work with interfaces. As long as T implements the interface it would be possible to make the projection automatically. The question is what concrete type to return in that case.

I'm thinking that you could actually return T but have the collection casted as TInterface. Even if a user in theory could cast it back I feel like this solution would properly describe the data and still make it incredibly easy to use.

I can see the value of the original suggestion too so maybe implement SelectAs<> over the ExcludeFields/IncludeFields.

FransBouma commented 9 years ago

What you propose is already in automapper if I'm not mistaken, and it's equal to writing your own projection into a new class (e.g. q.Select(v=>new MyOwnClass() { A=v.A, C=v.C...}); ). I don't see what that has to do with excluding fields from the original entity: you can always do your own custom projection to a custom type, that's not something that needs a core API method in the ORM, IMHO.

What the problem is with fetching entities mapped by the ORM is that if you work with entity graphs to manipulate them and persist them again, it's inefficient to fetch all fields. Excluding fields then lets you keep working with the entity graphs (so no custom projections needed to types which are not in the graph!) and also stay efficient. In scenarios where the data is used in a readonly fashion, excluding fields from an entity fetch might be simpler than writing your own projection but it also might not, so it's a choice then to use this or what you propose (and what automapper is for)

That fields turn out to be null while they should have a value in a normal fetch is indeed something that can be a problem but then again, the user of the code should know what is requested from the DB, at all times. It's equal to fetching a query with Customers and their last 10 orders. Looking into the customer.Orders collection of these customers suggests that they all have at most 10 orders, which isn't the case, the query dictated that. :)

We use excludefields/includefields in llblgen pro now for many years, works great. :)