Entity Framework Relationship Definition Issue - entity-framework

In my application I have Sections, Tables, Rounds and Players.
Each Section has N Tables (Table "Keys": SectionId, TableId).
And each Table has N Rounds (Round "Keys": SectionId, TableId, RoundNumber)
Players start at a Table but move to other Tables each Round, and are identified by the Table they started at.
In a perfect universe, I'd be able to put a Collection of Players on the Round object, and query for the right ones inside my controller. I get the round and then I'd do something like this:
db.Entry(theRound)
.Collection(p => p.Players)
.Query()
.Where(p=> (p.SectionId == theRound.SectionId) &&
((p.TableNumber == theRound.NSPair && (p.Direction == "N" || p.Direction == "S")) ||
(p.TableNumber == theRound.EWPair && (p.Direction == "E" || p.Direction == "W"))))
.Load();
Problem is, once I declare the Collection of Player Objects in Round, Entity Framework puts "Round_XXXX" fields for each of the three Round keys above into the table for Players, including it in the where clauses of that query. And it won't find them because they aren't associated with Rounds...not in that sense, anyway.
Associating a Round object with the Table Objects that the Players start at (in addition to it's "normal one) seems to be an awful kludge.
What I've done for now is annotated the Players collection to be [NotMapped] and executed the query from the root level context.
There is a better way to do this, right?

Related

How to use AND with ANY in NSPredicates while staying efficient

Imagine I have a collection of books in Core Data. Each book can have multiple authors. Here's what this imaginary collection of books could look like (it only has one book, just to simplify things):
[
Book(authors: [
Author(first: "John", last: "Monarch"),
Author(first: "Sarah", last: "Monarch")
])
]
I want to filter my collection of books down to only those that have an author whose name is "Sarah Monarch".
From what I've seen so far, if I wanted to write an NSPredicate to filter my collection and return a filtered collection that only contains this book, I could do it by using a SUBQUERY:
NSPredicate(format: "SUBQUERY(authors, $author, $author.#first == 'Sarah' && $author.#last == 'Monarch').#count > 0")
My understanding is that this operation is essentially the same as:
books.filter {
book in
let matchingAuthors = book.authors.filter {
author in
author.first == "John" && author.last == "Monarch"
}
return matchingAuthors.count > 0
}
My problem is that it seems there's some inefficiency here — SUBQUERY (and the example code above) will look at all authors, when we can stop after finding just one that matches. My intuition would lead me to try a predicate like:
ANY (authors.#first == "Sarah" && authors.#last == "Monarch")
Which, as code, could be:
books.filter {
book in
return book.authors.contains {
author in
author.first == "John" && author.last == "Monarch"
}
}
But this predicate's syntax isn't valid.
If I'm right, and the SUBQUERY-based approach is less efficient (because it looks at all elements in a collection, rather than just stopping at the first match) is there a more correct and efficient way to do this?
The following is an alternative approach, which does not use SUBQUERY, but should ultimately have the same results. I've no idea whether this would in practice be more or less efficient than using SUBQUERY.
Your specific concern is the inefficiency of counting all the matching Authors, rather than just stopping when the first matching Author is found. Bear in mind that there is a lot going on behind the scenes whenever a fetch request is processed. First, CoreData has to parse your predicate and turn it into an equivalent SQLite query, which will look something like this:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, ... FROM ZBOOK t0 WHERE (SELECT COUNT(t1.Z_PK) FROM ZAUTHOR t1 WHERE (t0.Z_PK = t1.ZBOOK AND ( t1.ZFIRST == 'Sarah' AND t1.ZLAST == 'Monarch')) ) > 0
(The precise query will depend on whether the inverse relationship is defined and if so whether it is one-many or many-many; the above is based on the relationship having a to-one inverse, book).
When SQLite is handed that query to execute, it will check what indexes it has available and then invoke the query planner to determine how best to process it. The bit of interest is the subselect to get the count:
SELECT COUNT(t1.Z_PK) FROM ZAUTHOR t1 WHERE (t0.Z_PK = t1.ZBOOK AND ( t1.ZFIRST == 'Sarah' AND t1.ZLAST == 'Monarch'))
This is the bit of the query which corresponds to your first code snippet. Note that it is in SQLite terms a correlated subquery: it includes a parameter (t0.Z_PK - this is essentially the relevant Book) from the outer SELECT statement. SQLite will search the entire table of Authors, looking first to see whether they are related to that Book and then to see whether the author first and last names match. Your proposition is that this is inefficient; the nested select can stop as soon as any matching Author is found. In SQLite terms, that would correspond to a query like this:
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, ... FROM ZBOOK t0 WHERE EXISTS(SELECT 1 FROM ZAUTHOR t1 WHERE (t0.Z_PK = t1.ZBOOK AND ( t1.ZFIRST == 'Sarah' AND t1.ZLAST == 'Monarch')) )
(It's unclear from the SQLite docs whether the EXISTS operator actually shortcuts the underlying subselect, but this answer elsewhere on SO suggests it does. If not it might be necessary to add "LIMIT 1" to the subselect to get it to stop after one row is returned). The problem is, I don't know of any way to craft a CoreData predicate which would be converted into a SQLite query using the EXISTS operator. Certainly it's not listed as a function in the NSExpression documentation, nor is it mentioned (or listed as a reserved word) in the predicate format documentation. Likewise, I don't know of any way to add "LIMIT 1" to the subquery (though it's relatively straightforward to add to the main fetch request using fetchLimit.
So not much scope for addressing the issue you identify. However, there might be other inefficiencies. Scanning the Author table for each Book in turn (the correlated subquery) might be one. Might it be more efficient to scan the Author table once, identify those that meet the relevant criteria (first = "Sarah" and last = "Monarch"), then use that (presumably much shorter) list to search for the books? As I said at the start, that's an open question: I have no idea whether it is or isn't more efficient.
To pass results of one fetch request to another, use NSFetchRequestExpression. It's a bit arcane but hopefully the following code is clear enough:
let authorFetch = Author.fetchRequest()
authorFetch.predicate = NSPredicate(format: "#first == 'Sarah' AND #last == 'Monarch'")
authorFetch.resultType = .managedObjectIDResultType
let contextExp = NSExpression(forConstantValue: self.managedObjectContext)
let fetchExp = NSExpression(forConstantValue: authorFetch)
let fre = NSFetchRequestExpression.expression(forFetch: fetchExp, context: contextExp, countOnly: false)
let bookFetch = Book.fetchRequest()
bookFetch.predicate = NSPredicate(format: "ANY authors IN %#", fre)
let results = try! self.managedObjectContext!.fetch(bookFetch)
The result of using that fetch is a SQL query like this:
SELECT DISTINCT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, ... FROM ZBOOK t0 JOIN ZAUTHOR t1 ON t0.Z_PK = t1.ZBOOK WHERE t1.Z_PK IN (SELECT n1_t0.Z_PK FROM ZAUHTOR n1_t0 WHERE ( n1_t0.ZFIST == 'Sarah' AND n1_t0.ZLAST == 'Monarch')
This has its own complexities (a DISTINCT, a JOIN, and a subselect) but importantly the subselect is no longer correlated: it is independent of the outer SELECT so can be evaluated once rather than being re-evaluated for each row of the outer SELECT.

EF- two WHERE IN clauses in an INCLUDE table produce two EXISTS in SQL sent to server

I have an INCLUDE table that I want to check a couple of values, in the same row, using an IN clause. The below doesn't return the correct result set because it produces two EXISTS clauses with subqueries. This results in the 2 values being checked independently and not strictly in the same child row. (forgive any typos as I'm typing this in from printed code)
var db = new dbEntities();
IQueryable<dr> query = db.drs;
// filter the parent table
query = query.Where(p => DropDown1.KeyValue.ToString().Contains(p.system_id.ToString()));
// include the child table
query = query.Include(p => p.drs_versions);
// filter the child table using the other two dropdowns
query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && c => DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
// I tried removing the second c=> but received an error "'c' is inaccessible due to its protection level" error and couldn't find an clear answer to how this related to Entity Framework
// query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
This is an example of the query the code above produces...
SELECT *
FROM drs d
LEFT OUTER JOIN drs_versions v ON d.dr_id = v.dr_id
WHERE d.system_id IN (9,8,3)
AND EXISTS (SELECT 1 AS C1
FROM drs_versions sub1
WHERE d.tr_id = sub1.tr_id
AND sub1.version_id IN (9, 4, 1))
AND EXISTS (SELECT 1 AS C1
FROM drs_versions sub2
WHERE d.tr_id = sub2.tr_id
AND sub2.status_id IN (12, 7))
This is the query I actually want:
SELECT *
FROM drs d
LEFT OUTER JOIN drs_versions v ON d.dr_id = v.dr_id
WHERE d.system_id IN (9, 8, 3)
AND v.version_id IN (9, 4, 1)
AND v.status_id IN (12, 7)
How do I get Entity Framework to create a query that will give me the desired result set?
Thank you for your help
I'd drop all of the .ToString() everywhere and format your values ahead of the query to make it a lot easier to follow.. If EF is generating SQL anything like what you transcribed, you are casting to String just to have EF revert it back to the appropriate type.
From that it just looks like your parenthesis are a bit out of place:
I'm also not sure how something like DropDown2.KeyBalue.ToString() resolves back to what I'd expect to be a collection of numbers based on your SQL examples... I've just substituted this with a method called getSelectedIds().
IEnumerable<int> versions = getSelectedIds(DropDown2);
IEnumerable<int> statuses = getSelectedIds(DropDown3);
query = query
.Where(p => p.drs_versions
.Any(c => versions.Contains(c.version_id)
&& statuses.Contains(c.status_id));
As a general bit of advice I suggest always looking to simplify the variables you want to use in a linq expression as much as possible ahead of time to keep the text inside the expression as simple to read as possible. (avoiding parenthesis as much as possible) Make liberal use of line breaks and indentation to organize what falls under what, and use the code highlighting to double-check your closing parenthesis that they are closing the opening you expect.
I don't think your first example actually was input correctly as it would result in a compile error as you cannot && c => ... within an Any() block. My guess would be that you have:
query = query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.ToString().Contains(c.version_id.ToString())) && p.drs_versions.Any(c => DropDown3.KeyValue.ToString().Contains(c.status_id.ToString()));
Your issue is closing off the inner .Any()
query.Where(p => p.drs_versions.Any(c => DropDown2.KeyValue.Contains(c.version_id))
&& DropDown3.KeyValue.Contains(c.status_id)); //<-- "c" is still outside the single .Any() condition so invalid.
Even then I'm not sure this will fully explain the difference in queries or results. It sounds like you've tried typing across code rather than pasting the actual statements and captured EF queries. It may help to copy the exact statements from the code because it's pretty easy to mistype something when trying to simplify an example only to find out you've accidentally excluded the smoking gun for your issue.

OrientDB query friend of friends with more information and filtering

Introduction:
Hi,
I have a question regarding how to create an OrientDB query. The query should get all the vertices of the friends of friends (this is equivalent to the second level relationship) + the bridge between the 2. After that, the query should filter the records after an edge property.
I am running OrientDB 1.7.4.
Schema example:
Let's take an example, taking the following schema:
-> User is a Vertex, with property id (int)
-> Knows is an Edge, with property type (string) (let's say this property has a few values: friend, family, etc.)
The relations I am looking for are like this:
User <--knows--> User <--knows--> User
(1) (2) (3)
I need all relations, doesn't matter what direction they have (even if the direction is important for other queries).
To get all Users (User3) of second degree, a query like this will suffice:
select expand( set(both().both()) ) from <rid>
Questions:
Now, I have 2 main problems / questions I can't figure them out:
If the query above return a list of records from the class User (User3), how can I get the record as User (User3) AND the User (or the property id) from the user in the middle of the relation (see above: User2)
How can I filter the query to traverse (or select) only through edges that have a certain property. To be more specific I want the second query to find the Users that are from question 1 but that are friend of friends: User <--knows(method='friend')--> User <--knows(method='friend')--> User.
As vitorenesduarte suggested, I found the answer. Yes I should look for edges, filter the edges, than take out the vertices from the edges, like following:
select expand( unionAll(inE('knows')[method='friend'].out, outE('knows')[method='friend'].in ) from <rid>
The problem with this query is that I think it could be optimized if OrientDB would have a function to take both way vertices from edges, something like this:
bothE('knows')[method='friend'].both
But the .both function doesn't exist (at least in the version I use, which is 1.7.4). If anyone knows something about this, please leave a comment.
You can do:
TREVERSE * FROM (SELECT FROM User WHERE id == 1)
WHILE (#class == 'User') OR (#class == 'Knows' AND method == 'friend')
This will give you all vertices and edges starting from vertex with id = 1.
If you want to stop at the 2nd level, then
TREVERSE * FROM (SELECT FROM User WHERE id == 1)
WHILE (#class == 'User') OR (#class == 'Knows' AND method == 'friend')
MAXDEPTH 2
If you only want to have vertices:
SELECT FROM (
TREVERSE * FROM (SELECT FROM User WHERE id == 1)
WHILE (#class == 'User') OR (#class == 'Knows' AND method == 'friend')
MAXDEPTH 2
)
WHERE #class == 'User '
If you happen to need to know the path from the initial person to the last one, and all the possible paths from id = 1 up to the 2nd level.
SELECT $path FROM (
TREVERSE * FROM (SELECT FROM User WHERE id == 1)
WHILE (#class == 'User') OR (#class == 'Knows' AND method == 'friend')
MAXDEPTH 2
)
But if you have a fixed depth is better to do it with select, try this:
SELECT bothE('Knows')[method='friend'].bothV() FROM User WHERE id = 1

Limit the rows returned in Include entity

I have simple data model Project,Member and ProjectMember where Project to Member has many-to-many relationship. Therefore ProjectMember table contains the both foreign keys.
I writes the code
var result= db.Projects.Include(p=>p.ProjectMembers).Where(p=>p.ProjectMembers.Any(pm=>pm.DeletedUser==1));
and I see result.ProjectMembers count is 2 . Here I have got additional record with DeletedUser is not equal to 1
Did I do something wrong here?
What is the expression I have to use to get the only one record (or many with DeletedUser=1) for result.ProjectMembers
You are asking for Projects having at least one (= any) ProjectMember with DeletedUser == 1. This condition is met. Other ProjectMembers of the Project can have any other value than 1 for DeletedUser.
If you want the Project with only ProjectMembers with DeletedUser == 1 you should start the query at ProjectMember:
ProjectMembers.Include("Project").Where(pm => pm.DeletedUser == 1)

How to write query in Entity Framework

I'm new to Entity Framework. I have a database query which I need to convert to Entity Framework. how to write the query in LinQ to Enity
Can someone help me on that?
SELECT
FLD1,
FLD2,
SUM(FLD3),
(TO_CHAR(FLD4,'MM/DD/YYYY'))
FROM
TABLE1
WHERE
(FLD2=XXX ) AND
(FLD3 BETWEEN TO_DATE(VARDATE,'MMDDYYYY') AND TO_DATE(VARDATE1,'MMDDYYYY'))
GROUP BY
FLD1,
FLD2,
FLD4
Well...info is sparse and you filled it with a lot of different cases something like this would do.
_context.SomeObject
.Where(x=>x.SomeField == "SomeValue" && x.SomeField > 5 && x.SomeField < 10)
.Select(x=>new { x.SomeField1, x.SomeField2, x.SomeField2, SomeField4 = x.SomeChildCollection.Sum(y=>y.SomeChildvalue)
.GroupBy(x=>new {x.SomeField1, x.SomeField2, x.SomeField3})
.ToList()
This would result in a group where the key was an object with the values SomeField1, SomeField2, SomeField3, and the object would be the an anonymous projection with the 4 properties in the Select.
In some kinds of comparisions regarding dates you might need to use EntityFunctions.