How to map entity associations with doctrine 2 to retrieve specific data? - symfony

Introduction:
The title of the question is a bit generic because I was not able to make a more specific one, but now I will try to give a better description of my problem.
It's my first big project with Symfony (>=3.*) and Doctrine ORM (>=2.5) and I hope to get some tips about what to keep in mind to improve my understanding about modelling entity associations.
Minimized Use Case (ps: CodeStyled words are Doctrine Entities):
I have the AccountType entity where are defined 4 account types.
A User can register his credentials and must choose one AccountType.
I have 5 profile types in the relative entities ProfileOne, ProfileTwo, ProfileThree, ProfileFour, ProfileFive.
The User with AccountType:A can create only 1 ProfileOne and only 1 ProfileTwo.
The User with AccountType:B can create unlimited ProfileOne and ProfileTwo.
The User with AccountType:C can create unlimited ProfileFour.
The User with AccountType:D can create only 1 ProfileFive.
Actual Entity Associations:
User have a unidirectional OneToOne with AccountType.
The Question (UPDATED):
I'm forced to manage the logic outside (es: in a repository) or exist a way to map entities to retrieve the right data based on the AccountType (as showed in the use-case)?
Maybe I've to create a ProfileAccountA, ProfileAccountB, ProfileAccountC and a ProfileAccountD, where to store the relative associations based on the AccountType to then be able to have something like $profile = $user->getProfile() where inside the function getProfile() I manage the logic to returns the right data (like in a Factory class)? If Yes, is this a common and valid approach or there are better alternatives for this use-case?

Create a class for each account type (eg.: AccountTypeA, AccountTypeB, etc.). The properties, associations, and rules that tell which and how many profiles a User can have should be encapsulated in those classes.
Your associations will look like this: User has an (oneToOne) Account that has one or more (oneToMany) Profile
You will probably want an AccountInterface:
interface AccountInterface
{
public function getProfiles(): Collection;
}
An example of an Account class. It's better naming them accordingly to their nature (FreeAccount, MasterAccount...):
class MasterAccount implements AccountInterface
{
private $masterProfile;
private $expirationDate;
private $anotherAccountRelatedProperty;
public function getProfiles(): Collection
{
return new ArrayCollection([$this->masterProfile]);
}
}
Any property, association, or behavior related to the account should live in these classes.
In order to get the User profiles it should delegate to the account:
//User class
private $account;
public function getProfiles(): Collection
{
return $this->account->getProfiles();
}
This is a decent OOP approach to be used as guideline in your situation.

Related

.NET Core 2.1 Identity : Creating a table for each Role + bridge M:M table

I'm having issues in figuring out the best design that fits my needs regarding a Role based authorizations using Identity in a .NET Core 2.1 project.
I already extended the User class from Identity with an ApplicationUser class.
I need 5 different roles to control the access to the different features of the app :
Admin, Teacher, Student, Parent and Supervisor
All the common attributes are kept in User and ApplicationUser but I still require different relationships to other tables depending of the User's Role.
User in Role Teacher is linked to 1-N School
User in Role Student is linked to 1-N GroupOfStudents (but not to a School directly)
User in Role Parent is linked to 1-N Student (but not to a School)
...
The other requirement is that a User must be able to be in 1-N Role.
What would be the best practice in my case?
Is there something I'm missing in the features of Identity?
My idea at first was to use nullable FK, but as the number of role increased, it doesn't look like a good idea to have so many empty fields for all those records.
I was thinking of using a "bridge table" to link a User to other tables for each role.
Have a many-to-many relationship between ApplicationUser and the bridge table nd a 0-1 relationship between the bridge table and individual tables for each role. But that's not really helping either since every record will produce the same amount of empty fields.
I'm fairly new with .NET Core and especially Identity, I'm probably missing some keywords to make an effective research because it looks to me that it's a really basic system (nothing really fancy in the requirements).
Thanks for reading !
EDIT :
I don't really have a error right now since I'm trying to figure out the best practice before going deeper in the project. Since it's the first time I face that kind of requirement, I'm trying to find documentation on what are the pros/cons.
I followed Marco's idea and used inheritance for my role based models as it was my first idea. I hope it will help understand my concern.
public class ApplicationUser : IdentityUser
{
public string CustomTag { get; set; }
public string CustomTagBis { get; set; }
}
public class Teacher : ApplicationUser
{
public string TeacherIdentificationNumber { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Student : ApplicationUser
{
public ICollection<StudentGroup> Groups { get; set; }
}
public class Parent : ApplicationUser
{
public ICollection<Student> Children { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public string Category { get; set; }
}
public class StudentGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
This creates the database having one big table for the User containing all the attributes :
User table generated
I can use this and it will work.
A user can have any of those nullable fields filled if he requires to be in different role.
My concern is that for each record I will have a huge number of "inappropriate fields" that will remain empty.
Let's say that on 1000 users 80% of the users are Students.
What are the consequences of having 800 lines containing :
- an empty ParentId FK
- an empty TeacherIdentificationNumber
And this is just a small piece of the content of the models.
It doesn't "feel" right, am I wrong?
Isn't there a better way to design the entities so that the table User only contains the common attributes to all users (as it is supposed to?) and still be able to link each user to another table that will link the User to 1-N tables Teacher/Student/Parent/... table?
Diagram of the Table-Per-Hierarchy approach
EDIT 2:
Using the answer of Marco, I tried to use the Table-Per-Type approach.
When modifying my context to implement the Table-Per-Type approach, I encountered this error when I wanted to add a migration :
"The entity type 'IdentityUserLogin' requires a primary key to be defined."
I believe this happens because I removed :
base.OnModelCreating(builder);
Resulting in having this code :
protected override void OnModelCreating(ModelBuilder builder)
{
//base.OnModelCreating(builder);
builder.Entity<Student>().ToTable("Student");
builder.Entity<Parent>().ToTable("Parent");
builder.Entity<Teacher>().ToTable("Teacher");
}
I believe those identity keys are mapped in the base.OneModelCreating.
But Even if I Uncomment that line, I keep the same result in my database.
After some research, I found this article that helped me go through the process of creating Table-per-type models and apply a migration.
Using that approach, I have a schema that looks like this :
Table-Per-Type approach
Correct me if I'm wrong, but both Techniques fits my requirements and it is more about the preference of design? It doesn't have big consequence in the architecture nor the identity features?
For a third option, I was thinking to use a different approach but I'm not too sure about it.
Does a design like this could fit my requirements and is it valid?
By valid, I mean, it feels weird to link a teacher entity to a Role and not to a User. But in a way, the teacher entity represent the features that a User will need when in the teacher role.
Role to Entities
I'm not yet too sure of how to implement this with EF core and how overriding the IdentityRole class will affect the Identity features. I'm on it but haven't figured it out yet.
I suggest you take advantage of the new features of asp.net core and the new Identity framework. There is a lot of documentation about security.
You can use policy based security, but in your case resource-based security seems more appropriate.
The best approach is to not mix contexts. Keep a seperation of concerns: Identity context (using UserManager) and business context (school, your DbContext).
Because putting the ApplicationUser table in your 'business context' means that you are directly accessing the Identity context. This is not the way you should use Identity. Use the UserManager for IdentityUser related queries.
In order to make it work, instead of inheriting the ApplicationUser table, create a user table in your school context. It is not a copy but a new table. In fact the only thing in common is the UserId field.
Check my answer here for thoughts about a more detailed design.
Move fields like TeacherIdentificationNumber out of the ApplicationUser. You can either add this as claim to the user (AspNetUserClaims table):
new Claim("http://school1.myapp.com/TeacherIdentificationNumber", 123);
or store it in the school context.
Also instead of roles consider to use claims, where you can distinguish the claims by type name (e.g. http://school1.myapp.com/role):
new Claim("http://school1.myapp.com/role", "Teacher");
new Claim("http://school2.myapp.com/role", "Student");
Though I think in your case it may be better to store the information in the school context.
The bottom line, keep the Identity context as is and add tables to the school context instead. You don't have to create two databases, just don't add cross-context relations. The only thing that binds the two is the UserId. But you don't need an actual database relation for that.
Use UserManager, etc. for Identity queries and your school context for your application. When not for authentication you should not use the Identity context.
Now to the design, create one user table that has a matching UserId field to link the current user. Add fields like name, etc only when you want to show this (on report).
Add a table for Student, Teacher, etc. where you use a composite key: School.Id, User.Id. Or add a common Id and use a unique constraint on the combination of School.Id, User.Id.
When a user is present in the table this means that the user is a student at school x or teacher at school y. No need for roles in the Identity context.
With the navigation properties you can easily determine the 'role' and access the fields of that 'role'.
What you do is completely up to your requirements. What you currently have implemented is called Table-Per-Hierarchy. This is the default approach, that Entity Framework does, when discovering its model(s).
An alternative approach would be Table-Per-Type. In this case, Entity Framework would create 4 tables.
The User table
The Student table
The Teacher table
The Parent table
Since all those entities inherit from ApplicationUser the database would generate a FK relationship between them and their parent class.
To implemt this, you need to modify your DbContext:
public class FooContext : DbContext
{
public DbSet<ApplicationUser> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("Students");
modelBuilder.Entity<Parent>().ToTable("Parents");
modelBuilder.Entity<Teacher>().ToTable("Teachers");
}
}
This should be the most normalized approach. There is however a third approach, where you'd end up with 3 tables and the parent ApplicationUser class would be mapped into its concrete implementations. However, I have never implemented this with Asp.Net Identity, so I don't know if it would or will work and if you'd run into some key conflicts.

Symfony 3 fluid entity relations

I am trying to implement a time tracking mechanism in my custom project management app.
This app contains multiple entities (tickets, projects, wiki pages, sprints, ...)
I want my timetracking to be "generic" in the sense that I want users to be able to log time against a ticket, project, wiki page, ...well any entity actually.
Now, I am trying to figure out what database schema (relation) to use for my TimeLog entity.
I could theoretically create a relation to each entity I have in my app, but that will require me to keep updating schema when I introduce new entities later on.
Has anybody every implemented anything like this?
All suggestions are welcomed.
Many thanks in advance.
I faced a similar situation in my app while trying to add comments, likes and other types of elements whose behaviour would not really depend on the entity they are attached to.
The solution I eventually chose was to have two fields in my referring entities (e.g. Comment) to hold both the id of the entity being referred to and its type. Since I was using this multiple times, I put the properties into the following trait:
namespace AppBundle\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait EntityReferenceTrait
{
/**
* #ORM\Column(name="reference_id", type="integer")
*/
private $referenceId;
/**
* #ORM\Column(name="reference_type", type="integer")
*/
private $referenceType;
/* ... setters & getters ... */
}
Then I could use it in the entities holding those kind of references:
/**
* #ORM\Table(name="comments", indexes={#ORM\Index(name="references", columns={"reference_id", "reference_type"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\Comment\CommentRepository")
*/
class Comment
{
/* ... other traits ... */
use \AppBundle\Entity\Traits\EntityReferenceTrait;
/* ... other fields & methods ... */
}
Note: I added an index for the references but it is not necessary for the whole thing to work properly. If you use such an index, beware of the order of your WHERE clauses if you want to benefit from it
In order to improve performance a bit and add additional configurations depending on the type of the entity being referred to, I handled settings directly in the config of my app. Thus, I have something like:
commentables:
news:
classname: AppBundle\Entity\News\News
type_id: 1
browse_route: news_comments
multiple_locales: false
...
This allows me to know precisely what kind of entities my Comment entity can refer. It also allows me to automatically hook specific listeners to the entities being referred to so that the removal of a referred entity triggers the removal of the related comments for example. I do this by processing the configuration in AppBundle/DependencyInjection/AppExtension.php (more about this here) and saving the needed listeners list into a parameter. Then, by adding a listener to the loadClassMetadata event, I can effectively handle the removal of related entities for example.
Here is the listener that hooks the listeners for specific lifecycle events of referred entities by using addEntityListener on the ClassMetadata instance:
namespace AppBundle\Listener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
private $entityListenersMapping = [];
/**
* #param array $mappingConfig Associative array with keys being listeners classnames and values being arrays associating an event to a method name
*/
public function __construct(array $mappingConfig)
{
$this->entityListenersMapping = $mappingConfig;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if(!array_key_exists($classMetadata->name, $this->entityListenersMapping))
{
return;
}
// Hook the entity listeners in the class metadata
foreach($this->entityListenersMapping[$classMetadata->name] as $listenerClassName => $eventsCallbacks)
{
foreach($eventsCallbacks as $event => $methodName)
{
$classMetadata->addEntityListener($event, $listenerClassName, $methodName);
}
}
}
}
Either way, for this part, it mainly depends on the specific needs of your entity but I guess it is quite a common need that these "soft" foreign keys emulate a ON DELETE CASCADE behaviour via preRemove and postRemove events.
Considering the handling of those references and the entities owning them, I also created a EntityRefererManagerTrait to easily create services that manage those entities so that the other components interacting with them would not have to worry about the underlying configuration.
The interface of most public methods of those managers thus usually require:
the classname of the entity being referred to
the numeric id of the entity being referred to
With those two info and the configuration retrieved in my manager service, I can easily interact with the database even if, in my case, it stores an integer defined in the configuration as the reference type in place of the classname of the entity being referred to.
Based on this, I can enable comments, likes, votes, subscriptions and so on for any of my app entities (as long as its primary key is a single integer) with just a few more lines in my configuration files. No need to update database schema and with proper lifecycle events listeners being hooked, no worries about orphan entries in the database.
On a side note, it should be mentioned that you won't be able to retrieve referring entities from the inverse side as it won't be a real association. You won't benefit from foreign keys behaviours either. Thus, even if you emulate the ON DELETE CASCADE behaviour by listening to remove events, you won't be able to ensure that there are no orphans in your database if some DELETE operations are performed directly via DQL for example.

Trying to obtain the current logged in user's ID for tracking fields

I am following this really good tutorial to setup a BaseEntity class that will contain 5 fields:
Active, DateCreated, UserCreated, DateModified, UserModified.
All of my entities that need these tracking fields will inherit from this class.
In the small tutorial below he shows me how to override the SaveChanges() method in my dbContext so that these fields will be set properly based on Creation/Updating.
I am trying to figure out how I would store the current logged in user's ID rather than the Name to the UserCreated and UserModified fields
Please let me know if the UserID shouldn't be what I am storing. This is always what I used to do in some of the webforms apps I created back in the day.
Also, what would be the best way to setup Active to always be true when adding new records. Should this be done in the db context also or within my BaseEntity class. I'm thinking I would create a function in the BaseEntity class called Disable() that will change Active=False.
Please view the small tutorial that I am using
You could create a custom implementation of IIdentity/IPrincipal which contains the UserID. Then you can retrieve this value from the Context or Thread and cast it to the correct type.
Set HttpContext.Current.User from Thread.CurrentPrincipal

Which is the best? Roles based App or Table per Type?

I'm recently working on an application that requires 2 user types. Physicians and General Users. They share some attributes (for example Username, Password, FirstName, LastName, Gender, etc).
But Physicians also need some other attributes that General Users does not have it. Fore example Physicians may have Speciality and WorkingPlace. I found that I should use Table Per Type Inheritance (TPT) in Entity Framework. I created a abstract base class named People, also I created two subclass named (RegisteredUser and Phisycian) inherit from People abstract class.
I Also found that the default Membership Provider in ASP.NET is not suitable for my needs. So I developed a CustomMembershipProvider class that can now work with Table per Type inheritance in Entity Framework.
But I think that it can be very better that I use Roles instead of Table Per Type inheritance. So I want to change my application for using Roles. But the problem is that how can I handle different attributes for Phisycians and General Users? I need a solution that can handle multiple profile fields for multiple User roles. For example if a User is in Physician role, he should have Speciality and WorkingPlace attributes.
Can anyone help me?
How about using Simple Membership with Roles?
You can use Custom Attributes for the specific User Types (Physicians, GeneralUsers ) like so.
public class PhysicianProfile : ProfileBase
{
public string WorkingPlace {
get { return this["WorkingPlace"]; }
set { this["WorkingPlace"] = value; }
}
}

ASP.NET EntityFramework 4 data context issues

I'm working on a site and there are two projects in the solution a business logic project and the website project. I understand that I want to keep the entity context out of the web project and only use the business objects the framework creates but I can't figure out how to save a modified object this way.
Let's say my entity model created this class:
public class Person //Person entity
{
Int32 Id {get;set;}
String Name {get;set;}
Address Address {get;set;} //Address entity
}
And I created this class to get a specific person:
public static class PersonController
{
public static Person GetById(int id)
{
using (Entities context = new Entities())
{
return context.Persons.FirstOrDefault(x => x.Id == id);
}
}
}
This allows me to get a person without a context by calling PersonController.GetById(1); and I can change the persons properties after I get them but I can't figure out how to save the modified information back to the database. Ideally I would like to partial class Person and add a .Save() method which would handle creating a context adding the person to it and saving the changes. But when I tried this a while ago there were all kinds of issues with it still being attached to the old context and even if I detatch it and attatch it to a new context it gets attached as EntityState.Unchanged, if I remember right, so when I call context.SaveChages() after attaching it nothing actually gets updated.
I guess I have two questions:
1) Am I going about this in a good way/is there a better way? If I'm doing this in a really terrible way I would appreciate some psudo-code to point me in the right direction; a link to a post explaining how to go about this type of thing would work just as well.
2) Can someone provide some psudo-code for a save method? The save method would also need to handle if an address was attached or removed.
There are many ways to handle Entity Framework as a persistence layer.
For one, it looks like you're not using pure POCOs. That is, you let EF generate the classes for your (in the EDMX.designer.cs file).
Nothing wrong with that, but it does inhibit a clean separation of concerns (especially when it comes to unit testing).
Have you considering implementing the Repository pattern to encapsulate your EF logic? This would be a good way to isolate the logic from your UI.
In terms of Save - this is where it gets difficult. You're right, most people use partial classes. Generally, you would have a base class which exposes a virtual "Save" method, which the partial classes can then override.
I personally don't like this pattern - i believe POCOs should not care about persistence, or the underlying infrastructure. Therefore I like to use pure POCOs (no code gen), Repository pattern and Unit of Work.
The Unit of Work handles the context opening/saving/closing for you.
This is how (my) Unit of Work does the magic. Consider this some code in your "Web" project:
var uOw = new UnitOfWork(); // this is class i created, implementing the UOW pattern
var person = repository.Find(10); // find's a "Person" entity (pure POCO), with id 10.
person.Name = "Scott";
uOw.Commit();
Or adding a new Person:
var uOw = new UnitOfWork();
var newPerson = new Person { Name = "Bob" };
repository.Add(newPerson);
uOw.Commit();
How nice is that? :)
Line 1 creates a new sql context for you.
Line 2 uses that same context to retrieve a single "Person" object, which is a hand-coded POCO (not generated by EF).
Line 3 changes the name of the Person (pure POCO setter).
Line 4 Saves the changes to the data context, and closes the context.
Now, there is a LOT more to these patterns than that, so I suggest you read up on these patterns to see if it suits you.
My repository is also implemented with Generics, so I can re-use this interface for all business entity persistence.
Also take a look at some of the other questions I have asked on Stack Overflow - and you can see how I've implemented these patterns.
Not sure if this is the "answer" you're looking for, but thought I'd give you some alternative options.

Resources