I'm really confused about this. I just can't find a guide that is designed for noobs or dummies to understand, all i'm getting are advanced or atleast learned technical stuff that i forgot. (not really big into database when i was still learning)
So i posted an image of a rough outline i made, i've been at it for hours but my undecisiveness isnt going anywhere, i double guess myself everytime i feel like im on the right track.
In any case, how should i design the tables?
Say for example there's 3 types of account. 1 is for normal, 2 is for trainers and 3 is for gymowners.
I use the accounts table to get the access level of accounts that are logging in. So i'm really confused about this. I'm pretty sure all tables need to have a primary key. so i decided to each have id's on all table.
Like userid, trainerid, gymid.
So how do i FK them onto the account's table? do i add all 3 as an FK? what if it was a normal user then the trainerid(FK) and gymid(FK) would be empty, is that even acceptable for an FK to be null?
The accounts table also have an accountid(PK) not even sure if it is useful at all or since i only need to check the accesslevel col of the accounts table(i'll probably know when i'm deeply involved in it later for now i just cant see the use of a PK on the accounts table except the fact that you need an PK for every table).
So i'm thinking, should i just use the username as the foreign key? but can normal unique cols be foreign key? or do they need to be set to primary keys?
Also, additional question, in regards to the 3 types of accounts, they all basically have a profile, should i make another table that connects to them named profile?(one for each user type ofcourse like user_profile, trainer_profile, gym_profile).
Maybe one of the problems is that you immediately start to think in tables and IDs and Foreign Keys and so on. It is usually much easier if you think in objects and their relation towards each other.
It is a bit difficult to read your picture. From the text I gather you have the notion of an account, and apparently there are three types of accounts: normal, trainers and gym owners. Is there a difference between those accounts? Do gym owners have properties that normal users don't have? or is it just some kind of identification.
From your drawing it seems that a normal user has a user name, a password and an access level. Do trainers also have these properties?
If so, then in normal object oriented design, you would say it is an aggregation: trainers, normal people and gym owners all HAVE a user name, a password and an access level. aggregation is also quite often called composition. There is a small difference, but for this discussion it is not important.
Instead of aggregation/composition (the object HAs an account) you could also think of inheritance: the object IS an account: trainer accounts, gym owner accounts and normal accounts are objects that are all different kinds of Accounts, with a user name, a password and an access level.
In this example there is not a big difference between aggregation (a trainer has an account), or inheritance (a trainer account is an account). Usually the advise is to favor aggregation over inheritance enter link description here
Be aware: if the only difference between your three accounts is a value of a property, then consider making them all the same type, with a property AccountType which gives you information on whether it is a normal account, a gym owners account or a trainer account.
If the only difference between those three is the access level, then don't create an account type. The access level would serve as account type.
So far, I'm only talking object oriented design. Once you've designed your objects you might think of putting them in a database.
Let's assume a trainer, a gym owner and normal people really are different things, with different properties. Your class would look similar to:
public enum AccessLevel
{
None,
...,
FullAccess,
}
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public .. some trainer properties {get; set;}
// a trainer has an account
public Account Account {get; set;}
}
public class GymOwner
{
public ... some gym owner properties {get; set;}
public Account Account {get; set;}
}
If your design would be like this, you'd see that at least you'd have a gym owner table and a trainer table. But what to do with the Account.
One solution would be to put an account in a separate table and add something to the trainer and the gym owner, so that if you have a trainer you know which item in the account table belongs to this specific trainer.
This method is usually not used in this design. If object A and object B have a one to one relation: one A belongs to one B, and one B belongs to one A, then in fact they can be put into one table. If you use entity framework and you had defined the classes above, the account properties would be added as columns to the trainer table and as columns to the gym owner table. The advantage is that fetching the information about a trainer is faster, as this would involve accessing only one table.
About Primary Keys. Every element in every table should have exactly one primary key. This is the property that identifies the object. If you have the key, you have very fast access to all other properties. To make it easier for you to understand the role of Account I've left out the Id in my original code.
But now that we've decided that it would be best to let Trainers and Gym owners HAVE an account, there would be two tabled: Trainers and GymOwners. Those are the only elements that have a primary key. The classes would be like:
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public Account Account {get; set;}
}
public class GymOwner
{
public int Id {get; set;}
public ... some gym owner properties {get; set;}
public Account Account {get; set;}
}
Note that there is no table for Account, so Account does not need a key.
So when do you need a foreign key
If a Trainer would not have one account, but several accounts, maybe a lot of accounts, usually called a collection of accounts, then we can't save the accounts as columns inside the Trainer table anymore. We will have to put all accounts of the Trainer in a separate table and tell each account to which trainer it belongs. The account gets a foreign key to the primary key of the trainer to whom he belongs.
In database terms, this is called a one-to-many relation: one trainer has many accounts.
For entity framework the classes would be like:
public class Account
{
public int Id {Get; set;}
public int TrainerId {get; set;}
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public virtual ICollection<Account> Accounts {get; set;}
}
public class MyDbContext : DbContext
{
public DbSet<Trainer> Trainers {get; set;}
public DbSet<Account> Accounts {get; set;}
}
Note that because Account has its own table it has gotten a primary key.
Beside it has also gotten a foreign key in property TrainerId.
I've also added a class derived from DbContext to access the tables. Until know I have only tables with Trainers and tables with Accounts. Each table is called a DbSet, the type of DbSet informs entity framework about the columns in the table.
Because Trainer has a virtual ICollection of Accounts, entity framework knows that there is a one-to-many relation between Trainer and Account, and because of the namer TrainerId, entity framework knows that TrainerId is the foreign key to the primary key of the Trainer that the account belongs to.
So if you have the id of a trainer, you get all accounts of this trainer using the following Linq statements:
int trainerId = GetMyTrainerId();
IEnumerable<Account> accountsOfTrainer = dbContext.Accounts
.Where(account => account.TrainerId == trainerId);
From the collection of Accounts, take all records where property TrainerId equals trainerId
So now you know how to design a primary key and let a foreign key point to it.
But what about gym owners? If a gym owner only has one account, just let it HAVE the one account (composition). But what if your gym owner also has a collection of accounts.
If you just add the gym owner accounts to the accounts table, then you get in trouble. To which Id does the foreign key point? To primary key in the Trainer table or in the Gym Owners table?
The safest way would be to create GymOwnersAccount and a TrainersAccount. They will each have their own table with a foreign key. A GymOwnersAccount will have a foreign key to the GymOwners table and a TrainersAccount will have a foreign key to the trainers table.
Here you could also decide to let the GymOwners account HAVE an account, but it seems more natural to say the a GymOwners account IS a special type of Account, and thus derives from Account.
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class GymOwnerAccount : Account
{
public int Id {get; set;}
public int GymOwnerId {get; set;}
}
public class TrainerAccount : Account
{
public int Id {get; set;}
public int TrainerId {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public virtual ICollection<Account> Accounts {get; set;}
}
public class GymOwner
{
public int Id {get; set;}
public ... some gym owner properties {get; set;}
public virtual ICollection<Account> Accounts {get; set;}
}
public class MyDbContext : DbContext
{
public DbSet<Trainer> Trainers {get; set;}
public DbSet<Account> GymOwnerAccounts {get; set;}
public DbSet<Account> TrainerAccounts {get; set;}
}
There are other solutions possible, for instance give each account two foreign keys, one to gym owners and one to trainers, where you always need to set one foreign key to 0. It's easy to see that this might lead to maintenance problems, while it doesn't give you any benefits, so I would advice to stick with separate tables for GymOwnerAccounts and TrainerAccounts.
Now that I've entered the realm of inheritance in database there is a wealth of items you can configure. An article that helped me a lot to understand entity framework, and how classes, inheritance, composition is transferred to databases is Entity Framework Code First
Related
My models … (fyi “Application" as in a job application and “Administrator” as in a person who has been assigned responsibility for this application)
public class Application
{
public int Id { get; set; }
public string Title { get; set; }
public ICollection<Administrator> Administrators { get; set; }
}
public class Administrator
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Application> Applications { get; set; }
}
Notice many-to-any relationship between applications and administrators.
Its an intranet application and the user (who will also be an administrator of one or more applications) will be identified using windows authentication. I want to return JSON, only for the applications for which the user is an administrator but also include all of the administrators for those applications. For example, John created a new application titled “Nurse Practitioner for Pediatric Clinic" which makes John automatically an administrator for that application but John also assigned Betty as an administrator for that application. Betty is not an administrator for any other applications. If Betty log in, she should get the Application object titled "Nurse Practitioner for Pediatric Clinic” and that application’s 2 administrators (John and Betty).
This works but includes all applications not limited to Betty’s ...
context.Applications.Include("Administrators").ToList();
These work but obviously is not what I want ...
context.Administrators.Include("Applications").ToList();
context.Applications.Include("Administrators").Where(a => a.Id.Equals(1)).ToList();
context.Administrators.ToList();
context.Administrators.Find(1);
This does not work …
context.Applications.Include("Administrators").Where(a => a.Administrators.Contains(context.Administrators.First())).ToList();
Get ...
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The method 'First' can only be used as a final query operation. Consider using the method 'FirstOrDefault' in this instance instead.
And this does not work …
context.Administrators.Find(1).Applications.ToList();
Get ...
An exception of type 'System.ArgumentNullException' occurred in System.Core.dll but was not handled in user code
Additional information: Value cannot be null.
Although I know that the Administrator with Id=1 has 2 Applications.
This works …
(from app in context.Applications
from admin in app.Administrators
where admin.Username == “John"
select app).ToList();
But it doesn’t include the Administrators and if I add an Include() clause like this …
(from app in context.Applications
from admin in app.Administrators
where admin.Username == “John"
select app).Include(“Administrators”).ToList();
it doesn’t work.
Can’t figure this out. Any help would be much appreciated. Thanks.
Figured it out myself ...
// get the current user's id
var userId = 1;
var apps = (from Application app in context.Applications.Include("Administrators")
where app.Administrators.Select(a => a.Id).Contains(userId)
select app).ToList();
I encountered a strange situation.
I have a root entity (table) with refereance to another entity (view)
public class RootEntity
{
public int Id {get; set;}
public int SubEntityId {get; set;}
public SubEntity SubEntity {get; set;}
}
public class SubEntity
{
public int Id {get; set;}
}
When I set only the RootEntity.SubEntityId with existing SubEntityId All goes well.
But, when I set the Ref to as follow
RootEntity.SubEntity = attachedSubEntity
For whatever reason EF is trying to insert the attached SubEntity to the view and I get this
System.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column '****', table '****'; column does not allow nulls. INSERT fails.
I found the problem! SubEntity was fetched with AsNoTracking option.
I thought that using this option will solve the "Insert" problem but in fact he was the cause!
When I removed the AsNoTracking addition all goes well.
It's still weird because the problem occurred only when used UnitTesting (Nunit). But with WCF I not encountered the problem
I'm using EF 4.2 where the relevant bits look like:
public class MyDbContext : DbContext
{
public IDbSet<User> Users { get; set; }
public IDbSet<Membership> Memberships { get; set; }
}
public User()
{
public int Id { get; set; }
public string Email { get; set; }
public virtual Membership Membership { get; set; }
}
If I pull back a particular User and then update the associated Membership object (to say update the failed password count) the Membership object gets updated, but so does the User despite the fact that none of the User properties have been updated. Is this because the Membership object has fired some sort of changed event and it's bubbled up to the parent User?
This occurs even if I load the User and then get the Membership using _context.Memberships.Find(userId) rather than just using the user.Membership navigation property. I'm guessing in the context graph these two are equivalent?
Is there any way to stop the User object being updated as I use a calculated value column of date modified and I would prefer this were not updated when the child entity is altered. Ideally I want to pull back the User object rather than just querying the Membership DbSet as I want to read some of the User properties too.
The SQL which is fired on the parent Users table is:
update [dbo].[Users]
set #p = 0
where (([Id] = #0) and ([Version] = #1))
With the following database schema:
User: UserID (primary key)
UserInfo: UserInfoID (primary key), UserID (foreignKey), [Other columns with UserInfo data]
User and UserInfo have a 1-1 relationship. I want to map this schema to the following POCOs:
public class User
{
[Key]
public int UserID {get;set;}
public virtual UserInfo userinfo {get;set;}
}
public class UserInfo
{
[Key]
public int UserInfoID {get;set;}
public int UserID {get;set;}
[ForeignKey("UserID")]
public virtual User User {get; set;}
... Other properties of UserInfo...
}
Basically I want to be able to load a Users object, and have the related UserInfo object present as well. This seems like a simple task, but I have not been able to find the right combination of attributes and/or fluent API to accomplish this.
EDIT: I have found that making the UserInfo property in Users an ICollection, then everything is wired up properly. So my question now is, is there a way to avoid using an ICollection, as I know that a User will have either 1 or 0 associated UserInfo records.
For one to one scenarios you have to use the same property as key and fk on both entities.
Try using the UserId property as the Id of UserInfo entity and then like the Fk to the User property.
I have got this weird error Cannot add an entity with a key that is already in use
But what is quite irritable about that error is that user gets no detais - Who? What? What table? What record is the culprit of this error?
It would be desperately complicated to determine it, in case you do many operations on LINQ objects before .Submit()
Is there any way to determine what certainly caused this error?
This error typically happens when you are creating a new record in a MetaTable with a foreign key relationship and the foreign key record already exists.
For example, let's say you have an Contact table and an Address table, and each Contact can hold multiple Addresses. The error occurs when you create a new Contact record and try to manually associate an existing Address record to that new Contact.
Assuming that the passed Address ID represents an existing Address record, this doesn't work:
public class Contact
{
public int Contact_ID { get; set; }
public string Name { get; set; }
public Address ContactAddress { get; set; }
public string Phone { get; set; }
}
public class Address
{
public int Address_ID { get; set; }
public string Street { get; set; }
public string CityState { get; set; }
public string ZIP { get; set; }
}
public void CreateNewContact(int addressID)
{
Contact contact = new Contact();
contact.Name = "Joe Blough";
contact.ContactAddress.Address_ID = addressID;
contact.Phone = "(555) 123-4567";
DataContact.SubmitChanges();
}
Historically, SQL developers are trained to just pass the ID value in order for the magic to happen. With LINQ-to-SQL, because the database activity is abstracted, we have to pass the whole object so that the LINQ engine can properly reflect the necessary changes in the ChangeSet. In the above example, the LINQ engine assumes that you are asking to create a new Address record, because it didn't have one to work with when the SubmitChanges was made and it has to respect the contract established by the foreign key relationship. It creates a blank Address record with the passed ID value. The error occurs because that ID value already exists in the data table and the ChangeSet has not flagged the Address delta as an Update.
The fix is to pass in the entire record, not just the ID value:
contact.ContactAddress = DataContext.Addresses.Where(a => a.Address_ID == addressID).Single();
Now, the LINQ engine can properly flag the incoming Address record as an existing one and not try to recreate it.
May be the column you trying to Attach(), Remove(), Add() or DeleteOnSubmit(), is a primary key and you are trying to add or attach the same value again.
Also you might be accessing a primary key or foreign key value column in a different method and it's not closed yet when you trying to call the above methods.
Above to these methods Attach(), Remove(), Add() or DeleteOnSubmit(), try to create a new instance of your datacontext again and run.
It sounds like you are doing an Table.Attach() and the entity you are attaching has a key value that L2S is already tracking. This has got nothing to do with a duplicate key in your physical database.
As explained on one of the answers above, this error is more likely due to trying to insert a record into the table with a repeated value on primary ID key field. You could solve the problem by selecting/creating a different primary key.