Doing joins with Fluent in a Vapor application - vapor

I'm struggling to figure out how to join my two tables together using fluent. In essence I want to run this SQL command:
SELECT p.name, o.amount, o.amount * p.amount total
FROM "OrderPoints" o
INNER JOIN "Points" p ON o.points_id = p.id
WHERE order_id = 10831
I've got my two models setup like so:
final class OrderPoint: Codable, PostgreSQLModel, Content, Migration {
var id: Int? = nil
var orderID: Order.ID
var pointID: Point.ID
var amount: Double = 0
var point: Parent<OrderPoint, Point> {
return parent(\.pointID)
}
}
final class Point: Codable, PostgreSQLModel, Content, Migration {
var id: Int? = nil
var name: String
var abbrev: String
var amount: Double
}
So now in my controller I loop over all of the orders that I care about:
// SELECT * FROM orders WHERE "month" = '2018-05-01'
let orders = try Order.query(on: req).filter(\Order.month == month).all()
return orders.flatMap(to: View.self) { orders in
let content = try orders.map { (order) -> OrderIndexContent in
let values = try order.orderPoints.query(on: req).all()
and I think that gets me all the items from order_points for the current ID, but I am just not seeing how to join it with the Points model so that I can do the rest of the query (i.e. get the multiplied amount and the point name)
An Order has multiple OrderPoint items.
An OrderPoint has a single Point item.
A Point could point to many OrderPoint items.
So in the way I think fluent refers to things, OrderPoints is not a pivot, as it's not a many to many.
This can't be right.
The following seems to be working, but there's no way this is the "right" way to do it as this would be massive amounts of extra SQL calls, right?
_ = try! order.orderPoints
.query(on: req)
.all()
.map(to: Void.self) { pointsForOrder in
pointsForOrder.forEach { orderPoint in
_ = try! orderPoint.point.get(on: req).map(to: Void.self) {
print("\(order.id!) \(order.name) \($0.abbrev) \(orderPoint.pointID) = \(orderPoint.amount) = \($0.amount * orderPoint.amount)")
}
}
}

In order to retrieve all the Points referenced in the OrderPoints from an Order you would have to query Point and join OrderPoint on it. Then you can add a filter on OrderPoint filtering the order you want to query.
This would result in the following Fluent query:
Point.query(on: req)
.join(field: \OrderPoint.pointID)
.filter(OrderPoint.self, \OrderPoint.orderID == order.id!)
.all()
This query would however only return an array of Points, so you would miss the information from OrderPoint as you pointed out in our discussion on Discord we would also have to decode OrderPoint, luckily Fluent has a nice method for this: .alsoDecode. So the final query would look like this:
Point.query(on: req)
.join(field: \OrderPoint.pointID)
.filter(OrderPoint.self, \OrderPoint.orderID == order.id!)
.alsoDecode(OrderPoint.self)
.all()
This would return a tuple containing both Point and OrderPoint

Related

I want to union together four queries and set this to be the repeater's data source

Based on user design I have to union together four queries and put them in a repeater.
var qryIssuer = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join iss in dbRRSP.Issuer on l.IssuerId equals iss.IssuerId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "Issuer",
LOAName = iss.CompanyName,
l.DateAdded
};
var qryEMD = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join emd in dbRRSP.Agent on l.AgentId equals emd.AgentId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "EMD",
LOAName = emd.CompanyName,
l.DateAdded
};
var qryEmdRep = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join ar in dbRRSP.AgentRepresentative on l.EMDRepresentativeId equals ar.AgentRepresentativeId
join arp in dbRRSP.Person on ar.PersonId equals arp.PersonId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription,
lat.LOAAccessTypeDescription,
PersonType = "EMD Rep",
LOAName = arp.FirstName + ' ' + arp.LastName, l.DateAdded
};
var qryLOAPerson = from l in dbRRSP.LOA
join lrb in dbRRSP.LOAOrReferredBy on l.LOAOrReferredById equals lrb.LoaOrReferredById
join lat in dbRRSP.LOAAccessType on l.LOAAccessTypeId equals lat.LOAAccessTypeId
join lp in dbRRSP.LOAPerson on l.LOAPersonId equals lp.LOAPersonId
where
l.PersonId == personId
select new
{
LOAOrReferredByDescription = lrb.LoaOrReferredByDescription, lat.LOAAccessTypeDescription,
PersonType = "Person",
LOAName = lp.LOAPersonName,
l.DateAdded
};
This is the four queries. And the trickiest part is that the last field is a datetime, which is causing me some issues. I know how to union two of them together like this:
var qryMultipleLOA = qryIssuer.Union(qryEMD).ToList().Select(loa => new ExtendedLOA
{
LOAOrReferredByDescription = loa.LOAOrReferredByDescription,
LOAAccessTypeDescription = loa.LOAAccessTypeDescription,
PersonType = loa.PersonType,
LOAName = loa.LOAName,
DateAdded = DateTime.Parse(loa.DateAdded.ToString()).ToString("MM/dd/yyyy")
});
But I'm at a loss on how to add the last two queries - first I tried wrapping it in brackets and adding a .Union which didn't work, and then when I tried to nest them with appropriate .ToLists, that didn't work either.
Below is the code to bind it to the repeater.
rptLOA.DataSource = qryMultipleLOA;
rptLOA.DataBind();
Suggestions would be greatly appreciated.
Did you try something like?
var qryMultipleLOA = qryIssuer.Union(qryEMD).Union(qryEmdRep).Union(qryLOAPerson).ToList();
Provided your queries' footprints are the same, this shouldn't be an issue to chain them upon each other.
Edit:
I would also recommend the following:
Create a class to hold an instance of the resultant data.
Instead of creating lists of dynamic variables generated from Linq and hoping they all match, funnel the linq results into a List. That way you can tell immediately if you have a type mismatch.
Once you have four lists of the same List, Unions as per my syntax above will be a snap.
Dynamic Linq lists can be a pain, unwieldy and a single property type change can throw of your code at runtime rather than design time. If you follow the steps above, your code will be much more maintainable and clear to you and others.
I hope this helps in some way.

Client Side Query to filter by extended properties

I have a need where i want to get the count of deleted items and get change set/collection for items that are not deleted.
How can i achieve that ?
i am able to do it using the LINQJS, but was wondering if there is more proper way of doing it.
var countryType = manager.metadataStore.getEntityType("Country");
var countries = manager.getEntities(countryType);
var deletedCount = Enumerable.From(countries)
.Where(function (x) {
return x.entityAspect.entityState == "Deleted"
}).Count();
self.totalRows(self.totalServerRows() - deletedCount);
var queryResult = Enumerable.From(countries)
.OrderBy(function(x){ x.Country_Code })
.Where(function (x) {
return x.entityAspect.entityState != "Deleted"
})
.Skip(self.pageIndex() * self.pageSize())
.Take(self.pageSize())
.ToArray();
self.list(queryResult);
The EntityManager.getEntities call is overloaded (see: http://www.breezejs.com/sites/all/apidocs/classes/EntityManager.html#method_getEntities ) so that you can simply ask for those entities within the entityManager that are of specific types or in specified states. So for 'deleted, try this:
var deletedEntities = em.getEntities(null, [EntityState.Deleted]);
or if you wanted just the Added or Modified entities
var deletedEntities = em.getEntities(null, [EntityState.Added, EntityState.Modified]);
I hope this helps.

LINQ to Entities query with join inside method for use in MVC app

In my Person table is a RequestedLocation column which stores location IDs. The IDs match the LocationId column in the Locations table, the Locations table also has the text location names, in the LocatioName column.
In my view, I need to display the string LocationName in the view which has the Person model passed to it. The view will be displaying a List of people in a telerik grid. CUrrently it works great, except the RequestedLocation column is all integers.
I am populating all my grids with methods containing LINQ queries. Here is the method that currently works:
public List<Person> GetPeople()
{
var query = from p in _DB.Person.ToList()
select p;
return query.ToList();
}
Here is the regular SQL query that works, and I need to convert into LINQ:
SELECT ApplicantID
,FirstName
,LastName
,MiddleName
,DateofBirth
,Gender
,RequestedVolunteerRole
,RequestedVolunteerLocation
,l.LocationName
FROM Form.Person p
JOIN dbo.Location l ON p.RequestedVolunteerLocation = l.LocationID
Order BY ApplicantID
Here is my attempt to convert to LINQ:
public List<NewApplicantViewModel> GetPeople()
{
var query = from pl in _DB.Person.ToList()
join l in _Elig_DB.Locations.ToList() on pl.RequestedVolunteerLocation equals l.LocationID
select new
{
pl.RequestedVolunteerLocation = l.LocationName
};
return query.ToList();
The number of errors I get from this are numerous, but most are along the lines of:
Cannot convert from type Annonymous to Type List<NewAPplicantModel>
and
Invalid annonymous type declarator.
Please help, and thank you for reading my post.
Oh, and I have only been programming for a couple months, so if I am going about this all wrong, please let me know. Only thing I have to stick with is the table structure because it is an existing app that I am updating, and changing the location or person tables would have large consequences.
public List<NewApplicantViewModel> GetPeople()
{
var query = from pl in _DB.Person
join l in _Elig_DB.Locations on pl.RequestedVolunteerLocation
equals l.LocationID
select new NewApplicantViewModel
{
LocationName = l.LocationName,
otherPropery = p.Property
};
return query.ToList();
}
Beware of calling _DB.Person.ToList() it will load all persons from DB because ToList() immediately executes the query and the join would be performed in memory (not in DB).
The reason you are getting an error is you are projecting an anonymous type
select new
{
pl.RequestedVolunteerLocation = l.LocationName
};
Instead, you need to project a NewApplicantViewModel
select new NewApplicantViewModel
{
RequestedVolunteerLocation = l.LocationName
};

Getting a list of distinct entities projected into a new type with extra field for the count

I'm designing an interface where the user can join a publicaiton to a keyword, and when they do, I want to suggest other keywords that commonly occur in tandem with the selected keyword. The trick is getting the frequency of correlation alongside the properties of the suggested keywords.
The Keyword type (EF) has these fields:
int Id
string Text
string UrlString
...and a many-to-many relation to a Publications entity-set.
I'm almost there. With :
var overlappedKeywords =
selectedKeyword.Publications.SelectMany(p => p.Keywords).ToList();
Here I get something very useful: a flattened list of keywords, each duplicated in the list however many times it appears in tandem with selectedKeyword.
The remaining Challenge:
So I want to get a count of the number of times each keyword appears in this list, and project the distinct keyword entities onto a new type, called KeywordCounts, having the same fields as Keyword but with one extra field: int PublicationsCount, into which I will populate the count of each Keyword within overlappedKeywords. How can I do this??
So far I've tried 2 approaches:
var keywordCounts = overlappingKeywords
.Select(oc => new KeywordCount
{
KeywordId = oc.Id,
Text = oc.Text,
UrlString = oc.UrlString,
PublicationsCount = overlappingKeywords.Count(ok2 => ok2.Id == oc.Id)
})
.Distinct();
...PublicationsCount is getting populated correctly, but Distinct isn't working here. (must I create an EqualityComarer for this? Why doesn't the default EqualityComarer work?)
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = ???
Text = ???
UrlString = ???
PublicationsCount = ???
})
I'm not very clear on GroupBy. I don't seem to have any access to 'o' in the Select, and c isn't comping up with any properties of Keyword
UPDATE
My first approach would work with a simple EqualityComparer passed into .Distinct() :
class KeywordEqualityComparer : IEqualityComparer<KeywordCount>
{
public bool Equals(KeywordCount k1, KeywordCount k2)
{
return k1.KeywordId== k2.KeywordId;
}
public int GetHashCode(KeywordCount k)
{
return k.KeywordId.GetHashCode();
}
}
...but Slauma's answer is preferable (and accepted) because it does not require this. I'm still stumped as to what the default EqualityComparer would be for an EF entity instance -- wouldn't it just compare based on primary ids, as I did above here?
You second try is the better approach. I think the complete code would be:
var keywordCounts = overlappingKeywords
.GroupBy(o => o.Id)
.Select(c => new KeywordCount
{
Id = c.Key,
Text = c.Select(x => x.Text).FirstOrDefault(),
UrlString = c.Select(x => x.UrlString).FirstOrDefault(),
PublicationsCount = c.Count()
})
.ToList();
This is LINQ to Objects, I guess, because there doesn't seem to be a EF context involved but an object overlappingKeywords, so the grouping happens in memory, not in the database.

How to join multiple tables using LINQ-to-SQL?

I'm quite new to linq, so please bear with me.
I'm working on a asp.net webpage and I want to add a "search function" (textbox where user inputs name or surname or both or just parts of it and gets back all related information). I have two tables ("Person" and "Application") and I want to display some columns from Person (name and surname) and some from Application (score, position,...). I know how I could do it using sql, but I want to learn more about linq and thus I want to do it using linq.
For now I got two main ideas:
1.)
var person = dataContext.GetTable<Person>();
var application = dataContext.GetTable<Application>();
var p1 = from p in Person
where(p.Name.Contains(tokens[0]) || p.Surname.Contains(tokens[1]))
select new {Id = p.Id, Name = p.Name, Surname = p.Surname}; //or maybe without this line
//I don't know how to do the following properly
var result = from a in Application
where a.FK_Application.Equals(index) //just to get the "right" type of application
//this is not right, but I don't know how to do it better
join p1
on p1.Id == a.FK_Person
2.) The other idea is just to go through "Application" and instead of "join p1 ..." to use
var result = from a in Application
where a.FK_Application.Equals(index) //just to get the "right" type of application
join p from Person
on p.Id == a.FK_Person
where p.Name.Contains(tokens[0]) || p.Surname.Contains(tokens[1])
I think that first idea is better for queries without the first "where" condition, which I also intended to use. Regardless of what is better (faster), I still don't know how to do it using linq. Also in the end I wanted to display / select just some parts (columns) of the result (joined tables + filtering conditions).
I really want to know how to do such things using linq as I'll be dealing also with some similar problems with local data, where I can use only linq.
Could somebody please explain me how to do it, I spent days trying to figure it out and searching on the Internet for answers.
var result = from a in dataContext.Applications
join p in dataContext.Persons
on p.Id equals a.FK_Person
where (p.Name.Contains("blah") || p.Surname.Contains("foo")) && a.FK_Application == index
select new { Id = p.Id, Name = p.Name, Surname = p.Surname, a.Score, a.Position };
Well as Odrahn pointed out, this will give you flat results, with possibly many rows for a single person, since a person could join on multiple applications that all have the same FK. Here's a way to search all the right people, and then add on the relevant application to the results:
var p1 = from p in dataContext.Persons
where(p.Name.Contains(tokens[0]) || p.Surname.Contains(tokens[1]))
select new {
Id = p.Id, Name = p.Name, Surname = p.Surname,
BestApplication = dataContext.Applications.FirstOrDefault(a => a.FK_Application == index /* && ???? */);
};
Sorry - it looks like this second query will result in a roundtrip per person, so it clearly won't be scalable. I assumed L2S would handle it better.
In order to answer this properly, I need to know if Application and Person are directly related (i.e. does Person have many Applications)? From reading your post, I'm assuming that they are because Application seems to have a foreign key to person.
If so, then you could create a custom PersonModel which will be populated by the fields you need from the different entities like this:
class PersonModel
{
string Name { get; set; }
string Surname { get; set; }
List<int> Scores { get; set; }
List<int> Positions { get; set; }
}
Then to populate it, you'd do the following:
// Select the correct person based on Name and Surname inputs
var person = dataContext.Persons.Where(p => p.Name.Contains("firstname") || p.Name.Contains("surname")).FirstOrDefault();
// Get the first person we find (note, there may be many - do you need to account for this?)
if (person != null)
{
var scores = new List<int>();
var positions = new List<int>();
scores.AddRange(person.Applications.Select(i => i.Score);
positions.AddRange(person.Applications.Select(i => i.Position);
var personModel = new PersonModel
{
Name = person.Name,
Surname = person.Surname,
Scores = scores,
Positions = positions
};
}
Because of your relationship between Person and Application, where a person can have many applications, I've had to account for the possibility of there being many scores and positions (hence the List).
Also note that I've used lambda expressions instead of plain linqToSql for simple selecting so that you can visualise easily what's going on.

Resources