NHibernate extension for querying non mapped property - c#

I'm looking for a way to get total price count from the Costs list in my object. I can't get Projections.Sum to work in my QueryOver so I tried another way but I'm having problems with it. I want to use a unmapped property in my QueryOver. I found this example but it's giving an error.
Object:
public class Participant
{
public int Id { get; set; }
public double TotalPersonalCosts { get { return Costs.Where(x => x.Code.Equals("Persoonlijk") && x.CostApprovalStatus == CostApprovalStatus.AdministratorApproved).Sum(x => x.Price.Amount); } }
public IList<Cost> Costs { get; set; }
}
The property TotalPersonalCosts is not mapped and contains the total price count.
Extension Class:
public static class ParticipantExtensions
{
private static string BuildPropertyName(string alias, string property)
{
if (!string.IsNullOrEmpty(alias))
{
return string.Format("{0}.{1}", alias, property);
}
return property;
}
public static IProjection ProcessTotalPersonalCosts(System.Linq.Expressions.Expression expr)
{
Expression<Func<Participant, double>> w = r => r.TotalPersonalCosts;
string aliasName = ExpressionProcessor.FindMemberExpression(expr);
string totalPersonalCostName = ExpressionProcessor.FindMemberExpression(w.Body);
PropertyProjection totalPersonalCostProjection =
Projections.Property(BuildPropertyName(aliasName, totalPersonalCostName));
return totalPersonalCostProjection;
}
}
My QueryOver:
public override PagedList<AccountantViewInfo> Execute()
{
ExpressionProcessor.RegisterCustomProjection(
() => default(Participant).TotalPersonalCosts,
expr => ParticipantExtensions.ProcessTotalPersonalCosts(expr.Expression));
AccountantViewInfo infoLine = null;
Trip tr = null;
Participant pa = null;
Cost c = null;
Price p = null;
var infoLines = Session.QueryOver(() => tr)
.JoinAlias(() => tr.Participants, () => pa);
if (_status == 0)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted || pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
else if (_status == 1)
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPrinted);
else
infoLines.Where(() => pa.TotalCostApprovalStatus == TotalCostApprovalStatus.CostPaid);
infoLines.WhereRestrictionOn(() => pa.Employee.Id).IsIn(_employeeIds)
.Select(
Projections.Property("pa.Id").WithAlias(() => infoLine.Id),
Projections.Property("pa.Employee").WithAlias(() => infoLine.Employee),
Projections.Property("pa.ProjectCode").WithAlias(() => infoLine.ProjectCode),
Projections.Property("tr.Id").WithAlias(() => infoLine.TripId),
Projections.Property("tr.Destination").WithAlias(() => infoLine.Destination),
Projections.Property("tr.Period").WithAlias(() => infoLine.Period),
Projections.Property("pa.TotalPersonalCosts").WithAlias(() => infoLine.Period)
);
infoLines.TransformUsing(Transformers.AliasToBean<AccountantViewInfo>());
var count = infoLines.List<AccountantViewInfo>().Count();
var items = infoLines.List<AccountantViewInfo>().ToList().Skip((_myPage - 1) * _itemsPerPage).Take(_itemsPerPage).Distinct();
return new PagedList<AccountantViewInfo>
{
Items = items.ToList(),
Page = _myPage,
ResultsPerPage = _itemsPerPage,
TotalResults = count,
};
}
Here the .Expression property is not found from expr.
I don't know what I'm doing wrong. Any help or alternatives would be much appreciated!
Solution with Projection.Sum() thx to xanatos
.Select(
Projections.Group(() => pa.Id).WithAlias(() => infoLine.Id),
Projections.Group(() => pa.Employee).WithAlias(() => infoLine.Employee),
Projections.Group(() => pa.ProjectCode).WithAlias(() => infoLine.ProjectCode),
Projections.Group(() => tr.Id).WithAlias(() => infoLine.TripId),
Projections.Group(() => tr.Destination).WithAlias(() => infoLine.Destination),
Projections.Group(() => tr.Period).WithAlias(() => infoLine.Period),
Projections.Sum(() => c.Price.Amount).WithAlias(() => infoLine.TotalPersonalCost)
);

You can't use unmapped columns as projection columns of a NHibernate query.
And the way you are trying to do it is conceptually wrong: the ParticipantExtensions methods will be called BEFORE executing the query to the server, and their purpose is to modify the SQL query that will be executed. An IProjection (the "thing" that is returned by ProcessTotalPersonaCosts) is a something that will be put between the SELECT and the FROM in the query. The TotalCosts can't be returned by the SQL server because the SQL doesn't know about TotalCosts

Related

Linq where clause doesn't work in Entity Framework

This code doesn't work (returns null):
var result = context.Data
.Select(x => x)
.Where(x => x.ID == 1)
.FirstOrDefault();
But this:
var result = context.Data.Take(1);
works.
My question is why while I am using EF and the context.Data returns an IEnumerable<Data> the first code doesn't work? (And yes, the data contains element with ID equals 1)
This is a question that has really nothing to do with Entity Framework but the nature of how LINQ works with collections with it's extension methods. Let do a simple example in a console application in C#:
class Program
{
public class Faker
{
public int Id { get; set; }
public string Name { get; set; }
public Faker(int id, string name)
{
Id = id;
Name = name;
}
}
static void Main(string[] args)
{
var ls = new List<Faker>
{
new Faker(1, "A"),
new Faker(2, "B")
};
Faker singleItem = ls.FirstOrDefault(x => x.Id == 1);
IEnumerable<Faker> collectionWithSingleItem = ls.Where(x => x.Id == 1);
Console.ReadLine();
}
}
When I pause under 'Locals' I see the variables populated as such:
The simple answer is : it should work. Although your line could be optimized to :
var v = context.Data.FirstOrDefault(x => x.ID == 1);
So, basically there is no ID == 1 in your database, or you misspelled something.
If you wanted a IEnumerable<T> type then :
var v = context.Data.Where(x => x.ID == 1);
But I'd rather use list :
var v = context.Data.Where(x => x.ID == 1).ToList();

Querying array of strings by array of strings in elasticsearch.net

I'm using elasticsearch.net library in C# and I'm trying to query for objects matching specified filter.
I would like the query to return objects where at least one of input names from filter exists in object's Names collection.
The problem is that I always get 0 hits as result with this query, even tho I am certain that data matching specified filter does exist in the database and I would love to find out what's wrong with my query...
The model:
public class A
{
public int AId { get; set; }
public IEnumerable<string> Names { get; set; }
}
The filtering object:
public class Filter
{
public IEnumerable<string> NamesToSearch { get; set; }
}
The method for querying data:
public async Task<IEnumerable<A>> GetFilteredData(Filter filter)
{
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Terms(a => a.Names, filter.NamesToSearch))
.Fields(a => a.AId, a => a.Names));
return query.Hits
.Select(x => new A
{
AId = x.Fields.FieldValues<A, int>(a => a.AId)[0]
})
.ToList();
}
I have also tried following query, but it didn't yield expected result neither:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Nested(n => n.Filter(f => f.Terms(y => y.Names, filter.NamesToSearch))))
.Fields(a => a.AId, a => a.Names));
SOLUTION WHICH WORKED FOR ME:
I have upgraded a bit code from SÅ‚awomir Rosiek's answer to actually compile using ElasticSearch.net 1.7.1 and be type-safe (no references to field name by string) and ended up with following extension method, which worked like a charm for my scenario:
public static QueryContainer MatchAnyTerm<T>(this QueryDescriptor<T> descriptor, Expression<Func<T, object>> field, object[] values) where T : class, new()
{
var queryContainer = new QueryContainer();
foreach (var value in values)
{
queryContainer |= descriptor.Term(t => t.OnField(field).Value(value));
}
return queryContainer;
}
and usage:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q =>
q.Bool(b =>
b.Should(s => s.MatchAnyTerm(a => a.Names, filter.NamesToSearch.ToArray()))
.Fields(a => a.AId, a => a.Names));
I think that your problem is that you tries to pass whole array to query. Instead of that you should treat that as OR expression.
Below is the raw query that you should use:
{
"query": {
"bool": {
"should": [
{ "term": {"names": "test" } },
{ "term": {"names": "xyz" } }
]
}
}
}
And that the C# code to achive that. First I have defined helper function:
private static QueryContainer TermAny<T>(QueryContainerDescriptor<T> descriptor, Field field, object[] values) where T : class
{
QueryContainer q = new QueryContainer();
foreach (var value in values)
{
q |= descriptor.Term(t => t.Field(field).Value(value));
}
return q;
}
And now the query:
string[] values = new[] { "test", "xyz" };
client.Search<A>(x => x.Query(
q => q.Bool(
b => b.Should(s => TermAny(s, "names", values)))));

Return list of items in order of how many related items there are

I want to return a list of items in order of how many related items there are.
Imagine the following classes. and imagine they all had DbSets... context.A..., context.B...
class A
{
public ID { get; set; }
}
class B
{
public virtual A A { get; set; }
}
I am trying to get a list of A items in order of most related from B. The query might look like this:
IEnumerable<A> GetMostRelatedAs( int numberOfAsToReturn )
{
return this.context.A.SelectMany(
a => a.ID,
( whatever) => new
{
A = whatever,
RelatedBCount = this.context.B.Where( b => b.A.ID == whatever.ID)
}).OrderByDescending( x => x.RelatedBCount ).Take( numberOfAsToReturn );
}
Where am I going wrong in my query?
Due to this:
I am trying to get a list of A items in order of most related to from B.
to from makes this quite confusing, so on this basis I'm going to have a stab in the dark with this one:
IEnumerable<dynamic> GetMostRelatedAs( int numberOfAsToReturn )
{
var results = this.context.A
.GroupJoin(
this.context.B,
a => a.ID,
b => b.A.ID,
(singleA, multipleBs) => new {
// this is the projection, so take here what you want
numberOfBs = multipleBs.Count(),
name = singleA.Name,
singleA.ViewCount
}
)
.OrderByDescending(x => x.ViewCount)
.Take(numberOfAsToReturn)
.ToList();
// here you can use automapper to project to a type that you can use
// So you could add the following method calls after the ToList()
// .Project(this.mappingEngine)
// .To<ClassThatRepresentsStructure>()
// The reason you don't map before the ToList is that you are already doing a projection with that anonymous type.
return results;
}
Edit
To address the comments:
IEnumerable<A> GetMostRelatedAs( int numberOfAsToReturn )
{
var results = this.context.A
.GroupJoin(
this.context.B,
a => a.ID,
b => b.A.ID,
(singleA, multipleBs) => new {
// this is the projection, so take here what you want
numberOfBs = multipleBs.Count(),
name = singleA.Name,
singleA.ViewCount,
singleA
}
)
.OrderByDescending(x => x.ViewCount)
.Take(numberOfAsToReturn)
.ToList()
.Select(x => x.singleA);
return results;
}

Possible to wrap Expression<> and Func<> after Func defined?

I create two kinds of nearly identical mapping functions in my View Models to map from POCOs, one for Queryables and one for Collections. Is it possible to create one method that does both so I can remove the duplicate code? I'd like to only keep the Expression<Func<>> then use it on local Collections and Entity Framework.
public class MyViewModel
{
public static readonly Expression<Func<MyPOCO, MyViewModel>> AsMap =
(e) => new ImportPattern
{ id = e.id,
name = e.name
}
public static readonly Func<MyPOCO, MyViewModel>> ToMap =
(e) => new ImportPattern
{ id = e.id,
name = e.name
}
public int id { get; set; }
public string name { get; set; }
}
Examples: AsMap succeeds with Entity Framework to project only the fields listed in the mapping function.
var a = Product
.Where(p => p.id > 0 && p.id < 10)
.OrderBy(q => q.id)
.Select( ImportPattern.AsMap );
ToMap successfully works on local Collections.
var products = Products.Where( p => p.id > 0 && p.id < 10).ToList();
var a = products
.OrderBy(q => q.id)
.Select( ImportPattern.ToMap );
Using AsMap on local collections fails with a Non-invocable member error. Here's a few more that fail:
.Select( ImportPattern.ToMap.Compile().Invoke() )
.Select( o => ImportPattern.ToMap.Compile().Invoke(o) )
FYI, not looking for an AutoMapper() answer please.
What about
public static readonly Expression<Func<MyPOCO, MyViewModel>> AsMap =
(e) => new ImportPattern
{ id = e.id,
name = e.name
}
public static readonly Func<MyPOCO, MyViewModel>> ToMap = AsMap.Compile();
It does not really hurt since you do that only once, but you immediately get rid of the duplicate code.
You need to pass the delegate directly:
Select( ImportPattern.ToMap.Compile() )
Note that Compile() is a slow call; you should cache its result.

Linq: dynamic Where clause inside a nested subquery

In the past I've dealt with optional search criteria by dynamically adding filters to a Linq query like this:
public IEnumerable<Customer> FindCustomers(string name)
{
IEnumerable<Customer> customers = GetCustomers();
var results = customers.AsQueryable();
if (name != null)
{
results = results.Where(customer => customer.Name == name);
}
results = results.OrderBy(customer => customer.Name);
return results;
}
or similarly using predicates, where you basically just move the lambda from the Where into a Func<> (or Expression<Func<>> if using LinqToEntities), like this:
public IEnumerable<Customer> FindCustomers(string name)
{
Func<Customer, bool> searchPredicate = customer => true;
if (name != null)
{
searchPredicate = customer => customer.Name == name;
}
IEnumerable<Customer> customers = GetCustomers();
var results = customers
.Where(searchPredicate)
.OrderBy(customer => customer.Name);
return results;
}
However I can't figure out how to do something similar when the Where clause is buried somewhere in a nested subquery. Consider the following (made up) scenario:
public class Customer
{
public string Name;
public int MaxOrderItemAmount;
public ICollection<Order> PendingOrders;
public ICollection<OrderItem> FailedOrderItems;
public ICollection<Order> CompletedOrders;
}
public class Order
{
public int Id;
public ICollection<OrderItem> Items;
}
public class OrderItem
{
public int Amount;
}
public IEnumerable<OrderItem> FindInterestingOrderItems(
bool onlyIncludePendingItemsOverLimit)
{
var customers = GetCustomersWithOrders();
// This approach works, but yields an unnecessarily complex SQL
// query when onlyIncludePendingItemsOverLimit is false
var interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(orderItem => onlyIncludePendingItemsOverLimit == false
|| orderItem.Amount > customer.MaxOrderItemAmount))
.Union(customer.FailedOrderItems)
);
// Instead I'd like to dynamically add the Where clause only if needed:
Func<OrderItem, bool> pendingOrderItemPredicate = orderItem => true;
if (onlyIncludePendingItemsOverLimit)
{
pendingOrderItemPredicate =
orderItem => orderItem.Amount > customer.MaxOrderItemAmount;
// PROBLEM: customer not defined here ^^^
}
interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(pendingOrderItemPredicate)
.Union(customer.FailedOrderItems)))
.OrderByDescending(orderItem => orderItem.Amount);
return interestingOrderItems;
}
Obviously I can't just move the lambda to a Func<> this time because it contains a reference to a variable (customer) defined by a higher-level part of the query. What am I missing here?
This works: building the predicate in a separate function, passing the "customer" value from the higher-level lambda as a parameter.
publicvoid FindInterestingOrderItems(bool onlyIncludePendingItemsOverLimit)
{
var customers = GetCustomersWithOrders();
var interestingOrderItems = customers
.SelectMany(customer => customer.PendingOrders
.SelectMany(order => order.Items
.Where(GetFilter(customer, onlyIncludePendingItemsOverLimit))
.Union(customer.FailedOrderItems)))
.OrderByDescending(orderItem => orderItem.Amount);
}
private Func<OrderItem, bool> GetFilter(Customer customer, bool onlyIncludePendingItemsOverLimit)
{
if (onlyIncludePendingItemsOverLimit)
{
return orderItem => orderItem.Amount > customer.MaxOrderItemAmount;
}
else
{
return orderItem => true;
}
}

Categories