How to add where clause for child items in Castle ActiveRecord - nhibernate

I am using Castle ActiveRecord and created two entities as follow :
[ActiveRecord("Teams")]
public class Team : ActiveRecordLinqBase<Team>
{
public Team()
{
Members = new List<Member>();
}
[PrimaryKey]
public int Id { get; set; }
[Property("TeamName")]
public string Name { get; set; }
[Property]
public string Description { get; set; }
[HasMany(Inverse = true,
Lazy = true,
Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
public virtual IList<Member> Members { get; set; }
}
[ActiveRecord("Members")]
public class Member : ActiveRecordLinqBase<Member>
{
[PrimaryKey]
public int Id { get; set; }
[Property]
public string FirstName { get; set; }
[Property]
public string Lastname { get; set; }
[Property]
public string Address { get; set; }
[BelongsTo("TeamId")]
public Team Team { get; set; }
}
And I used ICriterion to have filtered Team data
IList<ICriterion> where = new List<ICriterion>();
where.Add(Expression.Eq("Name", "name1"));
ICriterion[] criteria = where.ToArray();
var teams = Team.FindAll(criteria);
So far it works well, but I want to add another filter on Members table. The result query would be like this
select *
from Teams t join Member m on t.Id = m.TeamId
where t.Name = 'name1'
and m.Address = 'address'
How to get this done using ICriterion?
I mean how to add criterion for Team.Members property.
Not using LINQ. (I know this could be done using linq easily)

For join you can use
DetachedCriteria
DetachedCriteria criteriaTeam = DetachedCriteria.For<Team>();
DetachedCriteria criteriaMember = criteriaTeam .CreateCriteria("Members");
criteriaTeam .Add(Expression.Eq("Name", "name1"));
criteriaMember.Add(Expression.Eq("Address", "address"));
ICriteria executableCriteria = criteriaTeam .GetExecutableCriteria(session);
executableCriteria.List<Team>();
This will return only Team.To return both Team and Members in a single fetch you can use NHibernate result transformer Projections in NHibernate

Related

Convert CASE WHEN FROM SQL to NHIBERNATE

I have a SQL statement below that needs to convert it to NHibrnate QueryOver. I have searched the web but can't find a concrete solution. Anyone can help me on this?
SELECT
TABEL1.Id,
CASE WHEN EXISTS (SELECT Id FROM TABLE2 WHERE TABLE2.ID = TABLE1.ID)
THEN 'TRUE'
ELSE 'FALSE'
END AS NewFiled
FROM TABLE1
--Here is the real POCO
public class UserRole
{
[Required]
public virtual User User { get; set; }
[Required]
public virtual Role Role { get; set; }
}
public class UserTenant
{
[Required]
public virtual Tenant Tenant { get; set; }
[Required]
public virtual User User { get; set; }
}
public class Role
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string RoleLabel { get; set; }
[StringLength(4000), Required]
public virtual string RoleDescription { get; set; }
}
public class User
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string Firstname { get; set; }
[StringLength(255), Required]
public virtual string Lastname { get; set; }
}
public class Tenant
{
public int Id {get;set}
[StringLength(255), Required]
public virtual string Name { get; set; }
[StringLength(4000), Required]
public virtual string Description { get; set; }
}
public class AssignRoleUsersModel
{
public virtual int UserId { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual bool IsAssigned { get; set; }
}
--This is the method to get the users assigned or not for that particular role.
public RoleUsers GetRoleUsers(int Id)
{
UserRole userRolesAlias = null;
UserTenant userTenantsAlias = null;
-- This query will get the role depending the Id that have passed. Take note that I don't want to use this query that is why I am converting it to queryOver.
var role = (from r in RoleRepository.Queryable()
where r.Id == Id
select r).FirstOrDefault();
--This query will get all the users having the role result above.Take note that I don't want to use this query that is why I am converting it to queryOver.
var assignedUsers = UserRoleRepository.Queryable().Where(x => x.Role.Id == role.Id).Select(a => a.User.Id).ToArray();
--This is the condition to know if the user was assigned to the role
var projection = Projections.Conditional(Restrictions.Where(() => userRolesAlias.User.Id.IsIn(assignedUsers))
, Projections.Constant(true)
, Projections.Constant(false)
);
var users =
new List<AssignRoleUsersModel>(UnitOfWorkLocalData.CurrentUnitOfWork.Session.QueryOver(() => userTenantsAlias)
.Select(x => x.User.Id)
.Select(x => x.User.LastName)
.Select(x => x.User.FirstName)
.Select(x => x.User.UserName)
.Select(projection)
.TransformUsing(Transformers.AliasToBean<AssignRoleUsersModel>())
.List<AssignRoleUsersModel>());
}
EXTENDED based on the extended question, the way how to do CASE WHEN:
// the ID of searched role, coming as parameter id
int searchedRolId = ...
UserRole userRolesAlias = null;
// this is the SUBQUERY we need, the inner select
var subquery = QueryOver
.Of<UserRole>(() => userRolesAlias)
.Where(() => userRolesAlias.Role.Id == searchedRolId)
.Select(x => userRolesAlias.User.Id);
// here we use NHibernate built in Subqueries tools
var projection = Projections.Conditional(
Subqueries.Exists(subquery.DetachedCriteria) // this is the SUB-SELECT
, Projections.Constant(true)
, Projections.Constant(false)
);
ORIGINAL - This could look like this:
var projection = Projections.Conditional(
NHibernate.Criterion.Expression
.Sql("EXISTS (SELECT Id FROM TABLE2 WHERE TABLE2.ID = {alieas}.ID)")
, Projections.Constant(true)
, Projections.Constant(false)
);
var query = session.QueryOver<Table1>();
ResultDto result = null;
var list = query.SelectList(l => l
.Select(x => x.ID).WithAlias(() => result.ID)
.Select(projection).WithAlias(() => result.DoesExist)
)
.TransformUsing(Transformers.AliasToBean<ResultDto>())
.List<ResultDto>();
The list here contains the set of ResultDtos, with ID and decision if the subtabel TABLE2 record exists. The {alias} will be replaced by NHibernate with the alias of the outert table...
Where this is our DTO for projections:
public class ResultDto
{
public virtual int ID { get; set; }
public virtual bool DoesExist { get; set; }
}

Ravendb TransformResults showing null values for properties populated with Load()

I have two documents Ticket and MenuItem i have created index with TransformResults but problem is i am getting null value for Loaded document in transform
public class Ticket
{
public int ID { get; set; }
public int ItemId { get; set; }
public string ItemName { get; set; }
public int Price { get; set; }
}
public class MenuItem
{
public int ID { get; set; }
public string ItemName { get; set; }
public string PriceCategory { get; set; }
}
i have created a index like
public class TicketItemGross : AbstractIndexCreationTask<Ticket, TicketItemGross.TicketItemDetails>
{
public class TicketItemDetails
{
public string ID { get; set; }
public string ItemId { get; set; }
public string ItemName { get; set; }
public int Price { get; set; }
public string PriceCategory { get; set; }
}
public TicketItemGross()
{
Map = docs => from doc in docs
select new
{
ID = doc.ID,
ItemId=doc.ItemId,
ItemName=doc.ItemName,
Price=doc.Price
};
TransformResults = (database, docs) => from m in docs
let d = database.Load<MenuItem>(m.ID)
select new
{
ID = m.ID,
ItemId = m.ItemId,
ItemName = m.ItemName,
Price = m.Price,
PriceCategory=d.PriceCategory
};
}
}
and the problem is that when i query data. I get null for PriceCategory but for all other fields i get correct value
here is query
IEnumerable<TicketItemGross.TicketItemDetails> list;
using (var session = store.OpenSession())
{
list = session.Query<TicketItemGross.TicketItemDetails, TicketItemGross>();
}
This is happening because you are using integer IDs. When you call database.Load in your transform, you'll need to manually convert it to a string ID.
database.Load<MenuItem>("MenuItems/" + m.ID)
This is one of several places where Raven gets confused if you use integer or guid IDs. If you use string ids, you won't have this problem.
Also, you might consider using a results transformer instead. They are easier than index transformers, which are now obsolete.

RavenDB TransformResults

I'm trying to use the TransformResults feature, and I can't get it to work. I'm not totally sure I understand this feature, perhaps there is another way to solve this problem. What I want is just the Id from the Order and the email addesses from the Customer and the Entrepreneur. I am happy for all tips that can take me in the right direction. Here is my code.
Document
public class OrderDocument
public string Id {get; set }
public EntrepreneurInfo EntrepreneurInfo { get; set; }
public CustomerInfo CustomerInfo { get; set; }
public OrderStatus CurrentOrderStatus { get; set; }
}
Info classes
public class EntrepreneurInfo
{
public string EntrepreneurDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
public class CustomerInfo
{
public string CustomerDocumentId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
The info classes are just subsets of a Customer and Entrepreneur documents respectively.
The Customer and Entrepreneur documents inherits from a base class ( AbstractOrganizationDocument) that has the EmailAddress property.
My Index
public class OrdersApprovedBroadcastingData :
AbstractIndexCreationTask<OrderDocument, OrdersApprovedBroadcastingData.ReduceResult>
{
public OrdersApprovedBroadcastingData()
{
this.Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
Id = d.Id,
CustomerId = d.CustomerInfo.CustomerDocumentId,
EntrepreneurId = d.EntrepreneurInfo.EntrepreneurDocumentId
};
this.TransformResults = (db, orders) => from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurId)
select
new
{
o.Id,
o.CustomerId,
CustomerEmail = customer.EmailAddress,
o.EntrepreneurId,
EntrepreneurEmail = entrepreneur.EmailAddress
};
}
public class ReduceResult
{
public string Id { get; set; }
public string CustomerId { get; set; }
public string CustomerEmail { get; set; }
public string EntrepreneurId { get; set; }
public string EntrepreneurEmail { get; set; }
}
}
If I look at the result of this Index in Raven Studio I get null values for all fields except the Id. And finally here is my query.
Query
var items =
this.documentSession.Query<OrdersApprovedBroadcastingData.ReduceResult, OrdersApprovedBroadcastingData>()
.Select(x => new OrdersToBroadcastListItem
{
Id = x.Id,
CustomerEmailAddress = x.CustomerEmail,
EntrepreneurEmailAddress = x.EntrepreneurEmail
}).ToList();
Change your index to:
public class OrdersApprovedBroadcastingData : AbstractIndexCreationTask<OrderDocument>
{
public OrdersApprovedBroadcastingData()
{
Map = docs => from d in docs
where d.CurrentOrderStatus == OrderStatus.Approved
select new
{
};
TransformResults = (db, orders) =>
from o in orders
let customer = db.Load<CustomerDocument>(o.CustomerInfo.CustomerDocumentId)
let entrepreneur = db.Load<EntrepreneurDocument>(o.EntrepreneurInfo.EntrepreneurDocumentId)
select new
{
o.Id,
CustomerEmailAddress = customer.EmailAddress,
EntrepreneurEmailAddress = entrepreneur.EmailAddress
};
}
}
Your result class can simply be the final form of the projection, you don't need the intermediate step:
public class Result
{
public string Id { get; set; }
public string CustomerEmailAddress { get; set; }
public string EntrepreneurEmailAddress { get; set; }
}
You don't have to nest this class in the index if you don't want to. It doesn't matter either way. You can query either with:
var items = session.Query<Result, OrdersApprovedBroadcastingData>();
Or with
var items = session.Query<OrderDocument, OrdersApprovedBroadcastingData>().As<Result>();
Though, with the first way, the convention tends to be to nest the result class, so really it would be
var items = session.Query<OrderDocument.Result, OrdersApprovedBroadcastingData>();
Note in the index map, I am not including any properties at all. None are required for what you asked. However, if you want to add a Where or OrderBy clause to your query, any fields you might want to filter or sort on should be put in there.
One last thing - the convention you're using of OrderDocument, CustomerDocument, EntrepreneurDocument, is a bit strange. The usual convention is just Order, Customer, Entrepreneur. Think of your documents as the persisted form of the entities themselves. The convention you are using will work, it's just not the one usually used.

Criteria API and left join on a dictionary/map

I have the following classes:
public class Item
{
public int Id { get; set; }
public IDictionary<int, ItemLocal> { get; protected set; }
public ICollection<string> Tags { get; set; }
public int DefaultLanguageId { get; set; }
public DateTime Start { get; set; }
}
public class ItemLocal
{
public virtual Item Parent { get; set; }
public virtual int LanguageId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
These map to the tables Item, ItemTag and ItemLocal. I want to make the following query via the criteria api:
select
i.Id,
i.Start,
l.Title
from
Item i
left join ItemLocal l on i.Id = l.ParentId and i.DefaultLangaugeId = l.LanguageId
order by
l.Title,
i.Id
But I dont know how to perform the left join with the nhibernate criteria api. Especially with the usage of the default language selection.
Any help is very appreciated.
I have found a solution:
Session
.CreateCriteria<Item>("i")
.CreateCriteria("Localization", "l", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.Add(Restrictions.Disjunction()
.Add(Restrictions.EqProperty("i.DefaultLanguageId", "l.LanguageId"))
.Add(Restrictions.IsNull("l.LanguageId"))
)
.AddOrder(Order.Asc("l.Title"))
.AddOrder(Order.Asc("w.Id"));
This seems to work but results in a query with a workaround WHERE clause. Would rather see that it could render a SQL query where the restrictions defined in the disjunction could be part of the OUTER LEFT JOIN ... ON ... statement.

Sorting with Mathematical Formula with nHibernate (C#)

public class Feedback
{
public virtual int Id { get; private set; }
public virtual string ContentText { get; set; }
public virtual DateTime FeedbackDate { get; set; }
public virtual Student student { get; set; }
}
My Feedback Class.
public class Student
{
public virtual int Id { get; private set; }
public virtual int NumberOfStars { get; set; }
public virtual IList<Feedback> Feedbacks { get; private set; }
public Student()
{
Feedback = new List<Feedbacks>();
}
}
My Student Class
public class Course
{
public virtual int Id { get; set; }
// bla bla bla
public virtual IList<Student> Students { get; private set; }
public Course()
{
Students = new List<Student>();
}
public IList<Student> SortBy(string type)
{
// some other sorting
else if (type.Equals("popular")){
sortedStudents = session.CreateCriteria(typeof(Student))
.CreateAlias("Student", "s")
.CreateAlias("s.Feedback", "f")
.AddOrder(Order.Desc( -------- ))
.List();
}
return (IList<Student>) sortedStudents;
}
}
My Course class
I want sort students in a Course with method SortBy :
if type is x i will sort with following rule
(Students.Feedback.Count)*5 + Student.NumberOfStars)
How ?
HQL:
List<Student> sortedStudents = session
.CreateQuery(
#"from Students student
where student.Course == :course
order by size(student.Feedbacks) * 3 + student.NumberOfStars")
.SetEntity("course", course)
.List<Student>();
size is a HQL function. See the chapter "Expressions" in the NH documentation.
You may also select it with Criteria and sort it with Linq.
Edit
Just saw that you use it in a property and you may have the students already in memory.
You don't need a query, just to sort it.
return students
.OrderBy(x => x.Feedback.Count * 5 + x.NumberOfStars)
.ToList();
Query with LINQ
IList sortedStudents = (from student in this.Students
where student.Course == this
orderby (student.Feedbacks.Count*3 + student.NumberOfStars)
select student).ToList();