I'm using Larave 9 to build a complicated web application.
I'm trying to get foreign key already set in relationship of a model to make another dynamic join query for some other data.
This is an example to make a dynamic query after getting foreign keys from many models:
foreach ($joinFunc as $table => $jQ){
$relTbl = $jQ['related'];
$k = $jQ['oKey'];
$fk = $jQ['fKey'];
$q->leftJoin($table, $table. '.' . $k, $relTbl. '.' .$fk);
}
My relationship in model is:
public function autopays()
{
return $this->hasMany(ClientAutoPay::class, 'client_id', 'id');
}
I want to get client_id set in the relationship
$q = Client::query()->with('autopays');
dump(
$q->first()->autopays()->getRelated()->getKeyName(),
$q->first()->autopays()->getRelated()->getForeignKey(),
);
but the result id:
"id"
"client_auto_pay_id"
what I need is to get client_id instead of client_auto_pay_id. Any help :(
I found a solution to check if the relationship has an instance ($q->whereHas()) which gave me the table name and if no instance found then I didn't pass it to dynamic joining query loop.
One thing I had to pass manually from the controller to my function and that is array of each table joining ownKeys and foreignKeys.
Now my dynamic dataTable by Livewire component is working awesome 👍
Related
I'm using DBIx::Class as or mapper for my Perl project. When it comes to generating test data I'm using DBIx::Class::ResultSet::new to create new entities in memory. In order to link entities with relationships I use set_from_related. This works absolutely flawless until I try to set the value(s) for a has_many relationship. Pseudo example:
# Table 'AUTHOR' has
# one-to-one (belongs_to) relationship named 'country' to table 'COUNTRY'
# one-to-many (has_many) relationship named 'books' to table 'BOOK'
my $s = Schema::getSchema();
my $author = $s->resultset('Author')->new({ name => 'Jon Doe', year_of_birth => 1982 });
my $country = $s->resultset('Country')->new({ name => 'Germany', iso_3166_code => 'DE' });
my $book = $s->resultset('Book')->new({ title => 'A star far away', publishing_year => 2002 });
# Now let's make 'em known to each other
$author->set_from_related('country', $country);
$author->set_from_related('books', $book);
# At this point
# $author->country is defined
# $author->books->first is undef <<<---- Problem
I cannot find a suitable method in the DBIx::Class::Relationship::Base documentation. The one closest to what I need is add_to_$rel but this method creates (persists) the entities. This is not an option for me as some of the entites used in my project don't belong to me (no write permission).
Does anyone have an idea how to add entities in memory for a has_many relationship ?
That's not possible as newly created result objects don't have their primary key column(s) populated until they are persisted to the database.
What you possibly want is to use multi-create and run that inside a transaction.
I've written this query that fetches user ids (that's for now, cos I actually need way more fields from the user table as well as from another table called image that is related the user table).
The problem with this query is that it returns a plain object and I need an entity object, I mean I know I could just deserialise it to whatever model I need, but the thing also is that I normally deserialise entity to a required response model. Also, I would like to avoid making
a couple of requests: one fetching user ids and the other fetching right entity objects by those ids using queryBuilder.
So, it seems that one possible solution would be to rewrite this query to make use of queryBuilder straight away.
const matchedUsers = await this.usersRepository.query(
`
SELECT id FROM users
WHERE id IN (
SELECT "usersId" FROM locations_available_fighters_users
WHERE "locationsId" IN (
SELECT "locationsId" FROM locations_available_fighters_users
WHERE "usersId" = ${ me.getId() }
)
) AND is_active IS TRUE
AND id != ${ me.getId() }
AND weight = '${ me.getWeight() }'
AND gender = '${ me.getGender() }'
AND role_name = '${ me.getRoleName() }';
`
);
If the problems you're trying to solve are:
Use a single query
Get the returned data as entity objects rather than raw results
Use QueryBuilder to avoid writing raw queries
I believe what you should be looking into is typeorm subqueries.
For the query you posted in the question, you can try something like below using QueryBuilder:
// Subquery for your inner most subquery,
const locationsQb = connection.getRepository(LocationsAvailableFightersUser)
.createQueryBuilder("lafu_1")
.select("lafu_1.locationsId")
.where("lafu_1.usersId = :usersID", { usersID: me.getId() });
// Subquery for your middle subquery,
const usersQb = connection.getRepository(LocationsAvailableFightersUser)
.createQueryBuilder("lafu_2")
.select("lafu_2.usersId")
.where("lafu_2.locationsId IN (" + locationsQb.getQuery() + ")");
// Query for retrieving `User` entities as you needed,
const matchedUsers = await connection.getRepository(User)
.createQueryBuilder("user")
.where("user.id IN (" + usersQb.getQuery() + ")")
.setParameters(locationsQb.getParameters())
.getMany();
Here I assumed that,
LocationsAvailableFightersUser is the entity for locations_available_fighters_users table
User is the entity for users table
Hope this helps you. Cheers 🍻 !!!
I am using EF Framework to retrieve the data from SQL DB.
Sub Request Table looks like below:
In this table "org_assigneddept" is foreign key to another Department Table.
I have list of Departments as Input and I want to retrieve only those rows from DB whose org_assigneddept is matching the list.
Please find my whole code:-
private List<EventRequestDetailsViewModel> GetSummaryAssignedDeptEventRequests(List<EmpRoleDeptViewModel> vmDept)
{
List<EventRequestDetailsViewModel> vmEventRequestDeptSummary = new List<EventRequestDetailsViewModel>();
RequestBLL getRequestBLL = new RequestBLL();
Guid subRequestStatusId = getRequestBLL.GetRequestStatusId("Open");
using (var ctxGetEventRequestSumm = new STREAM_EMPLOYEEDBEntities())
{
vmEventRequestDeptSummary = (from ers in ctxGetEventRequestSumm.SubRequests
where vmDept.Any(dep=>dep.DeptId == ers.org_assigneddept)
select new EventRequestDetailsViewModel
{
SubRequestId = ers.org_subreqid
}).ToList();
}
}
It is giving the following error at the LINQ Query level:-
System.NotSupportedException: 'Unable to create a constant value of
type 'Application.Business.DLL.EmpRoleDeptViewModel'. Only primitive
types or enumeration types are supported in this context.'
Please let me know as how can I achieve the result
You cannot pass the department VMs to SQL, it doesn't know what those are.
// Extract the IDs from the view models.. Now a list of primitive types..
var departmentIds = vmDept.Select(x => x.DeptId).ToList();
then in your select statement...
..
where departmentIds.Contains(id=> id == ers.org_assigneddept)
..
I am a newbie to Laravel 5.2 and am working on a legacy system and am a bit confused regarding eloquent and would appreciate someone giving me the code.
There are 3 tables:
cards
categories
cards2cat
cards can be part many categories which are kept in cards2cat table.
The cards2cat table has the following structure
id (primary key)
image (card)
category
What I want to do is to have a method in the Cards model called something like getCardsWithCategores which returned the cards info plus the names of the categories from the category table.
The categories table has a key of id and a field category.
Thanks!
Go to your Card2Cats model and add this:
public function categories()
{
return $this->hasOne('App\Categories','id','category');
}
public function cards()
{
return $this->hasOne('App\Cards','id','image');
}
For the query you do this:
$cards = Card2Cat::with('categories','cards')->get();
foreach ($cards as $key => $value) {
echo $value->id.', Card:'.$value->cards->name.', Category:'.$value->categories->category.'<br>';
//$value->cards gives you all column of cards and you can do
//$value->cards->colName
// same goes for $value->categories
}
Make sure the spelling of your classes and table column names are correct before running the code :D
Sorry for this being so long, but at least I think I got all info to be able to understand and maybe help?
I would like to load data from my database using eager loading.
The data is set up in five tables, setting up two Levels of m:n relations. So there are three tables containing data (ordered in a way of hierarchy top to bottom):
CREATE TABLE [dbo].[relations](
[relation_id] [bigint] NOT NULL
)
CREATE TABLE [dbo].[ways](
[way_id] [bigint] NOT NULL
)
CREATE TABLE [dbo].[nodes](
[node_id] [bigint] NOT NULL,
[latitude] [int] NOT NULL,
[longitude] [int] NOT NULL
)
The first two really only consist of their own ID (to hook other data not relevant here into).
In between these three data tables are two m:n tables, with a sorting hint:
CREATE TABLE [dbo].[relations_ways](
[relation_id] [bigint] NOT NULL,
[way_id] [bigint] NOT NULL,
[sequence_id] [smallint] NOT NULL
)
CREATE TABLE [dbo].[ways_nodes](
[way_id] [bigint] NOT NULL,
[node_id] [bigint] NOT NULL,
[sequence_id] [smallint] NOT NULL
)
This is, essentially, a part of the OpenStreetMap data structure. I let Entity Framework build it's objects from this database and it set up the classes exactly as the tables are.
The m:n tables do really exist as class. (I understand in EF you can build your objects m:n relation without having the explicit in-between class - should I try to change the object model in this way?)
What I want to do: My entry point is exactly one item of relation.
I think it would be best to first eager load the middle m:n relation, and then in a loop iterate over that and eager load the lowest one. I try to do that in the following way
IQueryable<relation> query = context.relations;
query = query.Where( ... ); // filters down to exactly one
query = query.Include(r => r.relation_members);
relation rel = query.SingleOrDefault();
That loads the relation and all it's 1:n info in just one trip to the database - ok, good. But I noticed it only loads the 1:n table, not the middle data table "ways".
This does NOT change if I modify the line like so:
query = query.Include(r => r.relation_members.Select(rm => rm.way));
So I cannot get the middle level loaded here, it seems?
What I cannot get working at all is load the node level of data eagerly. I tried the following:
foreach (relation_member rm in rel.relation_members) {
IQueryable<way_node> query = rm.way.way_nodes.AsQueryable();
query = query.Include(wn => wn.node);
query.Load();
}
This does work and eagerly loads the middle level way and all 1:n info of way_node in one statement for each iteration, but not the Information from node (latitude/longitude). If I access one of these values I trigger another trip to the database to load one single node object.
This last trip is deadly, since I want to load 1 relation -> 300 ways which each way -> 2000 nodes. So in the end I am hitting the server 1 + 300 + 300*2000... room for improvment, I think.
But how? I cannot get this last statement written in valid syntax AND eager loading.
Out of interest; is there a way to load the whole object graph in one trip, starting with one relation?
Loading the whole graph in one roundtrip would be:
IQueryable<relation> query = context.relations;
query = query.Where( ... ); // filters down to exactly one
query = query.Include(r => r.relation_members
.Select(rm => rm.way.way_nodes
.Select(wn => wn.node)));
relation rel = query.SingleOrDefault();
However, since you say that the Include up to ...Select(rm => rm.way) didn't work it is unlikely that this will work. (And if it would work the performance possibly isn't funny due to the complexity of the generated SQL and the amount of data and entities this query will return.)
The first thing you should investigate further is why .Include(r => r.relation_members.Select(rm => rm.way)) doesn't work because it seems correct. Is your model and mapping to the database correct?
The loop to get the nodes via explicit loading should look like this:
foreach (relation_member rm in rel.relation_members) {
context.Entry(rm).Reference(r => r.way).Query()
.Include(w => w.way_nodes.Select(wn => wn.node))
.Load();
}
Include() for some reason sometimes gets ignored when there is sorting/grouping/joining involved.
In most cases you can rewrite an Include() as a Select() into an anonymous intermediary object:
Before:
context.Invoices
.Include(invoice => invoice .Positions)
.ToList();
After:
context.Invoices
.Select(invoice => new {invoice, invoice.Positions})
.AsEnumerable()
.Select(x => x.invoice)
.ToList();
This way the query never should loose Include() information.
//get an associate book to an author
var datatable = _dataContext.Authors
.Where(x => authorids.Contains(x.AuthorId))
.SelectMany(x => x.Books)
.Distinct();