Create a bidirectional ManyToOne relation - data-modeling

Hi I try to create a ManyToOne relation but get stucked.
I have a BILL and a Booking Class
The realation is :
A Bill can have many Bookings. (1:m)
The Bill Class should manage the relationship
A Booking belong only to one Bill. (1:1 A Booking can't exist without a Bill )
If I delete the Bill, all Bookings that belongs to the Bill should be deleted.
If I remove a Booking from the Bill, only this Booking should be deleted.
If I delete a Booking, this Booking should be removed from the Bill.
So far i have this model :
Bill Class
#Entity
public class Bill extends Model{
#OneToMany(cascade={CascadeType.ALL})
public List<Booking> bookings;
public void setBookings(List<Booking> bookings)
{
for (Booking booking : bookings)
{
booking.bill = this;
}
}
}
Booking Class
#Entity
public class Booking extends Model{
#ManyToOne(optional=false )
#Required(message="Bill needed")
public Bill bill;
public void setBill(Bill bill){
this.bill=bill;
bill.bookings.add(this);
}
}
If I delete the Bill, all Bookings belong to the Bill deleted as well.
But I can't delete a single Booking, either from the Bill Side or the Booking side.
What do I miss ?

Your case is the standard parent child case. You need to declare the following:
#Entity
public class Bill extends Model {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, mappedBy="bill")
public List<Booking> bookings = new ArrayList<Booking>();
}
#Entity
public class Booking extends Model {
#ManyToOne public Bill bill;
}
This code contains all necessary declarations. The orphanRemoval ensures that whenever you delete a Booking from Bill (i.e., when that Booking becomes orphaned) it will be deleted from the database. If you delete the Bill, then all its BookingS are deleted as well (via the CascadeType.ALL annotation).
With the above declarations, you can set Bookings by
for(Booking booking : bookings) {
bill.bookings.add(booking);
booking.bill = bill;
}
(you don't need to code setBookings() yourself) but you must make sure that you do the change on both sides.
For removal, if you set a Booking's bill to null and remove the booking from the Bill's bookings:
booking.bill = null;
bill.bookings.remove(booking);
should do it.
If you call
booking.delete();
then also its bookings should be deleted.
Really quite standard....

Related

Spring / Hibernate One to One relationship with extra column

Is it possible to have a one to one relationship in Spring / Hibernate with extra-column? If yes how do we do? I think I have to use a join table to link my two entities together and then add an extra column in this join table but I don't know how.
Here is a code sample that modelizes relationship between an employee and a workstation, it uses #JoinTable so that the relationship is persisted in a dedicated join table. And I would like to add an extra information in this relationship for example the last time the employee used a given workstation, how would I do that?
Employee entity:
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "emp_workstation",
joinColumns = { #JoinColumn(name = "employee_id", referencedColumnName = "id") },
inverseJoinColumns = { #JoinColumn(name = "workstation_id", referencedColumnName = "id") })
private WorkStation workStation;
//... getters and setters
}
WorkStation entity :
#Entity
#Table(name = "workstation")
public class WorkStation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(mappedBy = "workStation")
private Employee employee;
//... getters and setters
}
First of all, may I suggest you to change the relation between Employee and Workstation to create a #ManyToOne .
One-to-one relationships associate one record in one table with a single record in the other table.
Many-to-one relationships associate many record in one table with a single record in the other table.
Then, use #ManyToOne if :
There is many workstation in your database and the user get access to
only one workstation.
An employee can use different workstation (or the same workstation) but can use only one at a time.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "workstation_id",
foreignKey = #ForeignKey(name = "fk_workstation_id")
)
private WorkStation workStation;
According to the question of additional data in a relationship one-to-one should return all datas mapped in your entity.
Then, if you store "the last time the employee used a given workstation", that's mean you may have more then a single record for a given employee in the table "workstation".

laravel 5.4 many to many relationship

i have relationship many to many table 'admins' , 'pjt_roles' with pjt_role_admin.
but,not working
i have 2 model
class Role
protected $table = 'pjt_roles';
public function Admin(){
return $this->belongsToMany(Admin::class',pjt_role_admin');
}
class Admin
public function Role(){
return $this->belongsToMany(Role::class,'pjt_role_admin');
}
and table pjt_role_admin have attribute
admin_id from table admins
role_id from table pjt_roles
Specify your pivot table in relationship. Default laravel assume admin_role as your pivot table because you have Admin and Role models
class Role
protected $table = 'pjt_roles';
public function Admin(){ // should be admins() for better readability
return $this->belongsToMany(Admin::class, 'pjt_role_admin');
}
class Admin
public function Role(){ // should be roles() for better readability
return $this->belongsToMany(Role::class, 'pjt_role_admin');
}
To determine the table name of the
relationship's joining table, Eloquent will join the two related model
names in alphabetical order. However, you are free to override this
convention. You may do so by passing a second argument to the
belongsToMany method.
Fetch Data
$admin = Admin::find(1);
$roles = $admin->Role; // should change to roles() in relationship for better readability
Save
$admin->Role()->attach($roleId);
details https://laravel.com/docs/5.4/eloquent-relationships#many-to-many

How to add a constraint to an EF Code First table

First, I'm not sure if I'm reinventing the wheel with regards to foreign keys here, but lets say I have a Patient table
PatientId
Name
Gender
Age
HospitalId
I want to make sure that when an object is inserted into the Patient table, that it won't insert a record with a HospitalId that doesn't exist in the Hospital table. Is there an efficient way of doing this? Or as I said above am I reinventing a wheel here?
Try this:
public class Patient
{
public int PatientId {get;set;}
public string Name {get;set;}
public string Gender {get;set;}
public int Age {get;set;}
public int HospitalId {get;set;}
//add this line of code
public virtual Hospital Hospital {get;set;}
}
Also you can change Hospital class this way:
public class Hospital
{
//your code....
//new property
public virtual ICollection<Patient> Patients {get;set;}
}

Inheritance Hierarchies in Entity Framework

I'm working on a database for a flying club that has a table for Flights and a table for ClubMembers. Flights, unfortunately, must be paid for by so there is a BillTo that references the ClubMember who must pay.
So far it looks like this...
public ClubMember
{
public int ID{get;set;}
public string FirstName{get;set;}
public string LastName {get;set;}
}
public Flight
{
public int ID {get;set;}
public ClubMember PilotPayingTheBill{get;set;}
public double EnormousPriceToBePaid {get;set;}
}
Pretty simple ... but then the messy world intervenes. Sometimes the plane is flown by a mechanic for maintenance purposes. I don't want to do this the lazy way and enter the mechanic as a dummy record in the ClubMember table. The database is too new for that kind of kludge. Plus, EF has nifty ability to implement inheritance in the database, so I can keep it all nice and tidy like this:
public BillableEntity
{
public int ID{get;set;}
}
ClubMember : BillableEntity
{
public string FirstName{get;set;}
public string LastName {get;set;}
}
NonPayingUser : BillableEntity
{
public string Description {get;set;}
}
public Flight
{
public int ID {get;set;}
public BillableEntity billTo{get;set;}
public double EnormousPriceToBePaid {get;set;}
}
With a few instructions in my fluent configuration the NonPayingUser and ClubMembers are all put in their own tables, with ID as a primary key and foreign key - a nice, concise design I was very happy with. The Billto_id column in the flights table is not null so every flight will always have a billableEntity, which will be either a clubMember or NonPayingUser.
Writing the query in TSQL is pretty easy
select coalesce(cm.FirstName + ' ' + cm.LastName,np.Description) as BillTo
from Flights f
left outer join ClubMembers cm on f.billto_id = cm.ID
left outer join NonPayingUsers np on f.billto_id = np.ID
But doing the same thing in EF has me stumped.
The Flight class has a BillTO property which is the parent class of BillableEntity. I can cast it to the descendant classes like this...
var flights = DB.Flights
.Select(f => new
{
PersonName = (f.BillTo as ClubMember).FirstName + PersonName = (f.BillTo as ClubMember).LastName
,
OtherName = (f.BillTo as NonPayingUser).Description
});
but this produces monstrous amounts of TSQL.
One solution is just to write my own stored procs to join these tables together and use the EF classes to do all the basic CRUD on the individual tables, and that's the direction I'm leaning in. But is there a better way?
This is a duplicate of several similar questions...
EF (6, I'm looking at 7 RC1 now) always generates a huge amount of TSQL.
I think that the best answer is what is the problem of "producing monstrous amounts of TSQL"?
If you have a particular issue with a query solve that issue otherwise ignore it. You will see that in a huge percentage of cases it does not causes problem the DBMS.
I ended up creating a calculated field in the BillableEntities table
alter table BillableEntities drop column name;
alter table BillableEntities add Name as (case when [Description] is null then LastName + ', ' + FirstName else [Description] end)
and then marked it as DataBaseGenerated in the BIllableEntities class.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string FullName { get; private set; }
This way the SQL produced by EF is much more manageable. Performance is good, data integrity is good too.

ASP MVC 5 EF 6 - Insert into Multiple Tables

I have an existing database, from which I have built a shell web-app using VS2013 and EF6, but I have run into a few problems.
My database has the following tables, for example:
Table: Customer (Id, First, Last, Date)
Table: Assets (Id, CustID)
Table: Orders (Id, AssetId, CustID)
When the EF created the shell webapp for me (which has awesome) it gave me the following method, for example, to create a new Customer:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Id, First, Last, Date")] Customer customer)
....
return View(customer)
In my database I have a one Customer to many orders and assets, but I don't understand how to use this relationship during a Create operation, or any other.
My question -- How do I "Create" a new Customer when this operation needs to span multiple tables? For example, when the "Create Customer" form is filled out, you would add one or more Assets and/or Orders.
Should I use a stored procedure to do multiple inserts across three different tables? Or can I make a change to the database that will let me use all the EF magic?
Thanks for your help!
You can use a view model that contains all three classes.
public class CreateCustomerViewModel
{
public Customer Customer { get; set; }
public ICollection<Asset> Assets { get; set; }
public ICollection<Order> Orders { get; set; }
}
Your [HttpGet] action method will pass this view model as the model instead of a Customer.
[HttpGet]
public ActionResult Create()
{
CreateCustomerViewModel model = new CreateCustomerViewModel();
return View(model);
}
Your [HttpPost] action method will take the CreateCustomerViewModel as a parameter:
[HttpPost]
public ActionResult Create(CreateCustomerViewModel model)
{
// Create the Customer with the necessary Assets and Orders and save
}
If you're classes and relationships are configured properly, adding the appropriate Assets and Orders to the navigation properties on your Customer entity should trigger EF to automatically insert the assets and orders into the appropriate tables when you insert the Customer.

Resources