Use the result of SelectSubQuery in a condition with NHibernate QueryOver - c#

I'm using NHibernate with IQueryOver to retrieve a List<Message>. Users can mark a Message as a favourite. Each Message has a property public bool IsFavourite which contains true when at least one User has marked that Message as their favourite.
So in my query I use SelectSubQuery to retrieve the number of times it was marked as favourite. So far so good. I also want to use the result from that subquery in a condition to set IsFavourite.
My query right now looks like this.
Message messageAlias = null;
MessageDTO messageDto = null;
var messages = GetSessionFactory().GetCurrentSession()
.QueryOver<Message>(() => messageAlias)
.SelectList(list => list
.Select(() => messageList.Id).WithAlias(() => messageDto.Id)
.Select(() => messageList.Title).WithAlias(() => messageDto.Title)
.SelectSubQuery(
QueryOver.Of<UserMessageFavourite>()
.Where(f => f.Message.Id == messageAlias.Id).ToRowCountQuery()).WithAlias(() => messageDto.FavouriteCount)
)
)
The property MessageDto,FavouriteCount is merely there so I can set the IsFavourite property. So what I would like to do is to use the SubQuery result in a condition and set the result of that condition to IsFavourite like .SelectSubQuery(subquery.ToRowCountQuery()) > 0).WithAlias(() => messageDto.IsFavourite)

If you already have FavouriteCount in your MessageDTO entity then you can just use readonly C# property like this:
class MessageDTO {
//other properties
public int FavouriteCount { get; set; }
public bool IsFavorite => FavouriteCount > 0;
}
Otherwise you can use custom projection:
Message messageAlias = null;
MessageDTO messageDto = null;
var projection = Projections.Conditional(
Subqueries.Exists(QueryOver.Of<UserMessageFavourite>()
.Where(f => f.Message.Id == messageAlias.Id).DetachedCriteria)),
Projections.Constant(true),
Projections.Constant(false));
var messages = GetSessionFactory().GetCurrentSession()
.QueryOver<Message>(() => messageAlias)
.SelectList(list => list
.Select(() => messageList.Id).WithAlias(() => messageDto.Id)
.Select(() => messageList.Title).WithAlias(() => messageDto.Title)
.Select(projection).WithAlias(() => messageDto.IsFavorite)
)

Related

Cannot access variable on mapping (C#)

I have a method on the back end, that gets values related to a foreign key of the table.
Those foreign keys can be nullable, but one of those keys always will have value.
Here is method
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
if (landlordId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
At this row, I get an error Cannot resolve symbol query
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
How do I need to rewrite my method?
Declare and initialise your variable. Additionally I would re-write you method like so:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var query = _quoteRepository.GetAll();
if (landlordId.HasValue)
{
query = query.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = query.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = query .Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Also taken from this answer, you can create a WhereIf extension to clean up the if statements.
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
Making your code look like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var list = await _quoteRepository.GetAll()
.WhereIf(landlordId.HasValue, x => x.LandlordId == landlordId)
.WhereIf(agentId.HasValue, x => x.AgentId == agentId)
.WhereIf(propertyTenantId.HasValue, x => x.PropertyTenantId == propertyTenantId)
.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id)
.ToListAsync();
return new ListResultDto<QuoteListDto>(list);
}
Your problem is variable scope. When you define a variable it is only visible in the scope you define it in.
You define three different query variables in a local scope. None of them are accessible where you try to use it.
You need to define it before using it, something like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
IQueryable<Quote> query = null;
if (landlordId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Of course all of your queries should be of the same type. Otherwise you will have to define and execute them in the local scopes.
You should probably also add some error handling of the case where query is null, when you try to use it.

SQL Virtual Column in Linq

Is there a way to create the equivalent of a SQL virtual/computed column in a linq query within C# dotnetcore MVC page using Razor Pages and Entity Framework...
SELECT 0 as 'sortField'
FROM
database
WHERE
foo = bar
...but using method syntax, formed like so?:
Foo = await
(
_context.Foo
.Where(r => !StatusExceptionList.Contains(r.Status))
.Where(r => (Convert.ToDateTime(r.Statusdate) - today).TotalDays < 31)
.Where(r => r.DSRPID == PID)
.OrderBy(r => r.Submitteddate)
.ThenBy(r => r.Statusdate)
.ThenBy(r => r.recordnum)
)
.Union
(
_context.Foo
.Where(r => !DraftStatusExceptionList.Contains(r.Status))
.Where(r => r.DSRPID == PID)
.Where(r => r.csstatus != "not needed" || !String.IsNullOrEmpty(r.csstatus))
.Where(r => !_context.Foo
.Where(rr => rr.DSRPID == PID)
.Select(rr => rr.Fooid)
.Contains(r.Fooid)
)
.OrderBy(r => r.Submitteddate)
.ThenBy(r => r.Statusdate)
.ThenBy(r => r.recordnum)
)
.ToListAsync();
I believe you can do it like this:
class TableResults
{
public int sortField
}
from t in context.Table select new TableResults { sortField = 0 }
So you would have to have a class with a property called sortField.
you can use anonymous type like below in linq
from result in database
where foo = bar
select new { sortField=0 }
equivalent of a SQL virtual/computed column will be readonly property.
You can do it in your entity class if instance will contain all required values for calculating it's value
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public int SortValue => Id == 42 ? 0 : Id;
}
_context.Foo.Where(f => somecondition)
.OrderBy(f => f.SortValue)
.ToList();
In case instance doesn't have all required values for calculation you can use anonymous type
_context.Foo.Where(f => somecondition)
.Select(f => new
{
Id = f.Id,
Name = f.Name,
SortValue = f.Name == "External name" ? 0 : 1
})
.OrderBy(f => f.SortValue)
.ToList();

Retrieving entities with related tables c# using REST API2

I am building a web API that is suppose to populate data from a linked child table using a where clause.
I have attempted using include() with where() as per eager loading but without success.
public IQueryable<Market> getAllActive()
{
return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres.Where(e => e.IsActive == true));
}
On researching, there are recommendations that I use explicit loading but it keeps error about the need to cast the data type. I am lost of ideas at the moment and will appreciate any help. Here is my code:
private TravelCentresDbContext db = new TravelCentresDbContext();
public IQueryable<Market> getAllActive()
{
//return db.Markets.Where(c => c.IsActive == true).Include(d => d.TravelCentres);
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<Market>)result;
}
I get this exception message Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType42[System.String,System.Collections.Generic.IEnumerable1[TravelCentres.Models.TravelCentre]]]'
to type 'System.Linq.IQueryable1[TravelCentres.Models.Market]'.
Blockquote
result is not an IQuerytable<Market>, it's an IQueryable of an anonymous type with properties Market and TravelCenters. So (IQueryable<Market>)result is an invalid cast. It would be advisable to create a model with Market and TravelCenters properties and then return that.
public class MyModel
{
public int MarketId { get; set; }
public IEnumerable<TravelCentre> TravelCentres { get; set; }
}
.
var result = db.Markets
.Where(c => c.IsActive == true)
.Select(p => new MyModel()
{
Market = p.MarketId,
TravelCentres = p.TravelCentres.Where(x => x.IsActive == true)
});
return (IQueryable<MyModel>)result;

Error using Serge Zab's helper for optgroup dropdowns

I'm trying to work with the optgroup dropdown helper from Serge Zab that can be found here.
This is my category table:
As you can see I have a category and a category can also be categoryparent from a category. I want to have the parentcategorys as optgroup and the children as options of the optgroup. (Only the children can be selected)
In my ViewModel:
public short? CategoryId { get; set; }
public IEnumerable<ReUzze.Helpers.GroupedSelectListItem> GroupedTypeOptions { get; set; }
In my Controller:
[Authorize] // USER NEEDS TO BE AUTHORIZED
public ActionResult Create()
{
ViewBag.DropDownList = ReUzze.Helpers.EnumHelper.SelectListFor<Condition>();
var model = new ReUzze.Models.EntityViewModel();
PutTypeDropDownInto(model);
return View(model);
}
[NonAction]
private void PutTypeDropDownInto(ReUzze.Models.EntityViewModel model)
{
model.GroupedTypeOptions = this.UnitOfWork.CategoryRepository.Get()
.OrderBy(t => t.ParentCategory.Name).ThenBy(t => t.Name)
.Select(t => new GroupedSelectListItem
{
GroupKey = t.ParentId.ToString(),
GroupName = t.ParentCategory.Name,
Text = t.Name,
Value = t.Id.ToString()
}
);
}
In my View:
#Html.DropDownGroupListFor(m => m.CategoryId, Model.GroupedTypeOptions, "[Select a type]")
When I try to run this I always get the error:
Object reference not set to an instance of an object.
I get this error on this rule : .OrderBy(t => t.ParentCategory.Name).ThenBy(t => t.Name)
Can anybody help me find a solution for this problem?
Your error message suggests that either a t is null or a t.ParentCategory is null.
You can fix the error by simply checking for nulls, but this may or may not give you the desired output, depending on whether you also want to include categories that don't have a parent.
model.GroupedTypeOptions = this.UnitOfWork.CategoryRepository.Get()
.Where(t => t.ParentCategory != null)
.OrderBy(t => t.ParentCategory.Name).ThenBy(t => t.Name)
.Select(t => new GroupedSelectListItem
{
GroupKey = t.ParentId.ToString(),
GroupName = t.ParentCategory.Name,
Text = t.Name,
Value = t.Id.ToString()
});
I'm assuming your CategoryRepository can't return a null t, but if it can, you'd adapt the where to be:
.Where(t => t != null && t.ParentCategory != null)
The problem is that not all of your entities returned will have a ParentCategory.
It seems you should only be selecting the children and not the parents.
Try this:
model.GroupedTypeOptions = this.UnitOfWork.CategoryRepository.Get()
.Where(t => t.ParentCategory != null)
.OrderBy(t => t.ParentCategory.Name).ThenBy(t => t.Name)
.Select(t => new GroupedSelectListItem
{
GroupKey = t.ParentId.ToString(),
GroupName = t.ParentCategory.Name,
Text = t.Name,
Value = t.Id.ToString()
}
);

Linq OrderedQueryAble Error

First Thing's First I have a class to manipulate some data through a linq variable:
public class Result
{
public bool LongerThan10Seconds { get; set; }
public int Id { get; set; }
public DateTime CompletionTime { get; set; }
}
Then in a Separate class I'm using this to grab info from a linq var
using (var data = new ProjectEntities())
{
Result lastResult = null;
List<Result> dataResults = new List<Result>();
foreach(var subResult in data.Status.Select(x => x.ID).Distinct().Select(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time)))
{
if (lastResult != null)
{
if (subResult.CompletionTime.Subtract(lastResult.CompletionTime).Seconds > 10)
dataResults.Add(subResult);
}
lastResult = subResult;
}
however I get the error:
Linq.IOrderedQueryAble does not contain a definition for 'CompletionTime' and no Extension method 'CompletionTime' accepting a first argument of type 'System.Linq.IOrderedQueryable.
Is anyone able to provide a solution to get around this would be much appreciated been trying to figure it out for a while but seems a bit difficult in terms of a DateTime Variable.
Your problem is that subResult holds an IOrderedQueryable (presumably an IOrderedQueryable<Result>) rather than a Result.
You have this in the foreach: var subResult in data.Status.Select(x => x.ID).Distinct().Select(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time)). Notice what's inside the Select: Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time). That will return an IOrderedQueryable<T>, not a T (where T is whatever type is in the data.Status collection).
You need to either get a single value out of that IOrderedQueryable, using First() or some similar method, like this:
var subResult in data.Status.Select(x => x.ID).Distinct().Select(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time).First())
... or flatten your IEnumerable<IQueryable<T>> to an IEnumerable<T>:
var subResult in data.Status.Select(x => x.ID).Distinct().SelectMany(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time))
Edit: You may also be having an issue where C# is uncertain what type the var subResult is. If they're all Result type objects, try replacing var subResult with Result subResult.
Looks like you have to use SelectMany instead your second Select method call
data.Status.Select(x => x.ID).Distinct()
.SelectMany(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time))

Categories

Resources