Linq select group by where syntax - c#

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();

Related

Filter DbSet by list of objects

I am using Entity Framework Core with SQL Server to filter a list of division entities by a list of passed in search objects. I want to return division that match the criteria in any one of the search objects. The search object class looks like:
public class SearchObject
{
public int DivisionId { get; set; }
public int Status { get; set; }
}
Here is the query I tried:
var searchObjects = new List<SearchObject> { ... };
IQueryable<Product> query = myContext.Divisions.Where(div =>
searchObjects.Any(searchObj =>
searchObj.Status == div.Status &&
searchObj.DivisionId == div.DivisionId))
.Select(...);
When the IQueryable enumerates, I get an error stating: "The Linq Expresion DbSet ... Could not be translated ..." What I need is something like .Contains(), but that works with a list of SearchObj.
Well you have the right idea by
What I need is something like .Contains(), but that works with a list of SearchObj.
and this is how you'd do it
var searchObjects = new List<SearchObject> { ... };
var searchIds = searchObjects.Select(x => x.Divisiond) //.ToList() perhaps?
var searchStatus = searchObjects.Select(x => x.Status) //.ToList() perhaps?
//not you can simply use .Contains and it should generate a WHERE EXISTS query
IQueryable<Product> query = myContext.Divisions
.Where(div =>
searchIds.Contains(div.DivisionId) &&
searchStatus.Contains(div.Status))
.Select(...);

Updating many-to-many links with direct relationships

I am using Entity Framework Core 5 in a .NET 5.0 web app. I have two classes that are joined in a many-to-many relationship. I am using 'direct' approach, as described here. This mean that I do not have joining tables explicitly defined in code; instead, EF has inferred the relationship from the following schema:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public ICollection<Group> Groups { get; set; }
}
public class Group
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public ICollection<User> Users { get; set; }
}
I wish to be able to update a 'Group' or 'User' by simply providing EF with a new object. For example, if I provide EF with a 'Group' object that has the same ID as a Group already in the database, but it now has an extra 'User' in the Users collection, then I would expect EF to update the 'UserGroup' table that it has made behind the scenes with a new record to represent this relationship.
Here is what I have tried:
1.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Saves changes to any props belonging to the Group object, but does not affect any relationships.
2.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
oldGroup.Users = group.Users;
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Exception.
System.InvalidOperationException: 'The instance of entity type 'User' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
I added some extension methods for Intersect and LeftComplementRight from here and tried calculating the diffs myself.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
var oldUsers = oldGroup.Users;
var newUsers = group.Users;
var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);
foreach (var u in toBeAdded)
{
oldGroup.Users.Add(u);
}
foreach (var u in toBeRemoved)
{
oldGroup.Users.Remove(u);
}
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
Result: Same exception as above.
It was at this point I realised that the 'User' objects that compose the 'Users' collection had the 'Groups' object instantiated and populated with self-references back to the Group object. I realised that this was probably confusing EF, so I tried this:
4.
public void Update(Group group)
{
Group oldGroup = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == group.ID);
var oldUsers = oldGroup.Users;
var newUsers = group.Users;
var toBeRemoved = oldUsers.LeftComplementRight(newUsers, x => x.ID);
var toBeAdded = newUsers.LeftComplementRight(oldUsers, x => x.ID);
var toBeUpdated = oldUsers.Intersect(newUsers, x => x.ID);
foreach (var u in toBeAdded)
{
u.Groups = null;
oldGroup.Users.Add(u);
}
foreach (var u in toBeRemoved)
{
u.Groups = null;
oldGroup.Users.Remove(u);
}
_context.Entry(oldGroup).CurrentValues.SetValues(group);
_context.SaveChanges();
}
This works, however it seems like I'm doing far too much work. I anticipate having multiple many-to-many relationships throughout this project, and I don't want to have to duplicate this code on every update method. I suppose I could create an Extension method, but I feel like EF should be able to handle this common usecase.
Am I missing something?
You can try this. Basically just replaces the entities (the ones with matching IDs) with the currently tracked instances. The new entities in the list are automatically persisted when you save. And the entities which were present in the original list but aren't present in the new list, will be automatically removed.
public void Update(Group updatedGroup)
{
Group group = _context.Groups
.Include(g => g.Users)
.First(g => g.ID == updatedGroup.ID);
group.Users = updatedGroup.Users
.Select(u => group.Users.FirstOrDefault(ou => ou.ID == u.ID) ?? u)
.ToList();
// Do other changes on group as needed.
_context.SaveChanges();
}
Yeah, it's kind of ridiculous that there is no simple method to replace a list of entities in this way. Although it gets less ridiculous if you consider that normally we don't even get entity instances in our update-like methods, because usually we get the changes through DTOs, so we couldn't just replace a list to begin with.
Let me know if it doesn't work (it's late here). :)

NHibernate Like restriction on nested string properties

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();

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

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)});

Categories

Resources