NHibernate Like restriction on nested string properties - c#

For simplicity, let's guess there are two entities:
public class Entity
{
public string Value { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public string Value { get; set; }
}
I need to find all entities where either Value or Child.Value are insensitive like specified string query.
That's what I have by now:
Entity entity = null;
ChildEntity child = null;
var nhibernateQuery = session
.QueryOver(() => entity)
.JoinAlias(() => entity.Child, () => child);
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(
Restrictions.Or(
Restrictions.On(() => entity).IsInsensitiveLike(query),
Restrictions.On(() => child).IsInsensitiveLike(query)
)
);
}
return nhibernateQuery.List().ToArray();
I get the NullReferenceException - it seems like Restrictions.On does not handle alias correctly.
Another approach that I have tried is .JoinQueryOver() which is suggested by this post:
return session
.QueryOver<Entity>()
.Where(Restrictions.InsensitiveLike("Value", query))
.JoinQueryOver(e => e.Child)
.Where(Restrictions.InsensitiveLike("Value", query));
This thing works except for one thing: it returns all items where both Value and Child.Value are like query. I need the same thing, but with or logic.
What should be done to make it work? I would like to use .QueryOver(), either with or without aliases, but without .CreateCriteria(), but will appreciate if you help me with any working solution.

The problem has been solved by using NHibernate LINQ .Query<>().
It can resolve all joins and relations by itself.
At the same time, .Contains() method is translated into case-insensitive LIKE statement for MS SQL which suits me needs.
var nhibernateQuery = session
.Query<Entity>();
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(e => e.Value.Contains(query) || e.Child.Value.Contains(query));
}
return nhibernateQuery.ToArray();

Related

Linq select group by where syntax

I have these classes:
User {
//identity user class
public IList<Proyecto> Proyectos { get; set; }
public bool IsImportant { get; set }
}
Proyecto
{
public Guid Id { get; set; }
// more properties here
}
What I am trying to do is to group by userId all the projects they have created and where IsImportant is true.
I have tried this
var usuariosRobotVmInDb =await _dbContext.Users.Include(p=>p.Proyectos)
.groupby(u=>u.id)
.Where(u => u.IsImportant== True)
.Select(x=> new usuariosRobotViewModel
{
Id=x.Id,
Name=x.Name,
LastName=x.LastName,
Phone=x.Phone,
Email=x.Email,
proyectosVms=x.Proyectos.Select(a=>new ProyectosVm {Id=a.Id,Date=a.Date})
}).ToListAsync();
But it seems I can not use a groupBy and a where clause...How can I fix this? thanks
As you use navigation properties and the projects are already properties of your User class then you do not need to group by anything. Just retrieve the users where Important is true:
var result = await _dbContext.Users.Include(p => p.Proyectos)
.Where(u => u.IsImportant);
(And of course you can add the projection to the usuariosRobotViewModel object)
The problem with LINQ is that developers are still thinking in the SQL way and trying to convert it to LINQ Which sometimes doesn't work and sometimes is not optimal. In your case, you're using a navigation property when you're including the Proyectos object, and LINQ already knows the relationship between the User and Proyectos objects, so you don't need to group or do anything else:
var usuariosRobotVmInDb = await _dbContext.Users.Include(p => p.Proyectos)
.Where(u => u.IsImportant) //Same as u.IsImportant == true
.Select(x => new usuariosRobotViewModel {
Id = x.Key,
Nombre = x.Nombre,
...
}).ToListAsync();

NHibernate select a list of objects with related child objects with QueryOver

I'm having trouble with something that is probably very simple.
In my database I have the following tables:
tblOrder
-----------------
Id
OrderStatusId
tblOrderStatus
-----------------
Id
Name
And I have made the following mappings in my project:
[Class(NameType = typeof(Order), Table = "tblOrder")
public class Order {
[Id(-2, Name = "Id")]
[Generator(-1, Class = "native")]
public virtual long Id { get; set; }
[ManyToOne]
public virtual OrderStatus Status { get; set; }
}
[Class(NameType = typeof(OrderStatus), Table = "tblOrderStatus")]
public class OrderStatus {
[Id(-2, Name = "Id")]
[Generator(-1, Class = "native")]
public virtual long Id { get; set; }
[Property]
public virtual string Name { get; set; }
}
The query should return a IList<OrderSummary>. I want the class OrderSummary to have a property Status where Status is an object with an Id and a Name property. This could be either with a KeyValuePair or of type OrderStatus (whichever is best and works). Fetching the orders is not a problem but adding the OrderStatus as an object with said properties is the part I'm having trouble with.
I also need to return the result of the query as JSON to the client.
OrderSummary should look like this:
public class OrderSummary {
public long Id { get; set; }
public OrderStatus Status { get; set; }
}
In my first version OrderSummary had separate properties for OrderStatusId and OrderStatusName. This works but I'm trying to avoid these separate properties.
I have also tried this with SelectSubQuery but this returns an error because it returns more than one field in a subquery.
----------------------------------- UPDATE -----------------------------
Following Fredy Treboux's advice I changed my query using Eager which result in the following query:
var query = session.QueryOver<OrderStatus>
.Fetch(o => o.Status).Eager
.JoinAlias(o => o.Status, () => statusAlias, JoinType.LeftOuterJoin);
The problem is, I found out, is not selecting the data but how to convert the retrieved Status and assign it to OrderSummary.Status? I have tried the following:
OrderSummary orderAlias = null;
query.SelectList(list => list
.Select(o => o.Id).WithAlias(() => orderAlias.Id)
.Select(() => statusAlias).WithAlias(() => orderAlias.Status)
).TransformUsing(Transformer.AliasToBean<OrderSummary>());
-------------------------------- ANSWER ----------------------------------
As I said in my last edit, the problem does not seem to be the actual selection of OrderStatus but returning it to the client. So I thought it was my lack of knowledge of NHibernate instead it was as simple as adding the [JsonObject] attribute to the OrderStatus class. How silly of me.
I have changed my query to the following:
Order orderAlias = null;
OrderSummary orderSummary = null;
OrderStatus statusAlias = null;
var query = session.QueryOver<Order>(() => orderAlias)
.JoinAlias(() => orderAlias.Status, () => statusAlias, JoinType.LeftOuterJoin);
query = query
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => orderAlias.Id).WithAlias(() => orderSummary.Id))
.Add(Projections.Property(() => orderAlias.Status).WithAlias(() => orderSummary.Status)
);
Result = query.TransformUsing(Tranformers.AliasToBean<OrderSummary>())
.List<OrderSummary>()
.ToList();
I'm afraid that currently it's not possible. I guess that Nhibernate transformers are not able to construct nested complex properties.
You can return list of tuples and then cast it manually to your entity:
OrderStatus statusAlias = null;
var tuples = Session.QueryOver<Order>()
.JoinQueryOver(x => x.Status, () => statusAlias)
.SelectList(list => list
.Select(x => x.Id)
.Select(x => statusAlias.Id)
.Select(x => statusAlias.Name))
.List<object[]>();
var result = tuples.Select(Convert);
private OrderSummary Convert(object[] item) {
return new OrderSummary
{
Id = (long)item[0],
OrderStatus = new OrderStatus { Id = (long)item[1], Name = (string)item[2] }
};
}
Also if you don't bother about performance much it's possible to fetch a list of you Orders and convert it to OrderSummary. You can do it by simply define casting operator or using some tool like AutoMapper or ExpressMapper.
Sorry I didn't see your comment asking for an example before.
I'm going to leave some code explaining the approach I mentioned, although it was already given as an alternative in the other response and I believe it's the easiest way to go (not using transformers at all):
string GetOrderSummaries()
{
// First, you just query the orders and eager fetch the status.
// The eager fetch is just to avoid a Select N+1 when traversing the returned list.
// With that, we make sure we will execute only one query (it will be a join).
var query = session.QueryOver<Order>()
.Fetch(o => o.Status).Eager;
// This executes your query and creates a list of orders.
var orders = query.List();
// We map these orders to DTOs, here I'm doing it manually.
// Ideally, have one DTO for Order (OrderSummary) and one for OrderStatus (OrderSummaryStatus).
// As mentioned by the other commenter, you can use (for example) AutoMapper to take care of this for you:
var orderSummaries = orders.Select(order => new OrderSummary
{
Id = order.Id,
Status = new OrderSummaryStatus
{
Id = order.Status.Id,
Name = order.Status.Name
}
}).ToList();
// Yes, it is true that this implied that we not only materialized the entities, but then went over the list a second time.
// In most cases I bet this performance implication is negligible (I imagine serializing to Json will possibly be slower than that).
// And code is more terse and possibly more resilient.
// We serialize the DTOs to Json with, for example, Json.NET
var orderSummariesJson = JsonConvert.SerializeObject(orderSummaries);
return orderSummariesJson;
}
Useful links:
AutoMapper: http://automapper.org/
Json.NET: http://www.newtonsoft.com/json

Entity Framework ObjectQuery.Include()

I have an object with two objects as properties (User, PrimaryNode), both could potentially be null, see below:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public Node PrimaryNode { get; set; }
public User User { get; set; }
}
I'm using Entity Framework 6 to populate the Item object and using chained includes to populate the PrimaryNode and User objects within it.
When the first chained Include has a null object then the whole object returns as null, for example:
using (var db = new MyContext())
{
var item = db.Items.Include(i => i.User).Include(n => n.PrimaryNode).FirstOrDefault(i => i.ItemId == id);
}
If in the above example i.User is null then the item variable is null. Whats the best way of populating both the sub-objects in a way that if a sub-object is null then the parent object and the other sub-object will still be populated?
I don't think your issue is due to the Include calls. According with the documentation:
This extension method calls the Include(String) method of the
IQueryable source object, if such a method exists. If the source
IQueryable does not have a matching method, then this method does
nothing.
In other words is going to be translated to:
var item = db.Items.Include("User").Include("PrimaryNode").FirstOrDefault(i => i.ItemId == id);
My question is, are you sure you have an Item with that id properly related with existing rows in Users and PrimaryNodes tables in your DB?. When you call Include method at the end is going to be translated to a join, so if the FK of your relationship doesn't match with the PK that reference, your query should not return what you are expecting.
Anyways, if you want to try another variant to load related properties you can use Explicit Loading:
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
context.Entry(item).Reference(p => p.PrimaryNode).Load();
context.Entry(item).Reference(p => p.User).Load();
I think it would be better if you use Lazy loading int his situation. Just make the User and PrimaryNode virtual:
public class Item
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
public virtual Node PrimaryNode { get; set; }
public virtual User User { get; set; }
}
And then:
var db = new MyContext();
var item = db.Items.FirstOrDefault(i => i.ItemId == id);
As others have mentioned, I think your issue is not due to the Includes. However, I think the following method has value. It is functionally equivalent to what you are already doing with the chained includes, but I think it has several benefits including making the intention of the code clear to the user.
The includes can be placed in Extension methods:
using System.Data.Entity;
using System.Linq;
namespace Stackoverflow
{
public static class EntityExtensions
{
public static IQueryable<Item> IncludePrimaryNode(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.PrimaryNode);
}
public static IQueryable<Item> IncludeUser(this IQueryable<Item> query)
{
// eager loading if this extension method is used
return query.Include(item => item.User);
}
}
}
Then, you can use the extensions as follows:
using (var db = new MyContext())
{
var itemQuery = db.Items.IncludeUser();
itemQuery = itemQuery.IncludePrimaryNode();
var item = itemQuery.FirstOrDefault(i => i.Id == 1);
}
It's just another way of doing the same thing, but I like the clarity it adds to the code.

Related Tables LINQ statement / EF 6 - One to Many, Most recent record

I would like to return some data from 2 related tables. I have a one to many relationship. One WebLead can have many Pricing records. I would like to return the data for the WebLead and the data for most recent record inserted into the Pricing table.
I am new to LINQ and EF. Here is what I have so far but this is only returning the WebLeads table data...What am I missing? Do I need to add a FirstOrDefault for the Pricing table?
var priceRefi = db.WebLeads.Include(p => p.Pricings)
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.LeadStatus.Equals("Priced");
then to populate the view model:
PricedRefiLeads = priceRefi.ToList(),
UPDATE: I am sorry I left so much out. I updated my query to the following (LoanAgent is just a string parameter)
var priceRefi = from lead in db.WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new LeadWithLastPricing()
{
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
I then want to take the results of that query and return it as a list to my view model:
var viewModel = new PipelineViewModel
{
////
PricedRefiLeads = priceRefi.ToList(),
}
I am seeing the following error on the priceRefi.ToList():
Cannot implicitly convert type
'System.Collections.Generic.List(LoanModule.ViewModels.LeadWithLastPricing)'
to 'System.Collections.Generic.List(LoanModule.Models.WebLead)
I am new to MVC. As I read this error, I understand that I must be missing something in my PipelineViewModel but I am not sure what that is.
In PipelineViewModel, I do have:
public List<WebLead> PricedRefiLeads { get; set; }
What am I missing? Forgive me if I left information out, I am struggling to wrap my head around this.
I am using a number of assumptions, for information not specifically mentioned in your question:
LoanAgent is a (local) string variable representing the agent you want to filter on.
Pricing has a field named PricingDate that is of type DateTime.
Then you can do it like this:
// I am assuming a Pricing has a DateTime field named "PricingDate"
var priceRefi =
from lead in WebLeads
where lead.LoanAgent == LoanAgent && lead.LeadStatus == "Priced"
select new {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
Note that this returns an anonymous object as the projection result. If you want to pass this result on, you should create a class:
public class LeadWithLastPricing
{
public Lead Lead { get; set; }
public Pricing LastPricing { get; set; }
}
And do the select part like this:
// ...
select new LeadWithLastPricing() {
Lead = lead,
LastPricing = lead.Pricings.OrderByDescending(x => x.PricingDate).FirstOrDefault()
};
For your second error, change this:
public List<WebLead> PricedRefiLeads { get; set; }
To
public List<LeadWithLastPricing> PricedRefiLeads { get; set; }
And use it like:
var viewModel = new PipelineViewModel
{
PricedRefiLeads = priceRefi.ToList(),
}
You can try:
var query =
from l in db.WebLeads
let maxDate = l.Pricings.Max(p => p.InsertDate)
where l.LoanAgentID == someLoanAgentID
&& l.LeadStatus == "Priced"
select new { Lead = l, Pricing = l.Pricings.FirstOrDefault(x => x.InsertDate == maxDate) };
This will give objects with two properties: Lead and Pricing which are WebLead and Pricings objects. Please forgive syntax errors this is streight out of notepad.
Edit: I suppose I should tell you how to use the result object:
foreach (var MyLead in query)
{
string customerName = MyLead.Lead.CustomerName; // i.e. some property of WebLead
DateTime InsertDate = MyLead.Pricing.InsertDate; // i.e. some property of Pricings
}
You can project directly into a WebLead instead of creating a new class, by doing the following:
var priceRefi = db.WebLeads
.Include(p => p.Pricings)
.Where(l => l.LoanAgent == LoanAgent)
.Where(l => l.LeadStatus == "Priced")
.Select(lead=>new WebLead {
lead.LoanAgent,
lead.LeadStatus,
...,
Pricings=lead.Pricings.OrderByDescending(x=>x.‌​PricingDate).Take(1)});

Linq Select items that are mapped as many-to-many?

I am trying to build a select query to a table while only contains concatenated Primary Key values and no attributes. I managed to get the select query alright, but I can't quite figure out how to get the select statement. Basically I am trying to get a list of DegreeID's, from the DegreeRelationship table, (which is mapped and not an entity) from a ProgramID in DiplomaCertificate entity. Then I want to get the Degree name as well.
My context with the mapped tables looks like this:
modelBuilder.Entity<Degree>()
.HasMany(e => e.DiplomaCertificates)
.WithMany(e => e.Degrees)
.Map(m => m.ToTable("DegreeRelationship").MapLeftKey("DegreeID").MapRightKey("ProgramID"));
and basically, I am trying to put values into this object:
public class DegreeRelationshipInfo
{
public int ProgramID { get; set; }
public int DegreeID { get; set; }
public string LinkedProgramName { get; set; }
}
I am trying a method something like this, but I am not sure how to write this exactly (and this is completely wrong):
[DataObjectMethod(DataObjectMethodType.Select, false)]
public List<DegreeRelationshipInfo> Select_DegRel(int programID)
{
using (Pathway_Model context = new Pathway_Model())
{
var results = from data in context.Degrees
where data.DiplomaCertificates.Where(x => x.ProgramID == programID)
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
// no idea how to get this value....
};
return results.ToList();
}
}
Any help would be appreciated!
Select the entities by SelectMany and collect their key values:
from data in context.Degrees
from cert in data.DiplomaCertificates
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
DegreeID = cert.DegreeID,
LinkedProgramName = data.Name // I guess...
}
This from - from construction is compiled into SelectMany, but the syntax is much better readable.

Categories

Resources