LINQ query take(count) from collection of child elements - sql

How can I make LINQ query (I am using Entity Framework) that returns top n elements in a child collection?
Here are example classes
public class A {
public int ID
public ICollection<B> bData
}
public class B {
public int ID
public string Name
}
This is kind of query I was thinking;
db.A.Where(a => a.ID == query_id).Include(a => a.bData.Take(count)).ToList();
Normally this doesn't work, how could I accomplish this?

The Include method is intended for eager loading the related entity data. It's all or nothing and cannot be used for filtering, sorting, grouping etc.
So what are you asking for can be accomplished with projection (select). It would be simpler if you project to anonymous or custom type (a.k.a. DTO object), because EF does not allow projecting to entity type. But it's still doable by using anonymous type projection in LINQ to Entities query and doing second projection in LINQ to Objects to attach the filtered child collection to its parent entity like this:
var result = db.A
.Where(a => a.ID == query_id)
.Select(a => new { a, bData = a.bData.Take(count).ToList() })
.AsEnumerable() // Switch to LINQ to Object context
.Select(x =>
{
x.a.bData = x.bData;
return x.a;
})
.ToList();
Please note that in order the above to work, bData member should not be virtual or db.Configuration.LazyLoadingEnabled should be false (i.e. lazy loading should be off, otherwise the bData collections will be fully reloaded when first accessed).

Related

How to load different children classes to parent collection in Entity Framework Core?

Given the following:
public class Parent
{
public ChildType childType;
}
public class ChildA : Parent { ... }
public class ChildB : Parent { ... }
public enum ChildType {
childA,
childB
}
public class Content {
public long contentId;
public string? name;
public ICollection<Parent>? contentCollection; <--
...
}
I would like to use the Content class as part of an API. Is it possible to load both children into the collection just using the enum as a discriminator to determine which to cast to?
My understanding is the child objects would need to be loaded from EF as their child class first, then cast to the parent class before being added to the collection as they would be missing properties upon casting back to the child class otherwise. Is this correct? And how can the dbContext be configured to handle this when accessing through the Content class?
Apologies for all the questions, I have not done this before and cannot find an example online. I would like to know any thoughts, pointers or general info before proceeding. Please say if anything is unclear or more info is required.
Edit:
I was trying to map the child objects as their types from the DB, upcast to the parent type to be able to add multiple types to the one collection and then downcast when required for use. As far as I was aware, EF did not have the functionality to do this.
For anyone else who comes across this which needs assistance, I solved my issue by just using ADO.NET which is what entity framework is built around. I was getting stuck by trying to get this working using EF but my belief is it is not able to be done with EF.
Formatting is off a little, and I have renamed everything to suit my original question but here is the solution involved:
Writing an SP to retrieve the data similar to if the objects were stored in a TPH pattern.
Calling that SP using SQLConnection/SQLCommand. (I added this into my context class to keep DAL together but unsure if this is best practice)
public async Task<Collection<Parent>> GetModelMapCollectionAsync(long id) {
Collection<Parent> parentCollection;
using (SqlConnection connection = new SqlConnection(this.Database.GetConnectionString()))
{
using (SqlCommand sqlCommand = new SqlCommand("GetModelMapCollectionAsync", connection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Parameters.Add(new SqlParameter("#id", id));
await connection.OpenAsync();
await sqlCommand.ExecuteNonQueryAsync();
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
{
MapCollectionResult(sqlDataReader, out parentCollection);
}
}
}
return parentCollection; }
Using a nuget package called Dapper, create row parsers for each type (easiest solution for readability/simplicity IMO)
Use the discriminator column to determine which parser to use on each row returned from the SP. This creates the child object from the row which allows it to be downcast back later.
Add that parsed object to the collection.
private void MapCollectionResult(SqlDataReader sqlDataReader, out Collection parentCollection)
{
parentCollection= new Collection();
var parentParser = sqlDataReader.GetRowParser<Parent>(typeof(Parent));
var paramClassParser = sqlDataReader.GetRowParser<ParamClass>(typeof(ParamClass));
var childAParser = sqlDataReader.GetRowParser<ChildA>(typeof(ChildA));
var childBParser = sqlDataReader.GetRowParser<ChildB>(typeof(ChildB));
ChildType type = ChildType.None;
Parent parent;
while (sqlDataReader.Read())
{
type = (ChildType)sqlDataReader["ChildTypeId"];
switch(type)
{
case ChildType.ChildA:
parent = childAParser(sqlDataReader);
break;
case ChildType.ChildB:
parent = childBParser(sqlDataReader);
break;
default:
parent = parentParser(sqlDataReader);
break;
}
parent.paramClass = paramClassParser(sqlDataReader);
parentCollection.Add(parent);
}}

How to loop an IList of objects and set state to modified for SQL

I am trying to loop a list of objects and set them as modified before posting to SQL. but I am getting an error Object Reference not set to an instance of an object.
setting a single instance of object works fine using
[BindProperty]
public Models.Ord Order { get; set; }
Order = await _context.Ord.SingleOrDefaultAsync(m => m.Id == id);
_context.Attach(Order).State = EntityState.Modified;
But this return an error.
[BindProperty]
public IList<OrdLn> OrderLineList { get; private set; }
OrderLineList = await _context.OrdLn.Where(o => o.OrdId == id).ToListAsync();
foreach (OrdLn p in OrderLineList)
{
_context.Attach(p).State = EntityState.Modified;
}
await _context.SaveChangesAsync();
The 'Q' in LINQ stands for "Query". LINQ is not meant to update objects.
You can use LINQ to find the object you want to update and then update it "traditionally".
When you are using SingleOrDefault function, the object in remote or database until you make changing the entity type(changing to new class), so there you can make change on the entity and db operation able to carryout.
When you are using List function. the object is in memory of the application so there is relation between db and will not able to carryout db operate operation.
All of these return types have interfaces inherit from IEnumerable, which you should make sure you understand. That interface basically lets you use the class in a foreach statement (in C#).
IList : is everything that ICollection is, but it also supports adding and removing items, retrieving items by index, etc. It's the most commonly-used interface for "lists of objects", which is vague I know.
IQueryable : is an enumerable interface that supports LINQ. You can always create an IQueryable from an IList and use LINQ to Objects, but you also find IQueryable used for deferred execution of SQL statements in LINQ to SQL and LINQ to Entities.

Querying with linq a collection mapped as a map (IDictionary)

Using NHibernate, I have a collection of entities mapped as a dictionary.
By example, class A has a collection of B named Children, mapped as a IDictionary<int, B>. B has a property Name.
Querying class A base on some condition on B children unrelated to their dictionary indexation is quite straightforward with HQL:
from A where A.Children.Name = 'aName'
Runs flawlessly.
But for achieving the same with LINQ, this is quite less straightforward:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Fails with message could not resolve property: Values of: B
So yes, we can trick it through
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
That does works and yields expected results.
But this looks to me a bit ugly, I would rather not have to do that 'invalid' cast (at least 'invalid' outside of linq2NH context).
Is there any better way for querying a children collection mapped as a IDictionary with Linq and NHibernate?
As an exercise, I have decided to extend linq-to-nhibernate for supporting Values. This give a solution to the question.
There is many ways for extending linq2NH, see this list. Here, I need to add a new 'generator', as in my answer to another question.
First you need a bunch of using:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
Then, implement HQL translation for Values.
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
Extend the default linq2NH registry with your generator:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
I had quite a hard time figuring out how to register a generic class property generator. There is built-in support for many cases including generic dictionaries methods through derived class of GenericDictionaryRuntimeMethodHqlGeneratorBase, but apparently no support for generic dictionaries properties. So I have ended up 'hard coding' it in the TryGetGenerator method for properties.
Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
Now this does work:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Disclaimer: done only as an exercise, I have not even committed that in my actual code. I am currently no more using any map in my mappings. I have added some just for testing, then I have undone all.

QueryOver IList<string> property

I have a mapped class that has a ICollection property which is mapped as a Set (using by code mappings). Note that the collection contains strings and not another mapped entity. e.g.
public class Item
{
public virtual ICollection<string> Facts { get; set; }
}
public class ItemMapping
{
public ItemMapping()
{
Set(x => x.Facts, m =>
{
m.Key(k => k.Column("ItemId"));
m.Table("Facts");
}, col => col.Element(m =>
{
m.Column("Description");
m.Type(NHibernateUtil.String);
}));
}
}
This works and CRUD operations on Items with Facts works fine.
However, I want to QueryOver<> the Facts in the database (e.g. retrieve the count or first 20 facts or retrieve some random facts) but given there is no entity how do I do this? I don't want to introduce a Fact entity because the only property it would have would be a string.
Well, my suggestion would be:
introduce entity. Even if it would have only one property. Later you can extend that (with Order, IsVisible). And if you will do that everywhere your framework will be built only from one-to-many and many-to-one relations among first citizens objects. That mean simple "framework generalization, reuse..."
But I see that you do not like it (please, at least try to re-think) - so there is the way:
NHibernate How do I query against an IList property?
Where I tried to show that (based on the documentation) we can use magical word ".elements":
17.1.4.1. Alias and property references
So the query which will touch the string elements in your case
Item item = null;
string fact = null;
var demos = session.QueryOver<Item>(() => item)
.JoinAlias(i => i.Facts, () => fact)
// instead of this
// .Add(Restrictions.Eq("fact", "abc"))
// we can use the .elements keyword
.Where(Restrictions.Eq("fact.elements", "abc"))
.List<Item>();
So, this way, you can get Items which do have some facts equal to "abc"
non entities have to be queried by their entities and selected then. For example to get the first 20 facts:
string fact = null;
var first20facts = session.QueryOver<Item>()
.JoinAlias(i => i.Facts, () => fact)
.OrderBy(() => fact).Asc
.Take(20)
.Select(() => fact)
.List<string>();
Alternativly you could also map a readonly entity for Fact just to query it.

Loading multi-level collections without duplicates in NHibernate

My question is very similar to this one (that wasn't really answered): Nhibernate: distinct results in second level Collection
I have this object model:
class EntityA
{
...
IList<EntityB> BList { get; protected set; }
...
}
class EntityB
{
... does NOT reference its parent EntityA...
IList<EntityC> CList { get; protected set; }
}
They are One-to-Many relations. EntityB and C do not have an object reference to its parent object.
I'd like to fully load the collections by performing something like the following three SQL queries to avoid to Cartesian join:
SELECT id, ... FROM EntityA;
SELECT id, idA, ... FROM EntityB;
SELECT id, idB, ... FROM EntityC;
With that, the DAL has all the information to properly fill the objects. But since EntityB is not aware of who its parent is, it has to be nHibernate who takes care of filling the collections properly.
Can it be done ??
I could do this workaround with the Cartesian Product, but it requires modifying my model to provide a collection setter and that qualifies as a patch for a technical problem with the DAL in my mind.
ICriteria criteria = session.CreateCriteria<EntityA>()
.SetFetchMode("BList", FetchMode.Join)
.SetFetchMode("BList.CList", FetchMode.Join)
.SetResultTransformer(new DistinctRootEntityResultTransformer());
IList<EntityA> listA = criteria.List<EntityA>();
foreach (EntityA objA in listA) {
objA.BList = objA.BList.Distinct().ToList();
foreach (EntityB objB in objB.BList) {
objB.CList = objB.CList.Distinct().ToList();
}
}
Have you tried this syntax:
var entities = session.QueryOver<EntityA>().Where(...).List();
var entityIds = entities.Select(e => e.Id).ToArray();
session.QueryOver<EntityA>()
.WhereRestrictionOn(a => a.Id)
.IsIn(entityIds)
.Fetch(e => e.BList).Eager
.List();
var bEntityIds = entities
.SelectMany(e => e.BList)
.Select(b => b.Id)
.ToArray();
session.QueryOver<EntityB>()
.WhereRestrictionOn(b => b.Id)
.IsIn(bEntityIds).Fetch(e => e.CList).Eager
.List();
This should fire the three selects that you mention. It might seem a wee bit convoluted, but it is taking advantage of the session's first level cache, which ensures that all the entities in the first collection is updated with the loaded collections as they are executed.
Also, you do not pay the penalty of whatever complex query-logic you may have in place for the second and third query. The DB should use the primary index for the second query (maybe even clustered depending on you settings) and a foreign key for the join for extremely low-cost queries.