Where-clause for LINQ-to-NHibernate FetchMany? - linq

Using LINQ-to-NHibernate is there a way to narrow down what FetchMany() returns?
Given the following class structure
public class Foo
{
public virtual int Id { get; set; }
public virtual IList<Bar> Bars { get; set; }
}
public class Bar
{
public virtual string Description { get; set; }
}
How can I do this:
session.Query<Foo>()
.Where(foo => foo.Id > 30)
.FetchMany(foo =>
foo.Bars.Where(bar => bar.Description.StartsWith("x")));
And NHibernate will return all Foo's with an Id > 30 and for those Foo's all the attached Bar's where the Bar's description starts with the letter 'x'?
I found some posts that use the old QueryOver() stuff but I explicitely want to use NHibernate's LINQ provider.
Any ideas?
Update
I think I need to clarify what I want as a result.
<Foo Id="1">
<Bar Description="x1"/>
<Bar Description="b1"/>
</Foo>
<Foo Id="31">
<Bar Description="x2"/>
<Bar Description="x3"/>
<Bar Description="b2"/>
</Foo>
<Foo Id="32">
<Bar Description="b3"/>
</Foo>
From the data outlined above I expect the following result
<Foo Id="31">
<Bar Description="x2"/>
<Bar Description="x3"/>
</Foo>
<Foo Id="32"/>
The additional Where clause should only work on the Bar's! It should not further narrow down the list of Foo's! Just reduce what FetchMany() returns.

I'm fairly sure you're out of luck with the current linq provider - but for a non-linq (and cross-cutting) option, you might want to have a look at the filter functionality included in NHibernate - it would probably be the best bet for implementing this in a large scale / complex project.

var query = from foo in session.Query<Foo>()
where foo.Id >30
from bar in foo.Bars
where bar.Description.StartsWith("x")
select new { Id = foo, Bar = bar};
var results = query.ToList().ToLookup(x => x, x => x.Bar);
foreach(var foo in results.Keys)
{
var barsWhichStartWithX = results[foo];
//DO STUFF
}
Although this may produce inefficient SQL (I don't use nHibernate so I wouldn't know). You could also try this...Also the above would miss out foos without bars.
var foosQuery = session.Query<Foo>().Where(foo => foo.Id > 30);
var foos = foosQuery.Future();
var barsQuery = from f in foosQuery
from bar in foo.Bars
select new { Id = foo.Id, Bar = bar};
var foosDict = foos.ToDictionary(x => x.Id);
var bars = barsQuery.ToList().ToLookup(x => foosDict[x.Id], x => x.Bar);
foreach(var foo in foos)
{
var barsWhichStartWithX = bars[foo];
//DO STUFF
}

Perhaps not exactly what you're after, but certainly worth considering is the NHibernate.CollectionQuery library.
It allows you to query uninitialized NHibernate collections on an entity using Linq - but would require an additional query/round-trip to get them (unlike FetchMany, which grabs the entire collection in the same round-trip).

You will need a reference from child object to the parent.
var result = from foo in session.Query<Foo>().FetchMany(f => f.Bars)
from bar in session.Query<Bar>()
where foo.Id == bar.FooId && // FooId is missing in your entity
foo.Id > 30
bar.Description.StartsWith("x")
select foo;

Related

C#7: How to use tuples in generic methods (LINQ select example)

I have some heavily repeating code, which has always the same structure, just using different columns in a database for accessing it and doing similar stuff
A typical query looks like:
var portfolioIds = context.PortSelMotorSeries
.Select(x => new { x.Id, x.InstallationAltitudeMax })
.ToList();
Now I want to use a generic function for dependency inversion and to pass the selector function as a delegate to the query:
private void ForEachIterate<T1>(Func<MotorSeriesDb, T1> selectorFunc): where T1 : (int Id, double Value)
{
...
var portfolioIds = context.PortSelMotorSeries
.Select(selectorFunct)
.ToList();
...
}
So that I can call the query with my own selector:
ForEachIterate(x => new { Id = x.Id, Value = x.InstallationAltitudeMax });
ForEachIterate(x => new { Id = x.Id, Value = x.TemperatureMax });
Specifying the constraint with "where T1 : (int Id, double Value)" leads to a compiler error CS0701.
Leaving it away leads to other compiler errors.
Is there any way to use tuples in generic functions?
For one thing you're confusing tuples (the (int Id, double Value) thing) with anonymous classes (the new { Id = x.Id, Value = x.TemperatureMax }). They aren't even related, so your code would never work as is.
For another, if all you want is to force the user to output a tuple of some specific type, you can do something like this:
private void ForEachIterate(Func<MotorSeriesDb, (int, double)> selectorFunc)
{
...
var portfolioIds = context.PortSelMotorSeries
.Select(selectorFunct)
.ToList();
...
}
// call like:
ForEachIterate(x => (x.Id, x.InstallationAltitudeMax));
Note that there's nothing generic about your function at all. Which leads me to my third point: you're missing the entire point of Linq. You talk about inversion of control, but you're the one who's inverting it in the wrong direction to begin with.
You already have a construct that allows arbitrary selection: context.PortSelMotorSeries. Simply use Linq to select what you want out of it in the call site and you're done.
If you try this it could works:
private static void ForEachIterate<T1>(Func<MotorSeriesDb, T1> selectorFunc) where T1 : Tuple<int, double>
{
var portfolioIds = context.PortSelMotorSeries
.Select(selectorFunct)
.ToList();
}

EF6/Code First and indexes: Is there anything special to do to access/use indexes?

I'm in my first Code First project. I just learned how to add an index on two columns.
[Required]
[Index("IX_NameAndCity", 1, IsUnique = false)]
[MaxLength(900)]
public string Name { get; set; }
[Index("IX_NameAndCity", 2, IsUnique = false)]
[MaxLength(900)]
public string City { get; set; }
Does this look right? ^^^
Is there anything special in the LINQ to utilize these indexes or is it transparent? I was half expecting to see a choice in my LINQ for '.IX_NameAndCity'.
Here's what I'm doing now:
var property = _propertyRepository
.GetProperties()
.FirstOrDefault(x => x.Name == name && x.City == city);
Should it be something like:
var property = _propertyRepository
.GetProperties()
.FirstOrDefault(x => x.IX_NameAndCity.name == name && IX_NameAndCity.City == city);
Or does it automagically know there's an index?
Thanks all!
The index is created on the database server. Just like you don't want to explicitly refer to the index when you write a SQL query, you don't want to explicitly refer to the index when you write a LINQ query. And actually your entity won't have an IX_NameAndCity property. So simply use your first query:
var property = _propertyRepository
.GetProperties()
.FirstOrDefault(x => x.Name == name && x.City == city);
Entity Framework will construct the corresponding SQL query and pass it to the database server, and the database server will know it should (or possibly should not) use the index to speed up the query execution. It's transparent; don't worry about it.

nHibernate join and take one record

I'm doing the below join, there are many bookingActions records, but I want there to only be one BookingAction record per booking record. I want the BookingAction record that has the highest primary key value.
How would I do this?
var bookingLocationsQuery = (
from
booking in session.Query<Booking>()
join
bookingActions in session.Query<BookingAction>() on booking.Id equals bookingActions.bookingId
where
(booking.bookingAdminID == userId)
select new { booking, bookingActions }
);
A couple of suggestions. First, you should be leveraging NHibernate's many-to-one to do the join for you instead of doing it manually. It looks like you currently have something like this...
public class BookingAction
{
// ... other properties ...
public virtual int bookingId { get; set; }
}
<class name="BookingAction">
<!-- ... other properties ... -->
<property name="bookingId" />
</class>
Don't do that. Instead, you should have:
public class BookingAction
{
// ... other properties ...
public virtual Booking Booking { get; set; }
}
<class name="BookingAction">
<!-- ... other properties ... -->
<many-to-one name="Booking" column="bookingId" />
</class>
Similar advice for Booking.bookingAdminID. It should be a many-to-one to User, not just a simple property.
Second, after you make those changes, you should be able to accomplish your goal with a query like this:
var subquery = session.Query<BookingAction>()
.Where(a => a.Booking.Admin.Id == userId)
.GroupBy(a => a.Booking.Id)
.Select(g => g.Max(a => a.Id));
var bookingActions = session.Query<BookingAction>()
.Fetch(a => a.Booking)
.Where(a => subquery.Contains(a.Id));
Sorry about switching it to the chained extension method syntax - that's easier for me to work with. It's exactly equivalent to the from ... select syntax in execution.
Try using the Max() method, for sample:
var bookingLocation = session.Query<Booking>()
.Where(booking => booking.bookingAdminID == userId)
.Max(x => booking.bookingAdminID);

IN and NOT IN with Linq to Entities (EF4.0)

This has been ruining my life for a few days now, time to ask...
I am using Entity Framework 4.0 for my app.
A Location (such as a house or office) has one or more facilities (like a bathroom, bedroom, snooker table etc..)
I want to display a checkbox list on the location page, with a checkbox list of facilities, with the ones checked that the location currently has.
My View Model for the facilities goes like this...
public class FacilityViewItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
So when im passing the Location View Model to the UI, i want to pass a List<T> of facilities where T is of type FacilityViewItem.
To get the facilities that the location already has is simple - i make a query using Location.Facilities which returns an EntityCollection where T is of type Facility. This is because Facilities is a navigation property....
var facs = from f in location.Facilities
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name,
Checked = true
};
So here is where my problem lies - i want the rest of the facilities, the ones that the Location does not have.
I have tried using Except() and Any() and Contains() but i get the same error.
Examples of queries that do not work...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilities.Contains(f)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
var restOfFacilities = ctx.Facilities.Except(facilitiesThatLocationHas);
var notFacs = from e in ctx.Facilities
where !hasFacilities.Any(m => m.FacilityId == e.FacilityId)
select new FacilityViewItem()
{
Id = e.FacilityId,
Name = e.Name
};
And the error i get with every implementation...
System.NotSupportedException was unhandled
Message=Unable to create a constant value of type 'Chapter2ConsoleApp.Facility'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
What am i overlooking here?
ironically enough i solved it in a matter of hours after i posted the question on here, after days of suffering.
The error is basically saying 'i dont know how to calculate what items are not included by comparing strongly typed objects. Give me a list of Ints or some simple types, and i can take care of it'.
So, first you need to get a list of the primary keys, then use that in the contains clause...
//get the primary key ids...
var hasFacilityIds = from f in hasFacilities
select f.FacilityId;
//now use them in the contains clause...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilityIds.Contains(f.FacilityId)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
The first query seems fine, but you need to compare the Ids:
var restOfFacilities = from f in ctx.Facilities
where !facs.Select(fac => fac.Id).Contains(f.Id)
select f;
I wanna see what's hasFacilities, anyway, as L2E shows, "Only primitive types ('such as Int32, String, and Guid') are supported in this context", so I suppose you must retrieve first the data and put into a collection of FacilityViewItem.
var restOfFacilities = ctx
.Facilities
.Where(f => !hasFacilities.Contains(f))
.Select(f => new { f.FacilityId, f.Name })
.ToList()
.Select(f => new FacilityViewItem {
Id = f.FacilityId,
Name = f.Name
});
var notFacs = ctx
.Facilities
.Where(e => !hasFacilities.Any(m => m.FacilityId == e.FacilityId))
.Select(e => new { e.FacilityId, e.Name })
.ToList()
.Select(e => new FacilityViewItem {
Id = e.FacilityId,
Name = e.Name
});
hope it helps

Searching through multiple tables at once (Linq to SQL)?

I have a couple tables that are kind of unrelated - id like to search through both of them and create a type that i can sift through later
something like this doesnt work
var results = from dog in _dataContext.Dogs
where dog.Name.Contains(search)
from catin _dataContext.Cats
where cat.Name.Contains(search)
select new AnimalSearchResults
{
Dog = dog,
Cat = cat
};
return results;
I basically want to create a list of "AnimalSearchResults" that contains all dogs and all cats that have that name
Whats the best way to do something like this?
Sounds like you want to Union the two results so your basic query would be something like:
var results = (from dog in _dataContext.Dogs
where dog.Name.Contains(search))
.Union
(from cat in _dataContext.Cats
where cat.Name.Contains(search));
The other workaround would be to have the class Cat and the class Dog derivate the same base class, which would then carry the name.
Here's an example
public class Pet
{
public string Name {get; set;}
}
public class Dog : Pet {...}
public class Cat : Pet {...}
// I assume each classes have their singularity;
In your DataContext Object, create a GetPets() method, like this one :
public IEnumerable<Pet> GetPets()
{
return Cats.Cast().Union( Dogs.Cast() );
}
Then use LINQ on the Pets in the DataContext.
var results =
(from p in _dataContext.GetPets()
where p.Name.Contains(search)
select p);
This code would be more scalable, especially if u have more than 2 derivated classes.
{enjoy}

Resources