NHibernate - how to get an item that is not referenced by an item in another table - nhibernate

Let's say I have a class Voucher:
public class Voucher
{
public Guid Id {get;set;}
public DateTime DateAvailable {get;set;}
}
and a class Entry
public class Entry
{
public Guid Id {get;set;}
public Voucher Voucher {get;set;}
// ... other unrelated properties
}
How can I create an NHibernate Criteria query that finds the first available voucher that is NOT currently assigned to an Entry?
The equivalent SQL would be
select
v.Id, v.DateAvailable
from
Voucher v
left join Entries e on e.VoucherId = v.Id
where
v.DateAvailable <= getutcdate() and
e.Id is null
Edit: I'm still unable to figure this one out. The Voucher table has no reference to the Entries table, but I need to find the first voucher (by date order) that has not been assigned to an entry. This seems like such a simple task, but everything I keep reading about using NHibernate criteria left joins requires the Voucher object to have a property that references the entry. Surely there's a way to invert the query or add a reference property to the Voucher object without modifying the database to have each table reference the other.
Edit 2: For what it's worth, I don't think it's possible to do what I was trying to do without some modifications. I eventually got a query to work using the Entry as the primary criteria with the Voucher as a sub-criteria, but then UniqueResult returned null, even if the data was there. I guess it just couldn't make the association.
In case anyone runs into this, I ended up making a foreign key in each table that references the other and using the References<> mapping to associate the two. It's not idea, because I have to manually set each entity's sub property to the other to make the association bidirectional, but it at least works without a ton of changes.

Translating your SQL literally:
var voucher = NHibernateSessionManager.Session.CreateCriteria<Voucher>("v")
.Add(Restrictions.Le("v.DateAvailable", DateTime.UtcNow))
.CreateCriteria("Entries", "e")
.Add(Restrictions.IsNull("e.Id"))
.SetMaxResults(1)
.UniqueResult<Voucher>();
Now if I understand this correctly there may be an alterantive.
If the statement: "..finds the first available voucher that is NOT currently assigned to an Entry..." is the same with the statment "finds the first available voucher that has no entries..." (since the voucher is the entity that has many entries... according to your classes) then you could do:
var voucher = NHibernateSessionManager.Session.CreateCriteria<Voucher>()
.Add(Restrictions.IsEmpty("Entries"))
.Add(Restrictions.Le("DateAvailable", DateTime.UtcNow))
.SetMaxResults(1)
.UniqueResult<Voucher>();
...assuming that you have mapped the Entries property in the Voucher entity.
But maybe I got it wrong...

The following should work using HQL:
IQuery query = session.CreateQuery(#"
FROM
Voucher v LEFT JOIN
Entry e
WHERE
e.ID IS NULL AND
v.DateAvailable <= :now
");
query.SetParameter("now", DateTime.Now);
// If you want to restrict to only the first result
query.SetMaxResults(1);
Voucher v = query.UniqueResult<Voucher>();
// Otherwise, get a list of results...
// List<Entry> results = query.List<Entry>();

From memory, might not work:
session.CreateQuery<Entry>().CreateAlias("Voucher","v").Add(Restrictions.And(
Restrictions.Lt("DateAvailable",DateTime.Now),
Restrictions.Eq("v.Id",null")
).List<Voucher>();

Related

SQL subquery result in LINQ and Entity Framework Code First

I want to make a query that'll return me entities and additionally a number of one of their associated entities. For example:
select *, (select COUNT(*) from Forms where Contact_Id = Contacts.Id)
as FormsCount from Contacts;
My Contact entity has a property named FormsCount, but it isn't mapped since there's no column named like that in the table. Is it possible to write one LINQ query that'll return me Contact entities with the additional FormsCount property filled in?
Alternatively, I'd be happy if I could get the FormsCount values in a separate field and I can copy them to the entities manually. The result from the query could be in this form for example:
{
Contact Contact;
int FormsCount;
}
Then I can iterate over the results and copy FormsCount to Contact. Maybe this can be achieved by using projections?
I know how to do that using 2 queries:
a) fetch contact entities first
b) fetch pairs or contact ID and FormsCount for contacts returned in the first query.
But I'd like to do that using one query. Also, I don't want the FormsCount property to be always filled in my Contact entity, I want to have a control over that. Any ideas?
Thanks,
Michal
You are right about the projection.
If Contact has a navigation property Forms you can project:
from c in context.Contacts
select new { Contact = c, FormsCount = c.Forms.Count() }
If not, you'll have to use a subquery:
from c in context.Contacts
select new
{
Contact = c,
FormsCount = context.Forms.Count(f => f.Contact_Id == c.Id)
}
EF will handle both situations in one SQL query.

Efficiently getting a count of child records when returning parent record(s)

I am using Entity Framework 4.1 using the Code First approach. I have two entities that exhibit a parent-child relationship. To provide a concrete example, imagine I have a Category entity that has zero-to-many Product entities associated with it. I have set up navigation properties on both sides (in my example, the Category entity would have an ICollection<Product> property while the Product entity has a Category property).
Now, when I get Category entities I want to also get back a count of the number of children records for each category. I am aware I can do:
Category category = dbContext.Categories.Single(...);
int productCount = category.Products.Count();
But I am concerned because the resulting SQL that gets sent to the database depends on whether I use lazy or eager loading.
In the first case (lazy loading), the call to the Products collection prompts EF to run a SQL query like:
SELECT ... all columns ...
FROM Products
WHERE CategoryID = #CategoryID
In the second case (eager loading), the products are loaded when the category information is retrieved so there is no second query to the database, but the downside is that if I'm not at all interested in products (other than their count) then I'm bringing back a lot of unneeded data.
What I'd like it to have the best of both worlds: namely, the ability to have just one database query and one that uses SELECT COUNT(*) rather than one that gets all of the columns from the table. In short, I'd like SQL like the following to be sent to the database:
SELECT ... all category columns ...,
(SELECT COUNT(*) FROM Products p WHERE p.CategoryID = c.CategoryID)
FROM Categories c
WHERE c.CategoryID = ...
Is that at all possible with EF or is what I want a pipe dream?
Not sure, but maybe try this:
var result = db.Categories.Where(x => x.CategoryId == categoryId)
.Select(y => new
{
Count = y.Products.Count(),
Category = y
})
.Single();
//result.Count
//result.Category
Yes, this is possible with EF. You can also create a view model to show the information with the child counts as properties. This article cover how to do that.
http://www.ozkary.com/2015/04/entity-framework-associated-table.html

HQL Query - Castle Active Record - Outer Joins

I am trying to grab data from the DB of a particular table, where there is either no join to a another table, or there is but it isn't the right Data.
Structure
I have a domains table. All this does is hold a domain name, and a few other misc metadata.
I have a features table, all this has is an ID column, and a column for a value of what that feature is. E.g. It could look like this :
1 | First Registered
2 | Expired On
3 | Hosted On
Etc.
I have a DomainData table. This holds the values for the features. The columns are something like this.
ID | DomainId | FeatureId | Value
Essentially it holds the value of the feature for that domain, something like a key value column so that it can be ever expanding without modifying the table structure of the Domain table.
Now, what I need to do is do a query for all domains that do not have feature X.
For example, this works in SQL :
SELECT D.*
FROM Domain AS D
LEFT OUTER JOIN DomainData AS Data ON Data.DomainId = D.Id
WHERE data.featureId IS NULL OR data.FeatureId != #FeatureId
That works fine in SQL, and returns all needed data. All that does is grab all domains that either have NO features at all, or they do have features, but not the ones I require.
Now I am using CastleActiveRecord as my datalayer, but I am struggling to as to how I can write this in HQL. Spent many an hour on it now, and I either get back nothing, Or I get back ALL domains, regardless of their feature ID. Unfortunately I have deleted all the HQL statements that I have tried.
Can I get some help on how I would rewrite the above statement to HQL?
Side Note : Inside my classes, I have this inside the DomainData Class :
[BelongsTo("DomainId")]
public Domain Domain { get; set; }
[BelongsTo("FeatureId")]
public Feature Feature { get; set; }
And this inside my Domain Class :
private IList<DomainData> featureData = new List<DomainData>();
[HasMany(typeof(DomainData), Table = "DomainData", ColumnKey = "DomainId")]
public IList<DomainData> FeatureData
{
get { return featureData; }
set { featureData = value; }
}
I think that is the correct data structure? But correct me if I am wrong. It could be how I am doing the structure rather than the query itself.
check this out:
select d
from Domain d left join d.DomainData ddata
where ddata.Feature is null OR ddata.Feature.Id <> :featureId
notice that i'm calling IS NULL on the mapped entity Feature yet the resulting query will check the actual FK column. As a comment i find it odd that you need to check for null for featureid since you also compare with the #FeatureId variable which i'm guessing is not null.

How to build custom PLINQO query by multiple id's?

Here's my table structure
Places
PlaceId PK
Name
...
PlaceCategories
CatId PK
Name
...
PlaceCats
PlaceId PK
CatId PK
Here's my query that pulls Places based on category id (table join)
public static IQueryable<Places> ByPlaceCat(this Table<Places> table, Expression<Func<PlaceCats, bool>> predicate) {
var db = (DataContext)table.Context;
var innerBizBase = db.PlaceCats.Where(predicate);
return db.Places.Join(innerBizBase, a => a.PlaceId, ab => ab.PlaceId, (a, ab) => a);
}
I use it like this:
places = Db.Places.ByPlaceCat(a => a.CatId == 5);
But I want to be able to pull based on a List<int> of category id's. Looking through the generated PLINQO code, a query that pulls by multiple PlaceId's (but not using a joined table) looks like this:
public static IQueryable<Places> ByPlaceId(this IQueryable<Places> queryable, IEnumerable<long> values)
{
return queryable.Where(p => values.Contains(p.PlaceId));
}
How could I essentially merge those two queries, to let me pass in a List<int> of CatId's to query by? This LINQ/PLINQO query is melting my brain. Thanks in advance!
You would need to write a extension method like this:
public static IQueryable<Places> ByPlaceCats(this Table<Places> table, IEnumerable<int> catIds)
{
var db = (TestDataContext)table.Context;
var places = (from placeCat in db.PlaceCats
join place in db.Places on placeCat.PlaceId equals place.PlaceId
where catIds.Contains(placeCat.CatId)
select place);
return places;
}
Please note that the PlaceCats table could be made into a ManyToMany relationship by adding two foreign keys to the proper tables. Once this change has been made than PLINQO will automatically generate the correct code and will create a link between the two tables skipping the intermediary table. So you could get a collection of PlaceCategories associated to the current Places entity by accessing a property on the Places entity.
Please remember to contact us if you have any questions and be sure to check out the community forums located here and PLINQO forums here.
Thanks
-Blake Niemyjski (CodeSmith Support)

How to query for most commonly used many-to-one in nhibernate

I have the following issue in the project I am working on. Each transaction in the system is assigned to a given user. So there is a many to one relationship between transactions and users, like so:
public class User
{
public int ID { get; private set; }
public string FirstName { get; set; }
....
}
public class Transaction
{
public int ID { get; private set; }
public User CreatedBy { get; private set; }
...
}
I have mapped these entities with NHibernate so that there is a many-to-one mapping between the Transaction and the User classes. The User object doesn't have a list of transactions, but the Transaction has a reference to the User that created it.
Now I want to query to retrieve a list of the users who created the most transactions, but I can't figure out how to get the top 10 most referenced users using NHibernate.
Any ideas? I would like to be able to use ICriteria to complete this rather than HQL, but HQL would be ok if required.
Update
I tried sirrocco's suggestion with the query as...
DetachedCriteria topReferencedUsers = DetatchedCriteria.For(typeof(Transaction))
.SetProjection(Projections.GroupProperty("CreatedBy.Id"))
.SetProjection(Projections.Count("CreatedBy.Id").As("pcount" ))
.AddOrder(Order.Desc("pcount"))
.SetMaxResults(10);
and build that as the subquery...
GetSession().CreateCriteria(typeof (User))
.Add(Subqueries.PropertyIn("Id", topReferencedUsers))
.List<User>();
but this subquery does not group but returns the total number of transactions, which are then used as the IN clause to the User query. If I add the ProjectionList() with both projections, I get the output of the subquery that I want, but it fails because it tries to run the two column output into the IN clause of the User query. How do I get NHibernate to project both the ID and the Count, but only join on the ID?
Update (2)
I tried Sirrocco's SqlGroupProjection suggestion (thank you Sirrocco) but came up empty. First it gave me errors saying that it couldn't find the property pcount, which meant that I needed to remove the order by, which means it was ordering by some timestamp, which won't work. But even with that, it is still only outputing the count of the times that the user was referenced, not the user id with which to join the Users table. Any ideas? Thanks.
You can try it for yourself and see if you get the desired output.
var userIds = this.Session
.CreateQuery(#"
select a.User.Id
from Transaction as a
group by a.User
order by count(a.User) desc")
.SetMaxResults(10)
.List<int>().ToArray();
var users = this.Session.CreateCriteria(typeof(User))
.Add(Restrictions.InG("Id", userIds))
.List<Artist>();
return users;
The userId's that I get from the first queries are (90,22,50,55) but when passed to the second one I get my users in 22,50,55,90 order.
You could split the operation into two steps.
1) Execute topReferencedUsers, then extract the CreatedBy.Id projection into an int array in memory (since you're only dealing with 10).
2) Then Execute:
GetSession().CreateCriteria(typeof(User))
.Add(Expression.InG<int>("Id", topTenIdArray))
.List<User>();