Latest modified row for each item - sql

I have a sql table containing multiple rows with a memberid and lastmodified date. I need to get latest modified row for each member id. This is what I have tried in EFCore 3.1.1:
var a = context.Members
.Include(m => m.Histories.OrderByDescending(h => h.LastModifiedDate)
.FirstOrDefault());
and it gives error: Lambda expression used inside Include is not valid
What am I missing?
UPDATE:
I tried this as well that didn't work either:
var a = context.Histories
.GroupBy(h => h.MemberId)
.Select(g => g.OrderByDescending(p => p.LastModifiedDate).FirstOrDefault()).ToList();
The LINQ expression '(GroupByShaperExpression:
KeySelector: (o.MemberId),
ElementSelector:(EntityShaperExpression:
EntityType: History
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
)
)
.OrderByDescending(p => p.LastModifiedDate)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

While newer versions of EF Core do support some filtering via adding Where clauses, it's best to think of entities as a "complete" or "complete-able" representation of your data state. You either use the complete state of data, or you project to get the details you want.
For example, if you just want the last modified date for each member:
var lastModifiedDetails = context.Members
.Select(m => new
{
m.MemberId,
LastModifiedDate = m.Histories.OrderByDescending(h => h.LastModifiedDate)
.Select(h => h.LastModifiedDate)
.FirstOrDefault()
}).ToList();
This would give you a collection containing the MemberId and it's LastModifiedDate.
Alternatively, if you want the complete member and a quick reference to the last modified date:
var memberDetails = context.Members
.Select(m => new
{
Member = m,
LastModifiedHistory = m.Histories.OrderByDescending(h => h.LastModifiedDate)
.FirstOrDefault()
}).ToList();
Here instead of trying to get the last modified date or the latest history through a Member entity, you get a set of anonymous types that project down that detail. You iterate through that collection and can get the applicable history and/or modified date for the associated Member.

Related

Does RavenDb's `Include` support constructing a document id, not just selecting one?

This code here is not working as expected. Specifically, it seems the calls to Include are not causing any extra documents to be added to the session. Thus, the Loads are each costing a full db hit. This is RavenDb 3.5.
I couldn't find examples that actually use Include this way. They all seem to emit a "selector" that is used to find a member on the document, and that member holds the literal document id to include. Whereas I am constructing the document id and returning it as a string.
session.Query<Coil>()
.Include(c => nameof(CoilState) + "/" + id)
.Include(c => nameof(CoilExt) + "/" + id)
.Include(c => nameof(Material) + "/" + c.MaterialCode)
.Where(c => c.CoilId == id)
.ToList()
.Select(c =>
Coil_Dto.ToCoilDto(
c
, session.Load<CoilState>(nameof(CoilState) + "/" + c.CoilId)
, session.Load<CoilExt>(nameof(CoilExt) + "/" + c.CoilId)
, session.Load<Material>(nameof(Material) + "/" + c.MaterialCode)
)
)
.SingleOrDefault()
My question is: can Include be used in this way? If not, is there some way to achieve that "include" functionality using my constructed ids?
It sounds like you want to use Lazy, rather than Include.
Include
The .Include method should be passed the name of a property on your object(s) which contains an ID.
// Good:
// Note that StateId, ExtId, and MaterialId are all properties on our Coil class.
session.Query<Coil>()
.Include(c => c.StateId)
.Include(c => c.ExtId)
.Inclide(c => c.MaterialId);
// Bad:
session.Query<Coil>
.Include(c => "CoilStates/123")
.Include(c => "CoilExts/456")
.Include(c => "Materials/789")
In short, make sure your .Include calls are passed the name of properties on the Coil class. Those properties should contain string values which are IDs of other documents. Then they'll all be loaded in a single trip to the database.
Lazy
If Include doesn't make sense for your scenario, but you still want to load disparate objects in a single DB call, use the lazy API
// Lazily load the coil. No DB trip yet.
var lazyCoil = session.Query<Coil>()
.Where(...)
.Lazily();
// Lazily load a CoilState. No DB trip yet.
var lazyCoilState = session.Advanced.Lazily.Load<CoilState>("CoilStates/123");
// Lazily load a Material. Still no DB trip.
var lazyMaterial = session.Advanced.Lazily.Load<Material>("Materials/456");
// Grab one of the values. This will fetch all lazy loaded items in 1 trip.
var coil = lazyCoil.Value;
// Grab the other values. No DB trip needed; they're already loaded!
var coilState = lazyCoilState.Value;
var material = lazyMaterial.Value;

QueryOver OrderBy child property using strings

I'm struggling with using QueryOver.OrderBy with strings for property names on child entities. e.g. the following works but I am hardcoding the OrderBy field.
Customer custAlias = null;
session.QueryOver<Campaign>()
.JoinAlias(x => x.Customer, () => custAlias)
.OrderBy(() => custAlias.Name).Desc() // want to use string property name
.List();
I can specify the OrderBy using a string with something like:
.OrderBy(Projections.Property("DOB")).Desc();
But this is looking for "DOB" on the Campaign entity, not the child Customer entity. Is it possible to retrieve the alias used by NH and then set the path to the property e.g.
.OrderBy(Projections.Property("cust.DOB")).Desc(); // where "cust" is the alias
Any ideas?
The alias used is the name of the variable. So
Projections.Property("custAlias.DOB")
(can't test now, but if I remember corretly it works)
Interestingly, it isn't the variable, in itself, that is used as the alias, but its name. What does it means?
QueryOver<Campaign> query;
{
Customer custAlias = null;
query = session.QueryOver<Campaign>()
.JoinAlias(x => x.Customer, () => custAlias)
}
{
Customer custAlias = null;
var result = query.OrderBy(() => custAlias.Name).Desc() // want to use string property name
.List()
}
Two different custAlias, but it still works :-)
(useful if you want to split pieces of a query in multiple methods... The only important thing is that they use the same naming for the aliases)

Linq Many to many

Could anyone please help with the linq for this problem.
Users have a list of Properties and Properties have a list of Users.
I first do a query to get all Properties with a particular CompanyId. This makes a new list which we'll call MyProperties.
I need to get all Tenants that have a property in the MyProperties list.
For other reasons, I don't have access to the "PropertiesUsers" Join table.
Sorry if it looks like I haven't thought this through, I've been banging my head all day on it.
You can use Enumerable.SelectMany() to flatten the hierarchy:
var myProperties = dbContext.Properties.Where(property => property.CompanyId = companyId);
var tenants = myProperties.SelectMany(property => property.Tenants);
Use Intersect:
var myPropertyIds = MyProperties.Select(p => p.PropertyId).ToArray();
var result = Users.Where(u => myPropertyIds.Intersect(
u.Properties.Select(p => p.PropertyId))
.Any());
If you are sure that the properties in both lists are the same instances you could use
var result = Users.Where(u => MyProperties.Intersect(
u.Properties)
.Any());

NHibernate 3 - type safe way to select a distinct list of values

I am trying to select a distinct list of values from a table whilst ordering on another column.
The only thing working for me so far uses magic strings and an object array. Any better (type-safe) way?
var projectionList = Projections.ProjectionList();
projectionList.Add(Projections.Property("FolderName"));
projectionList.Add(Projections.Property("FolderOrder"));
var list = Session.QueryOver<T>()
.Where(d => d.Company.Id == SharePointContextHelper.Current.CurrentCompanyId)
.OrderBy(t => t.FolderOrder).Asc
.Select(Projections.Distinct(projectionList))
.List<object[]>()
.ToList();
return list.Select(l => new Folder((string)l[0])).ToList();
btw, doing it with linq won't work, you must select FolderOrder otherwise you'll get a sql error (ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
)
and then doing that gives a known error : Expression type 'NhDistinctExpression' is not supported by this SelectClauseVisitor. regarding using anonymous types with distinct
var q = Session.Query<T>()
.Where(d => d.Company.Id == SharePointContextHelper.Current.CurrentCompanyId)
.OrderBy(d => d.FolderOrder)
.Select(d => new {d.FolderName, d.FolderOrder})
.Distinct();
return q.ToList().Select(f => new Folder(f));
All seems a lot of hoops and complexity to do some sql basics....
To resolve the type-safety issue, the syntax is:
var projectionList = Projections.ProjectionList();
projectionList.Add(Projections.Property<T>(d => d.FolderName));
projectionList.Add(Projections.Property<T>(d => d.FolderOrder));
the object [] thing is unavoidable, unless you define a special class / struct to hold just FolderName and FolderOrder.
see this great introduction to QueryOver for type-saftey, which is most certainly supported.
best of luck.

NHibernate Multiquery for eager loading without joins

Is it possible to use a multiquery and have two hql queries returning two different sets of entities where one of the sets are used in the other and that the session "fixes" this via the first level cache?
E.g. scenario (a dumb one and it could be solved with joins)
public class Room
{
...
public virtual ISet<Bookings> Bookings {get;set;}
public virtual bool IsAvailible {get;set;}
...
}
public class Booking
{
...
}
After executing a multicriteria with two hql's:
returning all rooms where
IsAvailible = true
returning all bookings having a room that has a room that IsAvailible
when accessing a room from the result and its bookings I want them to be resolved from the second resultset via the firstlevel cache of the session and there by avoiding n+1.
Generally speaking, NHibernate can use the cache to "combine" the results from queries executed through Multiquery. However, it should be noted that this usually only applies to cases where lazy collections are loaded with no restrictions whatsoever.
Examples:
Invoice iAlias = null;
InvoiceDetails idAlias = null;
// Base-Query: get Invoices with certain condition
var invoices = session.QueryOver<Invoice>()
.Where(i => i.Number == "001")
.Future<Invoice>();
// Option 1: this will still cause N+1 if we iterate through invoices,
// because it doesn't know better
var invoicedetails = session.QueryOver<InvoiceDetails>()
.JoinAlias(a => a.Invoice, () => iAlias)
.Where(() => iAlias.Number == "001")
.Future<InvoiceDetails>();
// Option 2: this will still cause N+1 if we iterate through invoices,
// because we limited the possible results using a where-condition
var invoices2 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.And(() => idAlias.Quantity > 5)
.Future<Invoice>();
// Option 3: this will work without N+1, because we don't use a filter
// -> NHibernate will use the collection in cache
var invoices3 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.Future<Invoice>();
foreach (Invoice i in invoices)
{
int count = i.Details.Count;
}
If we comment out two of the three options and execute the code, we will see that only option 3 will prevent a N+1, the other two will still load the InvoiceDetails for each Invoice in the loop.
Of course this is a very simple example and it is obvious that Option 3 could also be executed without the Base-query and still return the same result, but I hope you get the idea.
In the case where we load two different sets of entities, i.e. the root class is different as in Option 1, this "combining" will most likely not work.
Sorry, if I used QueryOver instead of HQL, but the same rules apply.
Gyus, keep in mind that sometimes you can have similar problems because of
LeftOuterJoin is not set.
.JoinAlias(x => x.Prop, () => propAlias, JoinType.LeftOuterJoin)