Doctrine ManyToMany relationship issues - doctrine-orm

I'm trying to add Doctrine entities and relationships to my existing database schema but I'm running into some issues.
I have 4 tables:
+-------------+ +-----------+ +-----------------+ +-------------------+
| customers | | acl_roles | | acl_permissions | | acl_customer_role |
--------------- ------------- ------------------- ---------------------
| customer_id | | id | | role_id | | customer_id |
+-------------+ | name | | resource_id | | acl_role_id |
+------------ | flags | +--------------------
+------------------
In my ACL customers can have many roles and each role can have many permissions. The mapping of customers/roles is done through the acl_customer_role table.
I'm currently having issues making this relationship work. These are my entities (removed some standard annotations for brevity):
class Customer {
/**
* #ORM\ManyToMany(targetEntity="AclRole", cascade="persist")
* #ORM\JoinTable(name="acl_customer_role",
* joinColumns={#ORM\JoinColumn(name="acl_role_id", referencedColumnName="customer_id")}
* )
*/
protected $roles;
}
class AclRole {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
}
As you can see, in my customer entity I'm defining the $roles. It's a ManyToMany relationship since many roles can belong to many customers. I'm setting up my join table to be acl_customer_role and I specify the columns on which the join needs to take place. However, I get the following error:
PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'acl_customer_role.aclrole_id' in 'on clause'' in vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:641
It seems that Doctrine's query is trying to join on 'acl_customer_role.aclrole_id' which obviously doesn't exists.
How can I specify the relationship correctly?
UPDATE:
Somehow it seems that Doctrine is modifying my column name. When I specify acl_role_id Doctrine strips out the first underscore and assumes that the column name is aclrole_id (as demonstrated in the error message in the question above). However, when I add two underscores such as acl__role_id it leaves all underscores in there and gives pretty much the same error other than that now it can't join on acl__role_id.
I'm pretty much at a loss..

I know this question is old but recently i have came across the same error / problem and i have found the solution.
By default Doctrine is using DefaultNamingStrategy class for generating e.g. joinColumn, joinTableName, propertyToColumnName names:
...
public function joinColumnName($propertyName)
{
return $propertyName . '_' . $this->referenceColumnName();
}
That's why in your case aclrole_id column was generated.
To change that behavior all you have to do is to change naming strategy for Doctrine2 to underscore in you app/config.yml file:
doctrine:
orm:
# ...
naming_strategy: doctrine.orm.naming_strategy.underscore
UnderscoreNamingStrategy class
...
public function joinColumnName($propertyName)
{
return $this->underscore($propertyName) . '_' . $this->referenceColumnName();
}
This would generate: acl_role_id
You can also implement your custom naming strategy: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/namingstrategy.html
Note: This was introduced in version 2.3.

Related

Power BI data model design relationships - direct active relationship would introduce ambiguity between the tables

Can anyone offer some modeling & relationship advise in Power BI?
I have two Customer tables at different grains that I am trying to relate to a Customer rollup group ('dimCustomers').
The two customers tables ('dimBillTierCustomer' and 'dimCustomerMeter') are individually related to my fact table ('factSummaryTicket'). These two relationships work individually, but I want them to be aware of the relationship they each have to 'dimCustomers', so I can use Customers to filter both tables in the report.
When I relate each of them, I get an error message on the second relationship.
You can’t create a direct active relationship between 'dimCustomerMeter' and 'dimCustomers' because that would introduce ambiguity between the tables 'dimCustomers' and 'factSummaryTicket'. To make this relationship active, deactivate or delete one of the relationships between 'dimCustomers' and 'factSummaryTicket' first.
Screenshots below show sample data, table relations, and the error message.
Bill Tier is for Customer pricing rules. Customer Meter is customer locations hierarchy. Customer should filter both of these tables.
Table Relations
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
| Relation (From : To) | CrossFiltering | FromCardinality | ToCardinality | IsActive |
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
| [gopherMeterId].[factSummaryTicket] ==> : <== [bisonMeterId].[dimCustomerMeter] | OneDirection | Many | One | TRUE |
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
| [CustBillTierKey].[factSummaryTicket] ==> : <== [CustTierKey].[dimBillTierCustomer] | OneDirection | Many | One | TRUE |
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
| [eticketOperatorId].[dimCustomerMeter] ==> : <== [eticketOperatorId].[dimCustomers] | OneDirection | Many | One | **FALSE** |
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
| [CustKey].[dimBillTierCustomer] ==> : <== [eticketOperatorId].[dimCustomers] | OneDirection | Many | One | TRUE |
+------------------------------------------------------------------------------------------+----------------+-----------------+---------------+-----------+
Sample Data
Table Diagram
One simple change gave me the functionality I needed. Without having to relate ‘dimCustomerMeter’ and ‘dimBillTierCustomer’. The solution was to enable ‘Bi-Directional’ instead of ‘Single’.
Bi Directional Relationship

How to create foreign key constraint without relationship in Doctrine

I have the following 2 tables:
+-------+ +------------------------------+
| users | | blocked_users |
+-------+ +------------------------------+
| id | | id, user_id, blocked_user_id |
+-------+ +------------------------------+
My User class has the following relationship defined:
/**
* Users the instance user has blocked.
*
* #ORM\OneToMany(
* targetEntity="BlockedUser",
* mappedBy="blocker",
* fetch="EXTRA_LAZY",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
*
* #var BlockedUser[]|Collection
*/
protected $blockedUsers;
I haven't defined a blockedByUsers relationship, since a user should't know who blocked them. However, when I deleted a User who has been blocked I get a foreign key exception. I could fix the exception by adding another User::$blockedByUsers relationship, but as I said I don't believe that should exist.
Is there any other way to create the cascading foreign key relationship?
You can set the foreign key on the blocked_users table when you define the relationship from blocked_user_id to user:
#JoinColumn(name="blocked_user_id", referencedColumnName="id", onDelete="cascade")
The onDelete option will cascade deletion as you expect.

return all fields of document without specified fields in model

I want to show all fields of returning documents.
Because the returned docuemets' fields are differed.
I want to show all the fields without specifying fields in the model.
Model
class History
include Mongoid::Document
end
Returned documents only contains _id for now
+--------------------------+
| _id |
+--------------------------+
| 558a64b9253c9b33cbc90f8c |
| 558a64b9253c9b33cbc90f8d |
| 558a64b9253c9b33cbc90f8e |
| 558a64b9253c9b33cbc90f8f |
| 558a64b9253c9b33cbc90f90 |
| 558a64b9253c9b33cbc90f91 |
| 558a64b9253c9b33cbc90f92 |
| 558a64b9253c9b33cbc90f93 |
| 558a64b9253c9b33cbc90f94 |
| 558a64b9253c9b33cbc90f95 |
+--------------------------+
From the fine manual:
Dynamic fields
By default Mongoid doesn't supports dynamic fields. You can tell mongoid that you want to add dynamic fields by including 'Mongoid::Attributes::Dynamic' in model. 'Mongoid::Attributes::Dynamic' will allow attributes to get set and persisted on the document even if a field was not defined for them.
So you'll want to say:
class History
include Mongoid::Document
include Mongoid::Attributes::Dynamic
end
There are some issues with dynamic fields so you'll find yourself using #[]/read_attribute and #[]=/write_attribute methods in some cases.

Django: stop foreign key column on ManyToMany table from auto-ordering

I have a ManyToMany relationship between a Group model and a Source model:
class Group(models.Model):
source = models.ManyToManyField('Source', null=True)
class Source(models.Model):
content = models.CharField(max_length=8)
This creates an intermediate table with the columns : id (PK), group_id(FK) and source_id (FK)
Source could look like this:
+----+----------+
| id | content |
+----+----------+
| 1 | A |
| 2 | B |
| 3 | C |
+----+----------+
Each group can have different source member in different orders. For example, group 1 could have sources with 'content' C, A and B with keys of 3,1,2 respectively, and in that specific order.
Group 2 could have sources with 'content' B, C, A with keys of 2,3,1 respectively, and also in that specific order
the table should look like
+----+----------+---------------+
| id | group_id | source_id |
+----+----------+---------------+
| 1 | 1 | 3 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 2 | 1 |
+----+----------+---------------+
The trouble is when I associate these sources in the order I want in a code for loop
sequences = [['C', 'A', 'B'], ['B', 'C', 'A']]
for seq in sequences:
group = models.Group()
group.save()
for letter in seq:
source = models.Source.objects.get(content=letter)
source.group_set.add(group)
It ends up in the table as i.e. re-ordered sequentially in order which is definitely what I do not want as in this case the order of the Sources is essential.
+----+----------+---------------+
| id | group_id | source_id |
+----+----------+---------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 2 | 3 |
+----+----------+---------------+
How can I avoid this column re-ordering in Django?
It's important to understand that in SQL there isn't an inherent ordering to the table; the way the information is stored is opaque to you. Rather, the results of each query are ordered according to some specification that you provide at query time.
It sounds like you want the primary key of the M2M table to do double-duty as the field that defines the ordering. In most use cases that is a bad idea. What if you decide later to switch the order of A and B in group 1? What if you need to insert a new Source in between them? You can't do it, because primary keys are not that flexible.
The usual way to do this is to provide a specific column just for ordering. Unlike the primary key field you can change this at will, allowing you to adjust the order, insert new items, etc. In Django you would do this by explicitly declaring the M2M table (using the through field) and adding an ordering column to it. Something like:
class Group(models.Model):
source = models.ManyToManyField('Source', through='GroupSource')
class Source(models.Model):
content = models.CharField(max_length=8)
class GroupSource(models.Model):
# Also look into using unique_together for this model
group = models.ForeignKey(Group)
source = models.ForeignKey(Source)
position = models.IntegerField()
And your code would change to:
sequences = [['C', 'A', 'B'], ['B', 'C', 'A']]
for seq in sequences:
group = models.Group()
group.save()
for position, letter in enumerate(seq):
source = models.Source.objects.get(content=letter)
GroupSource.objects.create(group=group, source=source, position=position)
Thanks for taking the time and effort, and I probably would have gone down the route of doing much the same by adding another field to represent the ordering. But if you can safely get the same thing for free, why bother? These were individual inserts whose order of insertion is important. What puzzled me most later was some tests I have just concluded.
I managed to get the foreign keys still ordered the way I put them in by using sql-connector on a test db with the same schema relationships between the tables. There the keys in the intermediary table holding keys to each of the ManyToMany partners do not re-organise from lowest to highest. However, the exact same code unfortunately still did on the problematic database. Hence it was not a Django thing as such.
The only real difference between the functioning and non-functioning tables was the UNIQUE attribute pointing to the ManyToMany parters i.e foreign keys to Group and Source. After removing them, the problem went away.
However, to be honest, I am not sure why. Or why Django put those UNIQUE attributes there in the first place. Not sure either whether removing them will badly affect the application going forward.

Django: duplicates when filtering on many to many field

I've got the following models in my Django app:
class Book(models.Model):
name = models.CharField(max_length=100)
keywords = models.ManyToManyField('Keyword')
class Keyword(models.Model)
name = models.CharField(max_length=100)
I've got the following keywords saved:
science-fiction
fiction
history
science
astronomy
On my site a user can filter books by keyword, by visiting /keyword-slug/. The keyword_slug variable is passed to a function in my views, which filters Books by keyword as follows:
def get_books_by_keyword(keyword_slug):
books = Book.objects.all()
keywords = keyword_slug.split('-')
for k in keywords:
books = books.filter(keywords__name__icontains=k)
This works for the most part, however whenever I filter with a keyword that contains a string that appears more than once in the keywords table (e.g. science-fiction and fiction), then I get the same book appear more than once in the resulting QuerySet.
I know I can add distinct to only return unique books, but I'm wondering why I'm getting duplicates to begin with, and really want to understand why this works the way it does. Since I'm only calling filter() on successfully filtered QuerySets, how does the duplicate book get added to the results?
The 2 models in your example are represented with 3 tables: book, keyword and book_keyword relation table to manage M2M field.
When you use keywords__name in filter call Django is using SQL JOIN to merge all 3 tables. This allows you to filter objects in 1st table by values from another table.
The SQL will be like this:
SELECT `book`.`id`,
`book`.`name`
FROM `book`
INNER JOIN `book_keyword` ON (`book`.`id` = `book_keyword`.`book_id`)
INNER JOIN `keyword` ON (`book_keyword`.`keyword_id` = `keyword`.`id`)
WHERE (`keyword`.`name` LIKE %fiction%)
After JOIN your data looks like
| Book Table | Relation table | Keyword table |
|---------------------|------------------------------------|------------------------------|
| Book ID | Book name | relation_book_id | relation_key_id | Keyword ID | Keyword name |
|---------|-----------|------------------|-----------------|------------|-----------------|
| 1 | Book 1 | 1 | 1 | 1 | Science-fiction |
| 1 | Book 1 | 1 | 2 | 2 | Fiction |
| 2 | Book 2 | 2 | 2 | 2 | Fiction |
Then when data is loaded from DB into Python you only receive data from book table. As you can see the Book 1 is duplicated there
This is how Many-to-many relation and JOIN works
Direct quote from the Docs: https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
Successive filter() calls further restrict the
set of objects, but for multi-valued relations, they apply to any
object linked to the primary model, not necessarily those objects that
were selected by an earlier filter() call.
In your case, because keywords is a multi-valued relation, your chain of .filter() calls filters based only on the original model and not on the previous queryset.