I use OpenJPA 2.1 on WebSphere Application Server 8.
I have a bidirectional oneToMay relation like this:
#Entity(name = "table1")
public class Table1 {
#Id
#Column(columnDefinition = "DATE")
private Date date;
#OneToMany(mappedBy = "date", targetEntity = Table2.class, fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Table2> table2List;
}
#Entity(name = "table2")
public class Tabl2{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_date", nullable = false)
private Table1 table1;
#Column(name = "value", nullable = false, columnDefinition = "INT")
private int value;
}
Now I want to select the whole table1 entity with a condition on table2 like this:
TypedQuery<WeekBean> q = em.createQuery(
"SELECT t1 from table1 t1 JOIN FETCH t1.table2List t2 WHERE t2.value = :value",
t1.class);
But this does not work, beacause I get an ArgumentException:
Exception in thread "main" <openjpa-2.1.1-SNAPSHOT-r422266:1141200 nonfatal user error> org.apache.openjpa.persistence.ArgumentException: " Encountered "f" at character 50, but expected [",", ".", "GROUP", "HAVING", "INNER", "JOIN", "LEFT", "ORDER", "WHERE", <EOF>]."
at org.apache.openjpa.kernel.jpql.JPQLParser.parse(JPQLParser.java:51)
at org.apache.openjpa.kernel.ExpressionStoreQuery.newCompilation(ExpressionStoreQuery.java:154)
at org.apache.openjpa.datacache.QueryCacheStoreQuery.newCompilation(QueryCacheStoreQuery.java:262)
at org.apache.openjpa.kernel.QueryImpl.newCompilation(QueryImpl.java:672)
at org.apache.openjpa.kernel.QueryImpl.compilationFromCache(QueryImpl.java:654)
at org.apache.openjpa.kernel.QueryImpl.compileForCompilation(QueryImpl.java:620)
at org.apache.openjpa.kernel.QueryImpl.compileForExecutor(QueryImpl.java:682)
at org.apache.openjpa.kernel.QueryImpl.compile(QueryImpl.java:589)
at org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:996)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:107)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:86)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:34)
at org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:974)
How can I use a JOIN FETCH with a condition on the collection?
Best Regards Veote
According JPA 2.0 specification such a construct is not supported, and looks like OpenJPA also does not provide functionality as vendor extension:
The association referenced by the right side of the FETCH JOIN clause
must be an association that belongs to an entity that is returned as a
result of the query. It is not permitted to specify an identification
variable for the entities referenced by the right side of the FETCH
JOIN clause, and hence references to the implicitly fetched entities
cannot appear elsewhere in the query.
In your case this means that you cannot define t2 and as a consequence you cannot use it in the WHERE clause.
Related
I have a 'Skill' table where i store skills. And in 'Job' table i store all required skill when post job like UpWork. Employeers have checkbox to select all required skills. But i store skillID like: 1,5,6,8 in job table. When i retrieve the job details, i want to get name of the all skills because i want to show SkillName with other details of the Job from job table. My Web Api:
[HttpGet]
[Route("api/JobApi/BrowseJobs/")]
public object BrowseJobs()
{
var skills = db.Skills.ToDictionary(d => d.SkillID, n => n.SkillName);
var jobData = (from j in db.Jobs where j.Preference==2
//from cj in j.ClosedJobs.DefaultIfEmpty()
join cj in db.ClosedJobs.DefaultIfEmpty()
on j.JobID equals cj.JobID into closedJob
where !closedJob.Any()
join c in db.Categories on j.Category equals c.CategoryID
join jobContract in
(
from appliedJob in db.AppliedJobs.DefaultIfEmpty()
from offer in appliedJob.JobOffers.DefaultIfEmpty()
from contract in db.Contracts.DefaultIfEmpty()
select new { appliedJob, offer, contract }
).DefaultIfEmpty()
on j.JobID equals jobContract.appliedJob.JobID into jobContracts
where !jobContracts.Any(jobContract => jobContract.contract.CompletedDate != null)
select new
{
JobTitle = j.JobTitle,
JobID = j.JobID,
ReqSkillCommaSeperated = j.ReqSkill,
Category = c.CategoryName,
Budget=j.Budget,
Deadline=j.Deadline,
JobDetails=j.JobDetails,
PublishDate=j.PublishDate,
TotalApplied=(from ap in db.AppliedJobs where j.JobID == ap.JobID select ap.AppliedJobID).DefaultIfEmpty().Count()
}).AsEnumerable()
.Select(x => new
{
JobID = x.JobID,
JobTitle = x.JobTitle,
Category = x.Category,
Budget = x.Budget,
Deadline = x.Deadline,
JobDetails = x.JobDetails,
PublishDate = x.PublishDate,
SkillNames = GetSkillName(x.ReqSkillCommaSeperated, skills),
TotalApplied = (from ap in db.AppliedJobs where x.JobID == ap.JobID select ap.AppliedJobID).DefaultIfEmpty().Count()
}).ToList();
return jobData.AsEnumerable();
}
private string GetSkillName(string reqSkill, Dictionary<int, string> skills)
{
if (reqSkill == null) return string.Empty;
var skillArr = reqSkill.Split(',');
var skillNameList = skillArr.Select(skillId => skills[Convert.ToInt32(skillId)])
.ToList();
return String.Join(",", skillNameList);
}
My Problem is that the code is working well in my VS 2013. But when i uploaded it on a Godaddy live server, it doesn't work! returns 500 internal server error
Now i want to Make a SQL query instead of Linq. Can i do SQL with my desired result?
===================Edited=====================
your sql code is well worked. But i have others condition to be put on.
1. I need to show those job which is not closed yet (ClosedJobs table take the closed jobs ID).If a job ID is found on ClosedJobs table, it will not return in the list.
join cj in db.ClosedJobs.DefaultIfEmpty()
on j.JobID equals cj.JobID into closedJob
where !closedJob.Any()
Those job which is not found on Contracts table(Contracts table take the jobID of a job that is started as contract)
2nd Edit===================
join jobContract in
(
from appliedJob in db.AppliedJobs.DefaultIfEmpty()
from offer in appliedJob.JobOffers.DefaultIfEmpty()
from contract in db.Contracts.DefaultIfEmpty()
select new { appliedJob, offer, contract }
).DefaultIfEmpty()
on j.JobID equals jobContract.appliedJob.JobID into jobContracts
where !jobContracts.Any(jobContract => jobContract.contract.CompletedDate != null)
EXP: Job table has relation with AppliedJobs table. AppliedJobs table has relation with JobOffers. JobOffers has relation with Contracts.
i don't want to show those jobs that are completed.(Contracts.CompletedDate != null). When a contract starts the field CompletedDate is set to null. After completing the contract ,it is changed null to the completed date.
Where i will apply the condition?
How can i do that? Can you help me? #John Cappelletti
EDIT - Removed OUTER APPLY
Below is a simple example of using Stuff() and XML. If the sequence is important, then we must split the string first.
To be clear #Skills and #YourData are table variables and simply demonstrative.
Example
Declare #Skills table (SkillID int,SkillName varchar(50))
Insert Into #Skills values
(1,'ASP')
,(2,'JavaScript')
,(3,'AngularJS')
,(4,'WordPress')
,(5,'Joomla')
Declare #YourData table (ID int,ReqSkill varchar(50))
Insert Into #YourData values
(1,'2,3,4,5,1')
,(2,'3')
,(3,'3,4,5,2')
,(4,null)
Select A.ID
,Skills = Stuff((Select ',' +SkillName
From #Skills
Where charindex(concat(',',SkillID,','),','+A.ReqSkill+',')>0
For XML Path ('')),1,1,'')
From #YourData A
-- Your WHERE Statement Here --
Returns
ID Skills
1 ASP,JavaScript,AngularJS,WordPress,Joomla
2 AngularJS
3 JavaScript,AngularJS,WordPress,Joomla
4 NULL
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.
I am trying to write a query that includes 2 joins.
1 StoryTemplate can have multiple Stories
1 Story can have multiple StoryDrafts
I am starting the query on the StoryDrafts object because that is where it's linked to the UserId.
I don't have a reference from the StoryDrafts object directly to the StoryTemplates object. How would I build this query properly?
public JsonResult Index(int userId)
{
return Json(
db.StoryDrafts
.Include("Story")
.Include("StoryTemplate")
.Where(d => d.UserId == userId)
,JsonRequestBehavior.AllowGet);
}
Thank you for any help.
Try to flatten your hierarchy if it works for you. Here is a sample, and you may want to customize it for your needs.
var result = from c in db.Customers
join o in db.Orders
on c equals o.Customers
select new
{
custid = c.CustomerID,
cname = c.CompanyName,
address = c.Address,
orderid = o.OrderID,
freight = o.Freight,
orderdate = o.OrderDate
};
If flattering does not meet your requirements then you need to use query that returns a Nested Group. Finally, look at the following link for more references - LINQ Query Expressions .
When I retrieve a list of items from a database including some children (via .Include), and order the randomly, EF gives me an unexpected result.. I creates/clones addition items..
To explain myself better, I've created a small and simple EF CodeFirst project to reproduce the problem.
First i shall give you the code for this project.
The project
Create a basic MVC3 project and add the EntityFramework.SqlServerCompact package via Nuget.
That adds the latest versions of the following packages:
EntityFramework v4.3.0
SqlServerCompact v4.0.8482.1
EntityFramework.SqlServerCompact v4.1.8482.2
WebActivator v1.5
The Models and DbContext
using System.Collections.Generic;
using System.Data.Entity;
namespace RandomWithInclude.Models
{
public class PeopleContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Address> Addresses { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public string AdressLine { get; set; }
public virtual Person Person { get; set; }
}
}
The DB Setup and Seed data: EF.SqlServerCompact.cs
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using RandomWithInclude.Models;
[assembly: WebActivator.PreApplicationStartMethod(typeof(RandomWithInclude.App_Start.EF), "Start")]
namespace RandomWithInclude.App_Start
{
public static class EF
{
public static void Start()
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
Database.SetInitializer(new DbInitializer());
}
}
public class DbInitializer : DropCreateDatabaseAlways<PeopleContext>
{
protected override void Seed(PeopleContext context)
{
var address1 = new Address {AdressLine = "Street 1, City 1"};
var address2 = new Address {AdressLine = "Street 2, City 2"};
var address3 = new Address {AdressLine = "Street 3, City 3"};
var address4 = new Address {AdressLine = "Street 4, City 4"};
var address5 = new Address {AdressLine = "Street 5, City 5"};
context.Addresses.Add(address1);
context.Addresses.Add(address2);
context.Addresses.Add(address3);
context.Addresses.Add(address4);
context.Addresses.Add(address5);
var person1 = new Person {Name = "Person 1", Addresses = new List<Address> {address1, address2}};
var person2 = new Person {Name = "Person 2", Addresses = new List<Address> {address3}};
var person3 = new Person {Name = "Person 3", Addresses = new List<Address> {address4, address5}};
context.Persons.Add(person1);
context.Persons.Add(person2);
context.Persons.Add(person3);
}
}
}
The controller: HomeController.cs
using System;
using System.Data.Entity;
using System.Linq;
using System.Web.Mvc;
using RandomWithInclude.Models;
namespace RandomWithInclude.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var db = new PeopleContext();
var persons = db.Persons
.Include(p => p.Addresses)
.OrderBy(p => Guid.NewGuid());
return View(persons.ToList());
}
}
}
The View: Index.cshtml
#using RandomWithInclude.Models
#model IList<Person>
<ul>
#foreach (var person in Model)
{
<li>
#person.Name
</li>
}
</ul>
this should be all, and you application should compile :)
The problem
As you can see, we have 2 straightforward models (Person and Address) and Person can have multiple Addresses.
We seed the generated database 3 persons and 5 addresses.
If we get all the persons from the database, including the addresses and randomize the results and just print out the names of those persons, that's where it all goes wrong.
As a result, i sometimes get 4 persons, sometimes 5 and sometimes 3, and i expect 3. Always.
e.g.:
Person 1
Person 3
Person 1
Person 3
Person 2
So.. it's copying/cloning data! And that's not cool..
It just seems that EF looses track of what addresses are a child of which person..
The generated SQL query is this:
SELECT
[Project1].[ID] AS [ID],
[Project1].[Name] AS [Name],
[Project1].[C2] AS [C1],
[Project1].[ID1] AS [ID1],
[Project1].[AdressLine] AS [AdressLine],
[Project1].[Person_ID] AS [Person_ID]
FROM ( SELECT
NEWID() AS [C1],
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent2].[ID] AS [ID1],
[Extent2].[AdressLine] AS [AdressLine],
[Extent2].[Person_ID] AS [Person_ID],
CASE WHEN ([Extent2].[ID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
FROM [People] AS [Extent1]
LEFT OUTER JOIN [Addresses] AS [Extent2] ON [Extent1].[ID] = [Extent2].[Person_ID]
) AS [Project1]
ORDER BY [Project1].[C1] ASC, [Project1].[ID] ASC, [Project1].[C2] ASC
Workarounds
If i remove the .Include(p =>p.Addresses) from the query, everything goes fine. but of course the addresses aren't loaded and accessing that collection will make a new call to the database every time.
I can first get the data from the database and randomize later by just adding a .ToList() before the .OrderBy.. like this: var persons = db.Persons.Include(p => p.Addresses).ToList().OrderBy(p => Guid.NewGuid());
Does anybody have any idea of why it is happening like this?
Might this be a bug in the SQL generation?
As one can sort it out by reading AakashM answer and Nicolae Dascalu answer, it strongly seems Linq OrderBy requires a stable ranking function, which NewID/Guid.NewGuid is not.
So we have to use another random generator that would be stable inside a single query.
To achieve this, before each querying, use a .Net Random generator to get a random number. Then combine this random number with a unique property of the entity to get randomly sorted. And to 'randomize' a bit the result, checksum it. (checksum is a SQL Server function that compute a hash; original idea founded on this blog.)
Assuming Person Id is an int, you could write your query this way :
// Random instances should be stored and reused, not instanciated at each usage.
// But beware, it is not thread safe. If you want to share it between threads, you
// would have to use locks, see its documentation.
// https://learn.microsoft.com/en-us/dotnet/api/system.random.
// But using locks is a bad idea for scalability, especially in a Web context.
var randomGenerator = new Random();
// ...
var rnd = randomGenerator.NextDouble();
var persons = db.Persons
.Include(p => p.Addresses)
.OrderBy(p => SqlFunctions.Checksum(p.Id * rnd));
Like the NewGuid hack, this is very probably not a good random generator with a good distribution and so on. But it does not cause entities to get duplicated in results.
Beware:
If your query ordering does not guarantees uniqueness of your entities ranking, you must complement it for guarantying it. By example, if you use a non-unique property of your entities for the checksum call, then add something like .ThenBy(p => p.Id) after the OrderBy.
If your ranking is not unique for your queried root entity, its included children may get mixed with children of other entities having the same ranking. And then the bug will stay here.
Note:
I would prefer use .Next() method to get an int then combine it through a xor (^) to an entity int unique property, rather than using a double and multiply it. But SqlFunctions.Checksum unfortunately does not provide an overload for int data type, though the SQL server function is supposed to support it. You may use a cast to overcome this, but for keeping it simple I finally had chosen to go with the multiply.
tl;dr: There's a leaky abstraction here. To us, Include is a simple instruction to stick a collection of things onto each single returned Person row. But EF's implementation of Include is done by returning a whole row for each Person-Address combo, and reassembling at the client. Ordering by a volatile value causes those rows to become shuffled, breaking apart the Person groups that EF is relying on.
When we have a look at ToTraceString() for this LINQ:
var people = c.People.Include("Addresses");
// Note: no OrderBy in sight!
we see
SELECT
[Project1].[Id] AS [Id],
[Project1].[Name] AS [Name],
[Project1].[C1] AS [C1],
[Project1].[Id1] AS [Id1],
[Project1].[Data] AS [Data],
[Project1].[PersonId] AS [PersonId]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent2].[Id] AS [Id1],
[Extent2].[PersonId] AS [PersonId],
[Extent2].[Data] AS [Data],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [Person] AS [Extent1]
LEFT OUTER JOIN [Address] AS [Extent2] ON [Extent1].[Id] = [Extent2].[PersonId]
) AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC
So we get n rows for each A, plus 1 row for each P without any As.
Adding an OrderBy clause, however, puts the thing-to-order-by at the start of the ordered columns:
var people = c.People.Include("Addresses").OrderBy(p => Guid.NewGuid());
gives
SELECT
[Project1].[Id] AS [Id],
[Project1].[Name] AS [Name],
[Project1].[C2] AS [C1],
[Project1].[Id1] AS [Id1],
[Project1].[Data] AS [Data],
[Project1].[PersonId] AS [PersonId]
FROM ( SELECT
NEWID() AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent2].[Id] AS [Id1],
[Extent2].[PersonId] AS [PersonId],
[Extent2].[Data] AS [Data],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
FROM [Person] AS [Extent1]
LEFT OUTER JOIN [Address] AS [Extent2] ON [Extent1].[Id] = [Extent2].[PersonId]
) AS [Project1]
ORDER BY [Project1].[C1] ASC, [Project1].[Id] ASC, [Project1].[C2] ASC
So in your case, where the ordered-by-thing is not a property of a P, but is instead volatile, and therefore can be different for different P-A records of the same P, the whole thing falls apart.
I'm not sure where on the working-as-intended ~~~ cast-iron bug continuum this behaviour falls. But at least now we know about it.
I dont think there is an issue in query generation, but there is definately an issue when EF tries to convert rows into object.
It looks like there is an inherent assumption here that data for the same person in a joined statement will be returned grouped together order by or not.
for example the result of a joined query will always be
P.Id P.Name A.Id A.StreetLine
1 Person 1 10 ---
1 Person 1 11
2 Person 2 12
3 Person 3 13
3 Person 3 14
even if you order by some other column, same person would always appear one after the other.
this assumption is mostly true for any joined query.
But there is a deeper issue here i think. OrderBy is for when you want data in certain order ( as opposite to random), so that assumption does seem reasonable.
i think you should really get data out and then randomize it according to some other means in your code
From theory:
To sort a list of items, the compare function should be stable relative to items; this means that for any 2 items x, y the result of x< y should be the same as many time is queried(called).
I think the issue is related to misunderstanding of specification(documentation) of OrderBy method:
keySelector - A function to extract a key from an element.
EF didn't mention explicitly if the provided function should return the same value for same object as many times is called (in your case returns different/random values), but I think the "key" term that they used in documentation implicitly suggested this.
When you define a query path to define the query results, (use Include), the query path is only valid on the returned instance of ObjectQuery. Other instances of ObjectQuery and the object context itself are not affected. This functionality lets you chain multiple "Includes" for eager loading.
Therefor, Your statement translates into
from person in db.Persons.Include(p => p.Addresses).OrderBy(p => Guid.NewGuid())
select person
instead of what you intended.
from person in db.Persons.Include(p => p.Addresses)
select person
.OrderBy(p => Guid.NewGuid())
Hence your second workaround works fine :)
Reference: Loading Related Objects While Querying A Conceptual Model in Entity
Framework - http://msdn.microsoft.com/en-us/library/bb896272.aspx
I also ran into this problem, and solved it by adding a Randomizer Guid property to the main class I was fetching. I then set the column's default value to NEWID() like this (using EF Core 2)
builder.Entity<MainClass>()
.Property(m => m.Randomizer)
.HasDefaultValueSql("NEWID()");
When fetching, it gets a bit more complicated. I created two random integers to function as my order-by indexes, then ran the query like this
var rand = new Random();
var randomIndex1 = rand.Next(0, 31);
var randomIndex2 = rand.Next(0, 31);
var taskSet = await DbContext.MainClasses
.Include(m => m.SubClass1)
.ThenInclude(s => s.SubClass2)
.OrderBy(m => m.Randomizer.ToString().Replace("-", "")[randomIndex1])
.ThenBy(m => m.Randomizer.ToString().Replace("-", "")[randomIndex2])
.FirstOrDefaultAsync();
This seems to be working well enough, and should provide enough entropy for even a large dataset to be fairly randomized.
having the domain classes:
class A {
Date dateCreated
static hasMany = [b:B]
...
}
class B {
String name
String value
...
}
What createCriteria or HQL query can I use to return a list with:
A's creation dateB's value for A with the name entry set to 'X'
Note: Although there's no explicit constraint, there's only one "value" for each 'X' and a combination.
Thanks
The HQL would be
def results = A.executeQuery(
'select a.id, a.dateCreated, b from A a inner join a.b b ' +
'where b.name=:name',
[name: 'X'])
This will give you a List of 3-element Object[] arrays containing A.id, A.dateCreated, and the list of B instances. I added the id to the query so you can group by it client-side:
def grouped = results.groupBy { it[0] }
This will be a Map where the keys are the A ids and the values are the Lists from the original results.
Ideally you'd do the grouping at the database, but it would complicate the query, and assuming you don't have a large number of results it should be fast.