NHibernate parent list with child count - c#

i am using NHibernate 4 with mysql. i have got 2 tables. My tables are cat and answer.
public class cat
{
[Key]
public virtual int id { get; set; }
public virtual string catName { get; set; }
public virtual IList<answer> answers { get; set; }
}
public class answer
{
[Key]
public virtual int id { get; set; }
public virtual int catId { get; set; }
public virtual string detail { get; set; }
public virtual bool stat { get; set; }
[ForeignKey("catId")]
public virtual cat cats { get; set; }
}
i want to select all cat record(with answer list) and with their cild answers count.
my sql query like that;
select count(t2.id) as count, cats.*from cat cats left join answer t2 ON(cats.id=t2.catId and t2.stat=0) GROUP BY(cats.id);
Result like this;
id - catName - count
1 - Book - 5
2 - Pc - 0
3 - English - 22
4 - Arts - 56
i have try also this NH query;
public class myClass {
public virtual int count { get; set; }
public virtual cat cats { get; set; }
}
var u = db.CreateCriteria(typeof(cat), "cats")
.CreateAlias("answer", "t2", NHibernate.SqlCommand.JoinType.LeftOuterJoin, Restrictions.Eq("t2.stat", false))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Count("t2.id"), "count")
.Add(Projections.Group<cat>(g => g.id)));
var list = u.SetFetchMode("answer", FetchMode.Eager)
.SetResultTransformer(Transformers.AliasToBean<myClass>())
.List<myClass>();
This NHibernate query return also answers count. but cats always return null. How can i do my query for this result ?
Edit 1
i can do it like that
public class myClass {
public virtual int count { get; set; }
public virtual catId count { get; set; }
public virtual cat cats { get; set; }
}
cat cats = null;
answer answers = null;
var u = db.QueryOver<cat>(() => cats)
.JoinQueryOver(x => x.answers, () => answers, NHibernate.SqlCommand.JoinType.LeftOuterJoin, Restrictions.Eq("answers.stat", false))
.SelectList(cv => cv
.SelectCount(() => answers.id)
.SelectGroup(c => c.id))
.List<object[]>()
.Select(ax => new myClass
{
count = (int)ax[0],
catId = (int)ax[1],
cats = (cat)db.QueryOver<cat>().Where(w=>w.id==(int)ax[1]).Fetch(fe => fe.answers).Eager.SingleOrDefault()
})
.ToList();

In your result cats is always null, because the ResultTransformer tries to map the properties by their names.
Please check your NHibernate logfile. You will probably see that your query returns the columns count and id, but myClass has the properties count and cats.
Edit:
New suggestion:
The previous suggestion did not work, because the property id is of type Int32 and cannot be assigned to myClass.cat (which is of type cat).
If you don't need a reference to a cat-object in myClass then you can change it to this:
public class myClass {
public virtual int count { get; set; }
public virtual int catId { get; set; }
}
and have the projection like this:
.Add(Projections.Group<cat>(g => g.id), "catId"));
If you do need a property of type cat in your result class I don't think this can be done with a simple projection but I might be wrong.
Edit 2:
Since you require an object of type cat in your result I suggest you put it together manually after the query, e.g.:
New result class:
public class myClassResult {
public virtual int count { get; set; }
public virtual cat cats { get; set; }
}
Add this after your query logic:
IList<myClassResult> result = new List<myClassResult>();
foreach (var idWithCount in list)
{
result.Add(new myClassResult()
{
cats = catsOnlyList.FirstOrDefault(x => x.id == idWithCount.catId),
count = idWithCount.count
});
}
catsOnlyList refers to a simple list of cats that you need to get beforehand. I know this isn't pretty but I don't think you can group by cat itself in the query.
Old suggestion (does not work because of incompatible types):
Instead of
.Add(Projections.Group<cat>(g => g.id)));
use
.Add(Projections.Group<cat>(g => g.id), "cats"));

i can do that query like that;
public class myClass {
public virtual int count { get; set; }
public virtual İnt catId { get; set; }
public virtual cat cats { get; set; }
}
cat cats = null;
answer answers = null;
var u = db.QueryOver<cat>(() => cats)
.JoinQueryOver(x => x.answers, () => answers, NHibernate.SqlCommand.JoinType.LeftOuterJoin, Restrictions.Eq("answers.stat", false))
.SelectList(cv => cv
.SelectCount(() => answers.id)
.SelectGroup(c => c.id))
.List<object[]>()
.Select(ax => new myClass
{
count = (int)ax[0],
catId = (int)ax[1],
cats = (cat)db.QueryOver<cat>().Where(w=>w.id==(int)ax[1]).Fetch(fe=>fe.answers).Eager.SingleOrDefault()
})
.ToList();

Related

Linq with Lambda - how do I restrict joined table rows?

I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});

IQueryable<decimal> to decimal

I have a model class which contains a property ProductPrice of type decimal. I am not able to store an IQueryable type to a property decimal. I have even tried to Convert.ToDecimal but it still showing me the error.
Model - Product
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public int ProductQty { get; set; }
}
Model CartDispay
public class CartDisplay
{
public int ItemId { get; set; }
public String ProductName { get; set; }
public int ProductQty { get; set; }
public decimal ProductPrice { get; set; }
}
Controller
public ActionResult Index()
{
int userId = 3;
var items = _context.Cart.Join(_context.Items, c => c.CartId, i => i.CartId, (c, i) => new { c, i }).Where(c => c.c.UserId == userId).ToList();
foreach(var item in items)
{
CartDisplay display = new CartDisplay();
display.ItemId = item.i.ItemId;
display.ProductName = _context.Product.Where(p => p.ProductId == item.i.ProductId).Select(p => p.ProductName).ToString();
display.ProductPrice = _context.Product.Where(p => p.ProductId == item.i.ProductId).Select(p => p.ProductPrice); ;
display.ProductQty = item.i.ProductQty;
cartView.CartDisplay.Add(display);
}
retu
IQueryable<T> defines a sequence of elements of type T, rather than a single item. It also lets you perform additional querying without bringing the sequence into memory, but that is not important in the context of your question.
Your situation is a lot simpler, though: rather than querying for individual properties, you should query for the whole product by its ID, then take its individual properties, like this:
var prod = _context.Product.SingleOrDefault(p => p.ProductId == item.i.ProductId);
if (prod != null) {
display.ProductName = prod.ProductName
display.ProductPrice = prod.ProductPrice;
display.ProductQty = ...
} else {
// Product with id of item.i.ProductId does not exist
}

Compare object with an array with another array

I have a model Group:
public class GroupModel
{
[Key]
public int GroupModelId { get; set; }
[Required]
[MaxLength(50)]
[DataType(DataType.Text)]
public string GroupName { get; set; }
[Required]
public virtual ICollection<FocusArea> FocusAreas { get; set; }
...
And a model Focus:
public class FocusArea
{
public int FocusAreaId { get; set; }
public FocusEnum Focus { get; set; }
public List<ApplicationUser> ApplicationUser { get; set; }
public virtual ICollection<GroupModel> GroupModel { get; set; }
public enum FocusEnum
{
Psych,
Medical,
LivingWith
}
Group and Focus has a many-to-many relationship. My Controller is receiving:
public ActionResult GroupSearch(string[] focusSelected) // Values possible are Pysch, Medical and LivingWith
{
List<GroupModel> groups;
...
Problem: I want to select the groups that have all the focus that are inside the focusSelected array.
What I've tried:
groups = groups.Where(t => t.FocusAreas.Where(x => focusSelected.Contains(x.Focus))).ToList()).ToList();
Obviously not working. Does anyone have another idea?
This may help you
var result = groups.Where(g => g.FocusAreas.All(f => focusSelected
.Any(fs => (FocusEnum)Enum.Parse(typeof(FocusEnum), fs, true) == f.Focus)));
Where needs a delegate / expression that returns bool. In your sample - you are putting Where inside Where, where Where returns collection.
Changing inner Where to All should do the trick:
var allSelParsed = focusSelected.Select(s => (FocusEnum)Enum.Parse(typeof(FocusEnum), s)
.ToList();
groups = groups.Where(gr => allSelParsed.All(selected =>
gr.FocusAreas.Any(fc =>
fc.Focus == selected)))
.ToList();
This should give you expected result
var result = groups.Where(g =>
focusSelected.All(fs =>
g.FocusAreas.Any(fa => fa.ToString() == fs)));

OrmLite Selecting Multiple Columns Across Joined Tables

I'm having some difficulty populating some columns in a POCO using OrmLite. I have three tables named Dog, Bowl and DogBowl. DogBowl is a junction table and holds the id of Dog and Bowl.
Dogs
PK Id: int, not null
Breed: varchar(20), not null
Name: varchar(20), not null
Bowls
PK Id: int, not null
Type: varchar(20), not null
Color: varchar(20), not null
Dogs_Bowls
PK: DogId, not null
PK: BowlId, not null
Here are the POCOs I have mapped
public class Dog : IHasId<int>
{
[AutoIncrement]
public int Id { get; set; }
[Required]
public string Breed { get; set; }
[Required]
public string Name { get; set; }
}
public class Bowl : IHasId<int>
{
[AutoIncrement]
public int Id { get; set; }
[Required]
public string Type { get; set; }
[Required]
public string Color { get; set; }
}
public class DogBowl
{
[Required]
public int DogId { get; set; }
[Required]
public int BowlId { get; set; }
[Ignore]
public string DogName { get;set; }
[Ignore]
public string BowlColor { get;set; }
}
This is the c# code I'm running.
var dogBowl = db.Select<DogBowl>(db
.From<Dog>()
.Join<Dog, DogBowl>((d, db) => d.Id == db.DogId)
.Join<DogBowl, Bowl>((db, b) => db.BowlId == b.Id)
.Where<Dog>(d => d.Id == 5))
.ToList();
The SQL I would like to produce is this:
select
db.DogId,
db.BowlId,
d.Name AS DogName,
b.Color as BowlColor
from DogBowl db
join dog d on db.DogId = d.Id
join bowl b on db.BowlId = b.Id
where d.Id = 5
My problem is that the DogBowl.DogName and DogBowl.BowlColor properties are null after the code executes. I'm using the instructions provided on https://github.com/ServiceStack/ServiceStack.OrmLite from the section entitled "Selecting multiple columns across joined tables" but it's not working. How can I get the DogBowl.DogName and DogBowl.BowlColor properties populated?
The SQL generated may be correct. You can verify the generated SQL after execution by checking the property db.GetLastSql().
The problem is that by assigning the result as
db.Select<DogBowl>
, you are creating a List of DogBowl objects. The DogBowl properties DogName and BowlColor would always be null because there is no field in the SQL statement which matches those names exactly. OrmLite will not magically figure out what goes there - you have to have them match by name.
If you want to assign the result to a "flat" object with fields from Dog and Bowl, you could define a new DTO and assign the result, like so:
public class FullDogBowl
{
public int DogId { get; set; }
public int BowlId { get; set; }
public string Breed { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Color { get; set; }
}
var dogBowl = db.Select<FullDogBowl>(db
.From<Dog>()
.Join<Dog, DogBowl>((d, db) => d.Id == db.DogId)
.Join<DogBowl, Bowl>((db, b) => db.BowlId == b.Id)
.Where<Dog>(d => d.Id == 5))
.ToList();
Alternatively, if you know exactly the SQL you want to use, just use it:
string sql = #"select
db.DogId,
db.BowlId,
d.Name AS DogName,
b.Color as BowlColor
from DogBowl db
join dog d on db.DogId = d.Id
join bowl b on db.BowlId = b.Id
where d.Id = #dog_id ";
var dogBowlList = db.SqlList<DogBowl>(sql, new { dog_id = 5, });
Wanted to add to Raul's answer that [Ignore] tells OrmLite to ignore the property completely so your approach of re-using a table as the merged POCO "view" wont work. I recommend instead splitting the resultset POCO out into a separate POCO with all the fields you want returned:
public class DogBowl
{
[Required]
public int DogId { get; set; }
[Required]
public int BowlId { get; set; }
}
public class DogBowlInfo
{
public int DogId { get; set; }
public int BowlId { get; set; }
public string DogName { get; set; }
public string BowlColor { get; set; }
}
Which now returns a populated resultset with:
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<Dog>();
db.DropAndCreateTable<Bowl>();
db.DropAndCreateTable<DogBowl>();
var dog = new Dog { Breed = "Breed", Name = "Name" };
var bowl = new Bowl { Color = "Color", Type = "Type" };
db.Save(dog);
db.Save(bowl);
db.Insert(new DogBowl { DogId = dog.Id, BowlId = bowl.Id });
var dogBowl = db.Select<DogBowlInfo>(
db.From<Dog>()
.Join<Dog, DogBowl>((d, b) => d.Id == b.DogId)
.Join<DogBowl, Bowl>((d, b) => d.BowlId == b.Id)
.Where<Dog>(d => d.Id == dog.Id));
dogBowl.PrintDump();
}

Linq SelectMany Usage

I unable to come up with a linq query for the following scenario.
public class Product
{
public virtual string ProductName { get; set; }
public virtual IList<SubProduct> SubProducts { get; set; }
}
public class SubProduct
{
public string SubProductName { get; set; }
public int SubProductTypeId { get; set; }
}
public class SubProductType
{
public int SubProductTypeId{ get; set; }
public string Description { get; set; }
}
var productList = List<Product>();
var subProductTypeLlist = List<SubProductType>();
I have a list of products and each product has list of SubProducts. I want to get the query to represent {ProductName, Description}. Please suggest how to write linq query.
Something like this should do the trick:
var result = productList
.SelectMany(p => p.SubProducts
.Select(sp => new { SubProduct = sp, ProductName = p.ProductName }))
.Select(sp =>
new { Description = subProductTypeList
.Single(spt => spt.SubProduct.SubProductTypeId == sp.SubProductTypeId).Description,
ProductName = sp.ProductName })
In the SelectMany, we first do a Select on the internal IEnumerable (IList implements IEnumerable) to convert each SubProduct object to an anonymous class holding the SubProduct object and the ProductName. The SelectMany then converts that to a flat list. We then use Select on that list to create a new anonymous class again, where this time, we grab the Description from subProductTypeList. The result is an IEnumerable of an anonymous class with the members Description and ProductName.

Categories

Resources