Maintaining referential integrity - ddl

Given the schema:
MACHINE_TYPE { machine_type }
MACHINE { machine, machine_type }
SORT_PLAN { sort_plan, machine_type }
SCHEDULE { day_of_week, machine, sort_plan }
and the business rule:
A sort plan can be assigned to any
machine of the same machine_type.
How do I enforce that, in SCHEDULE, the tuples referenced by machine and sort_plan have the same machine_type?
The schema can be changed, if necessary.

I'd use an insert trigger on the SCHEDULE table.

You could change the plan table so it does not have MachineType, and add a new table called machinePlan, that has a row for every machine that can use that plan, with the MachineId and the PlanId. Then derive MachineType for a plan from this new table's parent machine table instead of from the plan table itself.
Last, change the schedule table so that it's FK is back to this new MachinePlan table, instead of as you currently have it
MACHINE_TYPE { machine_type }
MACHINE { machine, machine_type }
SORT_PLAN { sort_plan}
MACHINE_SORTPLAN {machine, sort_plan }
SCHEDULE { day_of_week, machine_Sortplan }
This also has added benefit in that you are NOT irevocably typing the rules for a plan on which machine type they apply to. You are keeping this association separately, and can, if necessary, decide to use the same set of rules (the same plan, for machines of more than one machine type...

Related

Spring JPA and GraphQL

Given the following graph
type Movie {
name: String!
actors: [Actor!]!
}
type Actor {
name: String!
awards: [Award!]!
}
type Award {
name: String!
date: String!
}
type Query {
movies(): [Movie!]!
}
I'd like to be able to run the following three types of queries as efficiently as possible:
Query 1:
query {
movies {
actors {
rewards {
name
}
}
}
}
Query 2:
query {
movies {
name
}
}
Query 3:
query {
movies {
name
actors {
rewards {
date
}
}
}
}
Please note, these are not the only queries I will be running, but I'd like my code to be able to pick the optimal path "automatically".
The rest of my business logic is using JPA. The data comes from three respective tables, that can have up to 40 columns each.
I am not looking for code examples, but rather for a high-level structure describing different elements of architecture with respective responsibilities.
Without further context and details of your DB schema, what I could do is to just to give you the general advice that you need to aware of.
Most probably you would encounter N+1 loading performance issue when executing a query that contains several levels of related objects and these objects are stored in different DB tables.
Generally there are 2 ways to solve it :
Use Dataloader . Its idea is to defer the actual loading time of each object to a moment that multiple objects can be batched loaded together by a single SQL. It also provides the caching feature to further improve the loading performance for the same query request.
Use "look ahead pattern" (Refer this for an example). Its ideas is that when you resolve the parent object , you can look ahead to analyse the GraphQL query that you need to execute require to include others related children or not. If yes , you can then use the JOIN SQL to query the parent object together with their children such that when you resolve its children later , they are already fetched and you do not need to fetch them again.
Also, if the objects in your domain can contain infinity number in theory , you should consider to implement pagination behaviour for the query in order to restrict the maximum number of the objects that it can return.

Data normalization in GraphQL query

I'm using GraphQL to query a database that has two data types: User and Group.
Groups have a field users which is an array of User objects which are in that group. I have one field at root named groups which returns an array of all of my groups.
A typical query might look something like this:
{
groups {
id,
name,
users {
id,
name,
address,
email,
phone,
attitude,
job,
favoriteQuote,
favoriteColor,
birthday
}
}
}
The problem is that a lot of those users can belong to multiple groups and, seeing as User has a lot of fields, this can make responses quite large.
Is there any way to get one set of fields for the first instance of an object, and a different set for every other instance in the response?
I only need name, job, email etc etc once per user in the response, and just the id thereafter (I can do my own normalization afterwards).
alternatively
Is there any way to only get id fields for all users in groups and return a separate array of all unique User objects that have been referenced in the query (which is not all User objects)?
Is there any way to get one set of fields for the first instance of an object, and a different set for every other instance in the response?
No. The same set of fields will be returned for each item in a list unless the type of the individual item is different, since a separate selection set can be specified for each type returned at runtime.
Is there any way to only get id fields for all users in groups and return a separate array of all unique User objects that have been referenced in the query (which is not all User objects)?
You could design your schema to accommodate this. Something like
{
groups {
nodes {
id
name
users {
id
}
}
uniqueUsers {
id
# other fields
}
}
}
Your groups resolver would need to handle all the normalization and return the data in the appropriate shape. However, a simpler solution might be to just invert your relationship:
{
users {
id
name
address
email
phone
attitude
job
favoriteQuote
favoriteColor
birthday
groups {
id
name
}
}
}
Generally - usually
... normalization ... of course ... f.e. using apollo and it's normalized cache.
All records returned from API has to be the same shape.
You can get data and render some <MembersList/> component using query for ids and names only (full/paginated).
Later you can render details in some <UserProfile/> component with own query (hook useQuery inside) to fetch additional data from cache/api (controllable).
Your specific requirements - possible
1st option:
Usually response is of one common shape (as requested), but you can decide on resolver level what to return. This requires query structure changes that allows (API, backend) to null-ify some properties. F.e.
group {
id
name
users {
id
name
profile {
photo
email
address
With profile custom json type ... you can construct users resolver to return full data only for 1st record and null for all following users.
2nd option:
You can use 2 slightly different queries in one request. Use aliases (see docs), in short:
groupWithFullMember: group ( groupId:xxx, limitUsers:1 ) {
id
name
users {
id
name
address
email
...
}
}
groupMembers: group ( groupId:xxx ) {
id
name // not required
users {
id
name
}
}
Group resolver can return it's child users ... or users resolver can access limitUsers param to limit response/modify db query.

GraphQL - Cannot update a table row

Say I have a table Person with attributes id and name. The GraphQL server is all setup by Postgraphile and working as I can query and create new entry. However, I could not update it. Scratching my head over and over again and I am still unable to find out the cause for this.
This is the mutation I tried that failed me every now and then.
mutation($id: Int!, $patch: PersonPatch!) {
updatePersonById(input: { id: $id, patch: $patch }) {
clientMutationId
}
}
The variables
{
id: 1,
patch: {"name": "newname"}
}
I was using Altair GraphQL client to submit the mutation request and the error message returned was "No values were updated in collection 'people' because no values were found."
The person of id = 1 does exist, confirmed by sending a query personById over to get his name. But I just couldn't update his name.
Edit #1
Below is the gql generated by Altair GraphQL Client
updatePersonById(
input: UpdatePersonByIdInput!
): UpdatePersonPayload
input UpdatePersonByIdInput {
clientMutationId: String
patch: PersonPatch!
id: Int!
}
input PersonPatch {
id: Int
name: String
}
Assuming you're using row-level security (RLS) it sounds like the row to be updated does not pass the required security policies for the currently authenticated user.
Here's a small example; you'll want to adjust it to fit your own permissions system
create table person (id serial primary key, name text);
alter table person enable row level security;
grant select, insert(name), update(name), delete on person to graphql;
create policy select_all on person for select using (true);
create policy insert_all on person for insert with check(true);
create policy update_self on person for update using (id = current_person_id());
create policy delete_self on person for delete using (id = current_person_id());
where
create function current_person_id() returns int as $$
select nullif(current_setting('jwt.claims.person_id', true), '')::int;
$$ language sql stable;
If you need more guidance, feel free to drop into the Graphile chat.

Entity Framework saves duplicates of one side in a one to many

I am having a problem with Entity Framework in my MVC 3 application. I have a users table, which is only ever populated with a new user row when a machine entity is created by a user that hasn't created any machines before, i.e. it only creates users it hasn't seen before. Each user belongs to a sector (division of the company) which also must be set before the user and the machine are saved. I have a default sector that new users are assigned to (so that this may be changed later on).
I have some code in my machine controller class for the creation of new machines that looks like this:
[HttpPost]
public ActionResult Create(Machine machine)
{
if (ModelState.IsValid)
{
// work out if the user exists in the database already
var users = userRepository.All.Where(u => u.Username == machine.User.Username);
if (users.Count() == 0)
{
// if the user entry doesn't exist we have to create it assigning a default sector
Sector defaultSector = null;
var defaultSectors = sectorRepository.All.Where(s => s.IsDefaultForNewUsers);
if (defaultSectors.Count() == 0)
{
// jebus! no default sector, so create one
defaultSector = new Sector() { Name = "Default", IsDefaultForNewUsers = true };
sectorRepository.InsertOrUpdate(defaultSector);
sectorRepository.Save();
}
else
{
defaultSector = defaultSectors.First();
}
machine.User.Sector = defaultSector;
}
else
{
machine.User = users.First();
}
machineRepository.InsertOrUpdate(machine);
machineRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleInstalledOS = installedosRepository.All;
ViewBag.PossibleLicenceTypes = licencetypeRepository.All;
ViewBag.PossibleUsers = userRepository.All;
return View();
}
}
[Edit] Here is the body of the InsertOrUpdate method from my Machine repository:
public void InsertOrUpdate(Machine machine)
{
if (machine.MachineId == default(int)) {
// New entity
context.Machines.Add(machine);
} else {
// Existing entity
context.Entry(machine).State = EntityState.Modified;
}
}
The problem I'm having with this code is that when I save the machine, it keeps creating a new user even though that user is already in the system. The line that finds the user works and retrieves the user as I would expect but entity framework doesn't seem to understand that I wish to use this user that I've found and not create a new one. So at the moment I have multiple identical users (except ID of course) in my users table. I want a one to many here so that multiple machines are owned by the same user.
Does anyone have any idea how I force entity framework to respect that there is already a user there that I want to tie the new machine to?
You didn't post the code for your InsertOrUpdate method but I suspect that that is where the problem is. I bet in that method at some point you do something equivalent to:
context.Machines.Add(machine);
When you call DbSet.Add (or change the state of an entity to Added) you are actually adding the whole graph to the context. This process will stop when it encounters an object that is being tracked by the context. So if you have a machine object that references a user object and neither of these objects are being tracked by the context, then both the machine object and the user object will be added to the context and end up in an Added state. EF will then insert them both as new rows in the database.
What you need to do, which was alluded to in the other answer, is make sure than EF knows that an existing user object does exist in the database by making sure it's state is Unchanged (or possibly Modified) and not Added when you save.
There are various ways that you could accomplish this and it's hard to know which is best for you without seeing more of how your app and repository work. One way is to make sure that the context used to query for the user is the same context as is used to save. This way EF will already be tracking the existing user object and will know not to add it when you call Add.
Another way is to let your repository know somehow whether or not the user object is new. Often people use the primary key to determine this--a zero key indicates a new object, non-zero indicates an existing object. You could also pass a flag into your repository.
You can then call Add to add the graph, but then set the state of the User object to Unchanged (or Modified if it might have been changed since it was queried) if it is an existing user. This will prevent EF from inserting a new user into the database.
Can you double check that your repositories are using the same data context? If not, you are essentially adding a new User entity to the machineRepository. Alternatively you could attach the user to the context for the machine repository, but you'll likely keep running into bugs like this.

Can't perform Create, Update or Delete operations on Table because it has no primary key

I've been trying to insert row in the table having an identity column RequestID (which is primary key as well)
HelpdeskLog logEntry = new HelpdeskLog { RequestBody = message.Body };
if (attachment != null)
logEntry.Attachments = Helper.StreamToByteArray(attachment.ContentStream);
Database.HelpdeskLogs.InsertOnSubmit(logEntry);
But my code inevitably throws following error
Can't perform Create, Update or Delete operations on Table because it has no primary key.
despite primary key column exists indeed
That's what I tried to do:
To look in debugger the value of identity column being inserted in object model. It is 0
To insert manually (with SQL) fake values into table - works fine, identity values generated as expected
To assure if SQLMetal has generated table map correctly . All OK, primary key attribute is generated properly
Nevertheless, neither of approaches helped. What's the trick, does anybody know?
I've also had this problem come up in my C# code, and realized I'd forgotten the IsPrimaryKey designation:
[Table (Name = "MySessionEntries" )]
public class SessionEntry
{
[Column(IsPrimaryKey=true)] // <---- like this
public Guid SessionId { get; set; }
[Column]
public Guid UserId { get; set; }
[Column]
public DateTime Created { get; set; }
[Column]
public DateTime LastAccess { get; set; }
}
this is needed even if your database table (MySessionEntries, in this case) already has a primary key defined, since Linq doesn't automagically find that fact out unless you've used the linq2sql tools to pull your database definitions into visual studio.
LINQ does not allow to insert data into table without primary key. To achieve the insert data with table without primary key you can either use store procedure or create a query and execute using LINQ. Below link provide good explanation of the same.
Can't perform Create, Update or Delete operations on Table(Employee) because it has no primary key
Delete the table and then reinsert it. You must make sure there is a little small key next to the field before you do this. Recompile your project and all should be fine.
Just because you updated the dabase does not mean the DBML file somehow automatically updated. It does not, sorry.
As the the table has the primary key in SQL Server, re-addthe table in the linq2sql designer.
If that were not the case, you can configure which properties are part of the primary key by hand on the designer.

Resources