C# linq to entity include with condition and ordering - c#

I have the following (simplified) setup:
Public class parent
{
public string name{get;set;}
public list<child> childList {get;set;}
}
Public class child
{
public int id {get;set;}
public bool imported{get;set;}
public dateTime? timeSpan {get;set;}
}
and I have this query:
var relevant = context.parent
.include(x => x.child.OrderByDescending(y => y.id).FirstOrDefaultAsync(z => z.imported == false && timeSpan == null)
.Where(x => x.child != null);
Which does not work.
Basically, I am trying to include all the parents children, but order them by id descending and then check if the first one (eg newest one) has imported == false and timeSpan == null, and only include the parent rows that have a child that meets this condition.
I know I could do this:
var relevant = context.parent
.include(x => x.child);
and then extract the data I need, but is it possible to do it in one using Linq?

As you are using the tag linq-to-entities I assume you are using entity framework.
It seems to me that you have modeled a one-to-many relation between Parent and Child: every Parent has zero or more Children, and every Child belongs to exactly one Parent.
It could also be that you have a many-to-many relation. The classes are slightly different (and the database will have an extra table that you don't have in your DbContext), but the problem remains the same.
It could be because of your simplifications, but I see some odd things in your classes that might cause your problems.
In entity framework a proper one-to-many relation is modelled as follows:
public class Parent
{
public int Id {get;set;}
public string name{get;set;}
// Every parent has zero or more Children
public virtual ICollection<Child> Children {get;set;}
}
public class Child
{
public int id {get;set;}
public bool Imported{get;set;}
public DateTime? TimeSpan {get;set;}
// every Child belongs to exactly one Parent using foreign key
public int ParentId {get; set;}
public Parent Parent {get; set;}
}
The collection of Children in your Parent can't be a List. What would ChildList[3] mean?
Besides, this Collection should be virtual (See SO: Understanding code first virtual properties)
You wrote:
Basically, I am trying to include all the parents children, but order
them by id descending and then check if the first one (eg newest one)
has imported == false and timeSpan == null, and only include the
parent rows that have a child that meets this condition.
A bit difficult to understand, but it seems that you have a sequence of Parents, and you want only those Parents and their children, where the Child with the highest ChildId is not Imported and has a null TimeSpan.
var result = dbContext.Parents
.Select(parent => new
{
// Take all Parent properties you need in your end result, for example
Id = parent.Id,
Name = parent.Name,
Children = parent.Children
.OrderByDescending(child => child.Id),
})
.Select(parent => new
{
Id = parent.Id,
Name = parent.Name,
Children = parent.Childrent,
NewestChild = parent.Children.FirstOrDefault(),
})
// keep only the parents you want to keep:
.Where(parent => parent.NewestChild != null
&& !parent.NewestChild.Imported
&& parent.NewestChild.TimeSpan == null));

Related

NHibernate save children element FK going NULL

So, I have a list of children on my parent object, and I want to persist them on my SQL Server. When I run the application for the first time, all the children get their FK correctly, but when I run it again and no new parent is added, the new child(of an existing parent) doesn't get it's parent FK, just NULL. How can I map the parent FK on my child mapping for those situations?
I've tried the Inverse() method, but as I need the parent key to be generated all children gets null anyway. I need something like, if the parent is new, then the parent will update it's children FK, but when only the child is new I would need it to do the Inverse() method, is it possible?
Some more info:
Every time I call the ParentPersist method, and it cascades as needed. I've added the AddChild() method to set the ParentId when a new child is added to the list, it's working as I debugged it, so the child is setting it's ParentId correctly.
The objects are like the following:
public class Parent
{
public virtual int Id { get; set; }
...
public virtual IList<Child> Children{ get; set; }
public virtual void AddChild(Child ch)
{
ch.IdParent = this.Id;
Children.Add(ch);
}
}
public class Child
{
public virtual int Id { get; set; }
...
public virtual int IdParent {get;set;}
}
And my mapping:
public class ParentMapping : ClassMap<Parent>
{
public ParentMapping ()
{
Id(cso => cso.Id).GeneratedBy.Identity();
...
HasMany(cso => cso.Children).KeyColumn("IdParent").Cascade.SaveUpdate().Not.LazyLoad();
}
}
public class ChildMapping : ClassMap<Child>
{
public ChildMapping ()
{
Id(cso => cso.Id).GeneratedBy.Identity();
...
}
}
Your logic (e.g. Add() method in Parent, Inverse() mapping) was OK. You were almost there. There is only one BUT...
In general, the proper (if not only correct) solution is to use objects to express realtion and not just the ValueType/int values. That's why we call it ORM - Object-relational mapping
Object in C# should look like this:
public class Parent
{
...
// correct mapping of the children
public virtual IList<Child> Children{ get; set; }
// this method uses the below updated Child version
public virtual void AddChild(Child ch)
{
// this is replaced
// ch.IdParent = this.Id;
// with this essential assignment
ch.Parent = this;
Children.Add(ch);
}
}
public class Child
{
...
// instead of this
// public virtual int IdParent {get;set;}
// we need the reference expressed as object
public virtual Parent Parent { get; set; }
}
So, now, once we have objects in place, we can adjust the mapping like this:
// parent
public ParentMapping ()
{
...
HasMany(cso => cso.Children)
.KeyColumn("IdParent")
.Inverse() // this is essential for optimized SQL Statements
.Cascade.SaveUpdate() // All delete orphan would be better
.Not.LazyLoad();
}
...
// Child
public ChildMapping ()
{
...
References(x => x.Parent, "IdParent"); // it is a to use Inverse()
}
With this Business Domain Model and the mapping (Inverse(), assigning bothe relation ends in Add() method...), NHibernat will have enough information to always (insert, update) issue proper SQL statements
NOTE: One could ask why to map Parent Parent { get; set; } and not just the int IdParent { get; set; }... In fact, if we would have existing Parent (with NOT transient ID, i.e. > 0) - there won't be any difference. The trick/problems would appear on a new Parent insertion. Almost always, assignement of the children comes before the Parent is persiseted (flushed), and its ID is recieved from DB (sql server identity). And that could/would cause the child.IdParent == 0 ...
We should remember, that in general - ORM is about objects, i.e. relation is represented by Reference types.

QueryOver using an ignored property

I have two entities:
public class Parent
{
public virtual int Id { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual int ParentId
{
get
{
if (Parent != null)
return Parent.Id;
return -1;
}
set
{
if (Parent != null)
Parent = new Parent();
Parent.Id = value;
}
}
protected virtual Parent Parent
{
get;
set;
}
}
The Parent property is set up like this to simplify the API side, and I do not want to change it to expose this property publicly. I have an override of the mappings for the Child class to accommodate this:
public class ChildMappingOverrides : IAutoMappingOverride<Child>
{
public void Override(AutoMapping<Child> mapping)
{
mapping.References<Parent>(Reveal.Member<Child>("Parent")).Column("Parent_id");
mapping.IgnoreProperty(x => x.ParentId);
}
}
Now, if I want to query all the Child objects for a given parent Id, I would perform this:
session.QueryOver<Child>().Where(c => c.ParentId == 1);
However, this throws a QueryException:
could not resolve property: ParentId of: My.Namespace.Child
How can I retrieve the set of Child objects that have a particular Parent Id?
Untested, but you could try this:
session.QueryOver<Child>()
.Where(Restrictions.Eq(
Projections.SqlProjection(
"{alias}.Parent_id as ParentId",
new[] { "ParentId" },
new[] { NHibernateUtil.Int32 }), 1))
.List<Child>();
You're not going to be able to query on unmapped associations in any NHibernate query, but this at least minimizes the loss of compile-time checking.
This is somewhat limited from what I can tell. {alias} will always be replaced by the alias of the root entity, meaning if you want to do this for a more complex query that doesn't start with Child, you might be out of luck.
I've solved it by using CreateSQLQuery instead of QueryOver:
session.CreateSQLQuery("SELECT C.* FROM CHILD C WHERE C.Parent_id = (:id)")
.AddEntity(typeof(Child))
.SetInt32("id", parentId)
.List<Child>();
I'd like to see a better way if possible, losing the compile-time checking is kind of a downer.

Joining two datatables as parent-child in linq

I need to build a typed list of parent-child objects that are read from two different Excel sources: One describes parent object, another describes child objects. The hierarchy is only 2 layers ever.
Reading into excel is not the issue, as it is read into 2 untyped datatables, but joining the information is.
The structure is very plain:
Parent has an ID and some text fields
Children have a parentID (so its 1-m) and some text fields
The objects that these are to be populated into looks like this:
public class ParkingSites
{
public List<ParkingLot> Lots { get; set; }
public ParkingSites(List<ParkingLot> arg)
{
Lots = arg;
}
}
public class ParkingLot
{
public List<Bay> Bays{ get; set; }
public int Id { get; set; }
public List<string> ParkingLotDetails { get; set; }
public ParkingLot()
{
}
}
public class Bay
{
public List<string> BayDetails { get; set; }
public int ParentId { get; set; }
public Bay()
{
}
}
The excel sources have a fixed column order with the parent sheet's first column being the parentId, and the first column on the child sheet also being the parentId.
EDIT: After playing around a bit, I just made both parent and child classes typed, as the initial reason for leaving them mostly untyped lead to more problems than it prevented. This is part of a larger project where the untypedness is a better solution for our problem on the other classes with data that is not hierarchial.
You can simply group the list of children by the parent id, and then iterate over the parents and add each child that belongs to it.
For example, you could use ToLookup:
// assuming you have all Bay instances in a collection called bays
var baymap = bays.ToLookup(b => b.ParentId);
// and all ParkingLot instances in a collection called lots
foreach(var lot in lots)
lot.Bays.AddRange(baymap[lot.Id]);
or, using the first element in the details lists:
var baymap = bays.ToLookup(b => b.BayDetails[0]);
foreach(var lot in lots)
lot.Bays.AddRange(baymap[lot.ParkingLotDetails[0]]);
or, using Where without a lookup (probably slower, depends on your data):
foreach(var lot in lots)
lot.Bays.AddRange(bays.Where(b => b.ParentId == lot.Id));

Pivoting an IEnumerable List

I am trying to apply a bit of groupby/crosstabbing logic to an IEnumerable list of a user defined object and was wondering if anyone could help me out. I'm stuck with an existing (rather annoying) object model to work with but anyway here goes...
consider the following class which I will condense to only relevant properties so you get the jist...
public class Holding
{
private void Institution;
private string Designation;
private Owner Owner;
private Event Event;
private Shares Shares;
}
I want to convert this into a list that satifys the following...
The object is grouped by Institution.
This parent list of institutions contains a list of a new object with a unique combination of Designation and Owner.
Now for each of this combination of Designation and Owner we get another child list of unique Events.
So it basically 3 lists deep.
I'm not sure if this is possible with an IEnumerable List or not, I have toyed around quite a bit with the GroupBy extension method to no avail thus far. I'd like most to do it this way, but I'm using linq-to-sql to get the initial list of holdings which is as follows and might be the better place to do the business...
public static List<Holding> GetHoldingsByEvents(
int eventId1,
int eventId2)
{
DataClassesDataContext db = new DataClassesDataContext();
var q = from h in db.Holdings
where
h.EventId == eventId1 ||
h.EventId == eventId2
select h;
return q.Distinct().ToList();
}
Any help/guidance would be much appreciated...
Thanks in advance.
I'm using ToLookup method, which is kind of a grouping, it takes two parameters, first one a function used for defining the group keys and the next one is a function used as a selector (what to take from the match).
items.ToLookup(c=>c.Institution.InstitutionId, c => new {c.Designation, c.Owner, c.Event})
.Select(c => new {
// find the institution using the original Holding list
Institution = items.First(i=>i.Institution.InstitutionId == c.Key).Institution,
// create a new property which will hold the groupings by Designation and Onwner
DesignationOwner =
// group (Designation, Owner, Event) of each Institution by Designation and Owner; Select Event as the grouping result
c.ToLookup(_do => new {_do.Designation, _do.Owner.OwnerId}, _do => _do.Event)
.Select(e => new {
// create a new Property Designation, from e.Key
Designation = e.Key.Designation,
// find the Owner from the upper group ( you can use items as well, just be carreful this is about object and will get the first match in list)
Owner = c.First(o => o.Owner.OwnerId == e.Key.OwnerId).Owner,
// select the grouped events // want Distinct? call Distinct
Events = e.Select(ev=>ev)//.Distinct()
})
})
I assumed your classes look like these
public class Holding
{
public Institution Institution {get; set;}
public string Designation {get; set;}
public Owner Owner {get; set;}
public Event Event {get; set;}
}
public class Owner
{
public int OwnerId {get; set;}
}
public class Event
{
public int EventId {get; set;}
}
public class Institution
{
public int InstitutionId {get; set;}
}

Sort child objects while selecting the parent using LINQ-to-Entities

Imagine you've got some Entity Framework entities that look like this (obviously not these specific classes, but the autogenerated ones with all the Entity Framework plumbing; these are just for illustration):
public class Parent
{
public int ID { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Parent Parent { get; set; }
public int Number { get; set; }
}
I have a LINQ query that looks like this:
from parent in context.Parents.Include("Child")
select parent
However, this returns a list of Parents where the children are in ID order. I want the children to be sorted by their Number property within their Parent.
How can this be done?
Edit: A clarification: the idea is to have the query hidden behind a method call (in the layer facade) that simply returns an IList<Parent>. This makes using solutions like anonymous class queries and manual sorting painful (compared to some panacea solution where you can just do it in the query or something).
Alex James discusses this issue in this tip.
Essentially, relationships are considered as unordered, per standard relational modeling. So you can't get them sorted. But you can project onto other collections, which can be sorted.
Take a look at this post. You could try something like this:
var query = ((from parent in context.Parents
from child in parent.Child
orderby child.Number ascending
select parent) as ObjectQuery<Parent>
).Include("Child");
One option is executing the query and sorting in memory (e.g. on output).
var parents = context.Parents.Include("Child").ToList(); //note that ToList is here just to execute the query and get the objects in memory
foreach (var p in parents)
{
//do something with parent item
foreach (var c in p.Child.OrderBy(c => c.Number))
{
/do something with the child item
}
}
There are two other options that also seem to work with their own pros and cons:
LINQ ".Include" orderby in subquery
LINQ OrderBy Name ThenBy ChildrenCollection.Name
here is something that I have done:
var query = from parent in context.Parents
select new
{
parent,
childs = from child in context.Child
orderby child.ID ascending
select new
{
child
}
}
I implememented something like this and it worked very well for me

Categories