NHibernate select complex sum - nhibernate

I have an entity:
class Entity
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
I want to select sum of (A-B-C). So I want to run sql like this:
SELECT SUM(A-B-C) FROM Entity
I can achieve it by SqlProjection:
QueryOver.Of<Entity>().Select(Projections.SqlProjection("SUM(A-B-C) AS total", new[] { "total" }, new IType[] { NHibernateUtil.Int32 }));
But I do not want to use strings. How it can be done in other way?

Unfortunately NHibernate doesn't have built in arithmetic operators. Piggybacking on this question and answer, here are a few options:
Use VarArgsSQLFunction directly:
var subtractFunction = new VarArgsSQLFunction(string.Empty, " - ", string.Empty);
session.QueryOver<Entity>(() => entityAlias)
.Select(
Projections.Sum(
Projections.SqlFunction(
subtractFunction, NHibernateUtil.Int32,
Projections.Property(() => entityAlias.A),
Projections.Property(() => entityAlias.B),
Projections.Property(() => entityAlias.C)
)
)
)
.SingleOrDefault<int?>()
This is the most straightforward way to accomplish this, but there are a few ways to dress it up.
Create your own dialect and register a - function:
public class MyDialect : MsSql2008Dialect
{
public MyDialect()
{
this.RegisterFunction("-", new VarArgsSQLFunction(string.Empty, " - ", string.Empty));
}
}
session.QueryOver<Entity>(() => entityAlias)
.Select(
Projections.Sum(
Projections.SqlFunction(
"-", NHibernateUtil.Int32,
Projections.Property(() => entityAlias.A),
Projections.Property(() => entityAlias.B),
Projections.Property(() => entityAlias.C)
)
)
)
.SingleOrDefault<int?>()
This basically allows you to avoid redefining the - function every time you use it, and is a bit cleaner.
You can go even further and refactor the projection into an extension method:
public static class CustomProjections
{
public static IProjection Subtract(IType type, params IProjection[] projections)
{
return Projections.SqlFunction("-", type, projections);
}
}
session.QueryOver<Entity>(() => entityAlias)
.Select(
Projections.Sum(
CustomProjections.Subtract(
NHibernateUtil.Int32,
Projections.Property(() => entityAlias.A),
Projections.Property(() => entityAlias.B),
Projections.Property(() => entityAlias.C)
)
)
)
.SingleOrDefault<int?>()
All of these generate the following SQL:
SELECT
sum(this_.A - this_.B - this_.C) as y0_
FROM
Entity this_

Related

Joining 3 tables and using a left outer join with linq in EF Core 3.1.1

I have 3 tables, Notices, Users, and Likes. I want to get all notices with user name and information if user likes this notice.
So far I have this code, but it returns one notice multiple times (one for each like):
return context.notices
.GroupJoin(context.Users, notice => notice.CreatedBy, user => user.Id, (notice, users) => new { notice, users })
.SelectMany(group => group.users.DefaultIfEmpty(), (group, user) =>
new
{
group.notice,
user
})
.GroupJoin(context.Likes, noticeDto => noticeDto.notice.Id, like => like.ItemId, (noticeDto, likes) => new { noticeDto, likes })
.SelectMany(group => group.likes.DefaultIfEmpty(), (group, like) =>
new NoticeDto
{
CreatedByName = (group.noticeDto.user == null ? "" : group.noticeDto.user.FullName),
Id = group.noticeDto.notice.Id,
Liked = like.CreatedBy == userId,
});
I also tried this code.. but I am getting an error:
return context.notices
.GroupJoin(context.Users, notice => notice.CreatedBy, user => user.Id, (notice, users) => new { notice, users })
.SelectMany(group => group.users.DefaultIfEmpty(), (group, user) =>
new
{
group.notice,
user
})
.GroupJoin(context.Likes, noticeDto => noticeDto.notice.Id, like => like.ItemId, (noticeDto, likes) => new { noticeDto, likes })
.Select((group) =>
new NoticeDto
{
CreatedByName = (group.noticeDto.user == null ? "" : group.noticeDto.user.FullName),
Id = group.noticeDto.notice.Id,
Liked = group.likes != null ? group.likes.Any(w => w.CreatedBy == userId) : false,
});
This is the error I get:
Processing of the LINQ expression 'DbSet
.LeftJoin(
outer: DbSet
.AsQueryable(),
inner: notice => notice.CreatedBy,
outerKeySelector: user => user.Id,
innerKeySelector: (notice, user) => new {
notice = notice,
user = user
})
.GroupJoin(
outer: DbSet,
inner: noticeDto => noticeDto.notice.Id,
outerKeySelector: like => like.ItemId,
innerKeySelector: (noticeDto, likes) => new {
noticeDto = noticeDto,
likes = likes
})'
by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Can anyone help me achieve what I need?.. Thank you.
Notice
public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
User
public Guid Id { get; set; }
public string FullName{ get; set; }
Like
public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
public Guid ItemId { get; set; }
Like's ItemId is Notice's Id? Notice's and Like's CreatedBy is User's Id?
If so, try this.
return context.notices.Include(x => x.Users)
.Include(x => x.Likes)
.Include("Likes.Users");
Database
Result
EDITED
As you do not have foreign key and relationship, then you can try this
var users = context.Users;
return context.notices.Select(x => new Notice()
{
Id = x.Id,
CreatedBy = x.CreatedBy,
Users = users.Where(y => y.Id == x.CreatedBy).FirstOrDefault(),
Likes = context.Likes.Where(y => y.ItemId == x.Id)
.Select(y => new Likes()
{
Id = y.Id,
CreatedBy = y.CreatedBy,
ItemId = y.ItemId,
Users = users.Where(z => z.Id == y.CreatedBy).FirstOrDefault()
}).ToList()
});

NHibernate, child entities are not lazy loaded

why are the children eager loaded ?
This is the relation :
public class Order
{
private IList<Product> _Product;
public virtual IReadOnlyCollection<Product> Products { get => _Product.ToList(); }
}
public class Product
{
private IList<Item> _Item;
protected internal virtual IReadOnlyCollection<Item> Items { get => _Item.ToList(); }
}
public class Item
{
private string _name;
public string Name { get => _name; }
private decimal _quantity;
public decimal Quantity { get => _quantity; }
}
Some method that gets the aggregate from DB
void Get(aggregateId)
{
var q = await Session.GetAsync<TAggregate>(aggregateId, cancellationToken);
var y = q as Order;
if (NHibernateUtil.IsInitialized(y.Products)) /// => TRUE
}
The Configurations for Order and Product
OrderConfiguration :
mapping
.HasMany<Product>(Reveal.Member<Order>("Products"))
.Access.LowerCaseField(Prefix.Underscore)
.Cascade.AllDeleteOrphan()
.Not.KeyNullable()
.Not.KeyUpdate()
.LazyLoad();
ProductConfiguration
mapping
.HasMany<Item>(Reveal.Member<Product>("Items"))
.LazyLoad()
.Component(
composit =>
{
composit
.Map(instruction => instruction.Name)
.Column("Name")
.Not.Nullable();
composit
.Map(instruction => instruction.Quantity)
.Column("Quantity")
.Not.Nullable();
});
Why does NHibernate eager loads all the data instead of waiting for it to be loaded only if needed ?
How can this be made lazy ?
Your use of ToList() on the _Product and _Item collections is inadvertently triggering the lazy loading.

NHibernate QueryOver Sum within JoinQueryOver

Though I was reading through the NHibernate Cookbook and all available forum-posts up and down, I'm still not able to get this simple query done:
I have users with everyone having one account. Each account hast a balance.
The classes look like that:
public class User
{
public virtual int Id { get; set; }
public virtual Account Account { get; set; }
public virtual bool Active { get; set; }
}
public class Account
{
public virtual int Id { get; set; }
public virtual double Balance { get; set; }
}
Now I would like to sum the balance of all active users. Nothing more...
In plain SQL it is quite easy:
SELECT SUM(a.Balance)
FROM User u
INNER JOIN Account a
ON u.Account_id = a.Id
WHERE u.Active = 'true'
I don't have any I idea, how I could solve that with the new QueryOver-Api from NHibernate 3. Could you please provide a code-example?
Thank you in advance!
Daniel Lang
EDIT
I know, that with NHibernate Linq it is very easy too, but I would like to solve it using QueryOver... Here is the working Linq-Example:
var result = Session.Query<User>()
.Where(x => x.Active)
.Sum(x => x.Account.Balance)
SOLUTION
Thanks to AlexCuse I could find the final solution (he was very very close) - here is the full code:
User userAlias = null;
Account accountAlias = null;
session.QueryOver<User>(() => userAlias)
.JoinAlias(() => userAlias.Account, () => accountAlias)
.Where(() => userAlias.Active)
.Select(Projections.Sum<Account>(acct => accountAlias.Balance))
.SingleOrDefault<double>()
Have you tried something like this?
session.QueryOver<User>(() => userAlias)
.JoinAlias(() => userAlias.Account, () => accountAlias)
.Where(() => userAlias.Active)
.Select(Projections.Sum<Account>(acct => acct.Balance))
.UnderlyingCriteria.UniqueResult()
I'm not sure what the UniqueResult equivalent is in the QueryOver API, so had to go through the underlying criteria.
You wrote in your answer:
User userAlias = null;
Account accountAlias = null;
session.QueryOver<User>(() => userAlias)
.JoinAlias(() => userAlias.Account, () => accountAlias)
.Where(() => userAlias.Active)
.Select(Projections.Sum<Account>(acct => accountAlias.Balance))
.SingleOrDefault<double>()
You don't need to have an alias for the generic type. It could be:
Account accountAlias = null;
session.QueryOver<User>()
.JoinAlias(user => user.Account, () => accountAlias)
.Where(user => user.Active)
.Select(Projections.Sum<Account>(acct => accountAlias.Balance))
.SingleOrDefault<double>()

nhibernate inner join a table without a property in class

i have the following model:
public class FlatMap : ClassMap<Flat>
{
public FlatMap()
{
Id(m => m.FlatID).GeneratedBy.Identity();
Map(m => m.Name);
Map(m => m.Notes);
Map(m => m.Released);
}
}
public class BuildingMap : ClassMap<Building>
{
public BuildingMap()
{
Id(i => i.BuildingID).GeneratedBy.Identity();
Map(m => m.Name);
HasMany<Flat>(m => m.Flats).Cascade.All().KeyColumn("BuildingID").Not.LazyLoad();
}
}
public class ContractMap : ClassMap<Contract>
{
public ContractMap()
{
Id(m => m.ContractID).GeneratedBy.Identity();
Map(m => m.Amount);
Map(m => m.BeginIn);
Map(m => m.EndIn);
References(m => m.RentedFlat);
}
}
how can i make the following query using fluent nhibernate ?
Select * From Contract
Inner Join Flat On Contract.RentedFlatID = Flat.ID
Inner Join Building On Building.BuildingID = Flat.BuildingID
Where Building.BuildingID = #p0
especially there no reference from Flat to Building?? and i don't want it to be !
of course the reference i am talking about in order to be able to do something like this
var criteria = session.CreateCriteria<Contract>().CreateCriteria ("RentedFlat").CreateCriteria ("Building"/*there is no such property in Flat class*/);
i solved a problem, but not the way as i think is good.
but i will make this as an answer until someone provide me a better solution.
i add a property BuildingID to the Flat class, and modified the mapping class to :
Map(m => m.BuildingID);
now i can do the following query:
criteria.CreateCriteria("RentedFlat")
.Add(Restrictions.Eq("BuildingID", selectedBuilding.BuildingID));

AutoMapping with FluentMapping doesn't quite seem to work for me

I'm a n00b. Here's what I want to do:
Use AutoMapping to configure every property between the model -> table. Then I would like to override 2 specific items in a fluent map. The two items are: Id & Table name.
So my Maps look like this:
public class BillMasterMap : ClassMap<BillMaster>
{
public BillMasterMap()
{
Table("BILLMAST");
Id(x => x.SYSKEY);
}
}
And my factory settings look like this:
public static ISessionFactory SessionFactory(string connectionString)
{
if (_sessionFactory == null)
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ShowSql()
.ConnectionString(c => c.Is(connectionString)).Cache(c => c.UseQueryCache().ProviderClass<HashtableCacheProvider>()))
.Mappings(m =>
m.AutoMappings.Add(AutoMap.AssemblyOf<BillMaster>()
.Where(x => x.Namespace.EndsWith("Entities"))))
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<BillInvoiceMap>())
.BuildSessionFactory();
}
return _sessionFactory;
}
The problem is that FNH finds the Id override for BillMaster, but not for BillInvoice which looks like this (identical it seems)
public class BillInvoiceMap : ClassMap<BillInvoice>
{
public BillInvoiceMap()
{
Id(x => x.SYSKEY);
Table("BILLINV");
}
}
I've gotten around the problem by configuring my automapping with the Setup() as shown below:
.Mappings(m =>
m.AutoMappings.Add(AutoMap.AssemblyOf<BillMaster>()
.Setup(s => s.FindIdentity = property => property.Name == "SYSKEY")
.Where(x => x.Namespace.EndsWith("Entities"))))
But I would like to combine auto & fluent as other tables don't use "SYSKEY" as their Id column.
Thoughts? Is this a FNH bug?
Got it working.
1.) I have to setup AutoMapping with the Setup method I described above
2.) Additionally I have to setup fluentmappings with the Id method
when both are setup like this, then it works for me.
So
public static ISessionFactory SessionFactory(string connectionString)
{
if (_sessionFactory == null)
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ShowSql()
.ConnectionString(c => c.Is(connectionString)).Cache(
c => c.UseQueryCache().ProviderClass<HashtableCacheProvider>()))
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<BaseEntity>();
m.AutoMappings.Add(AutoMap.AssemblyOf<BaseEntity>()
.Setup(s => s.FindIdentity = property => property.Name == "SYSKEY")
.Where(x => x.Namespace.EndsWith("Entities")));
})
.BuildSessionFactory();
}
return _sessionFactory;
}
And the map
public class BillInvoiceMap : ClassMap<BillInvoice>
{
public BillInvoiceMap()
{
Table("BILLINV");
Id(x => x.SYSKEY);
}
}