having 2 one to one relations EF - c#

I have been doing some research into the fluent API to be able to make this but i am not sure if that is the way to go, from what i understand the OnModelCreate will recreate a DB but what i actually need is a way to relate my tables for my entity, i have a DB with this 2 tables
dbo.Fleets
OwnerId (PK,uniqueidentifier,not null)
ownerName (nvarchar(255),not null)
dbo.UserAccount
UserID (PK,uniqueidentifier,not null)
UserName (nchar(20), null)
SelectedFleet (FK,uniqueidentifier,null)
PrimaryFleet (FK,UniqueIdentifier,null)
The foreign keys are for Fleets.OwnerId, both of them, so inside my application i want to be able to get the fleet for my Primary and SelectedFleet with EF.
so if i run var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).Include(d => d.Fleet).SingleOrDefault(); I'll be getting my complete results

I personally prefer to use DataAnnotations.
[Table("UserAccounts")]
public class UserAccounts
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Fleets> Fleets { get; set; }
}
[Table("Fleets")]
public class Fleets
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public virtual UserAccounts UserAccount { get; set; }
}
Then to access the users fleet you would use
var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).Include(d => d.Fleet).SingleOrDefault();

Related

Insert record in many-to-many when key is DatabaseGeneratedOption.None [duplicate]

When inserting data into a many-to-many relationship, should you insert to the join-table or to both original tables?
My table models:
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }
The relationship between them is configured with Fluent API:
builder.Entity<GroupMembers>().HasKey(gm => new { gm.UserId, gm.GroupId });
builder.Entity<GroupMembers>().HasOne(gm => gm.Group).WithMany(group => group.GroupMembers).HasForeignKey(gm => gm.GroupId);
builder.Entity<GroupMembers>().HasOne(gm => gm.User).WithMany(user => user.GroupMembers).HasForeignKey(gm => gm.UserId);
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public List<GroupMember> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public List<GroupMembers> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class GroupMembers
{
[Key]
public Guid UserId { get; set; }
public User User { get; set; }
[Key]
public Guid GroupId { get; set; }
public Group Group { get; set; }
}
Now, the question is; in which tables/classes should I insert the data about the group members?
Is it like this:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
user.GroupMembers.Add(groupMember);
group.GroupMembers.Add(groupMember)
_databaseContext.Users.Update(user);
_databaseContext.SaveChanges();;
_databaseContext.Groups.Update(group);
_databaseContext.SaveChanges();
Or like this, leaving the User and Group untouched, with the information about their relationship ONLY in the join-table:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
As far as Entity Framework is concerned, this is not a many-to-many relationship
What you have here is three entity types with two one-to-many relationships defined between them. You might know that this is done to represent a many-to-many, but EF doesn't know that.
If I arbitrarily change the names of your entities while maintaining the structure, you wouldn't be able to tell if this was a many-to-many relationship or not.
Simple example:
public class Country {}
public class Company {}
public class Person
{
public int CountryOfBirthId { get; set; }
public virtual Country CountryOfBirth { get; set; }
public int EmployerId { get; set; }
public virtual Company Employer { get; set; }
}
You wouldn't initially think of Person as the represenation of a many-to-many relationship between Country and Company, would you? And yet, this is structurally the same as your example.
Essentially, your handling of your code shouldn't be any different from how you handle any of your one-to-many relationships. GroupMembers is a table (db set) like any else, and EF will expect you to treat it like a normal entity table.
The only thing that's different here is that because GroupMember has two one-to-many relationships in which it is the "many", you therefore have to supply two FKs (one to each related entity). But the handling is exactly the same as if you had only one one-to-many relationship here.
In other words, add your groupMember to the table itself:
GroupMembers groupMember = new GroupMembers
{
// You don't have to fill in the nav props if you don't need them
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
Note: The following only applies to non-Core Entity Framework, as EF Core does not yet support it.
An example of what would be a "real" many-to-many relationship in (non-Core) EF would be if the intermediary table was not managed by you, i.e.:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
}
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
In this scenario, EF will still generate the cross table in the database, but EF will hide this table from you. Here, you are expected to work via the nav props:
var user = myContext.Users.First();
var group = myContext.Groups.First();
user.Groups.Add(group);
myContext.SaveChanges();
Whether you use a "real" many-to-many relationship or manage the cross table yourself is up to you. I tend to only manage the cross table myself when I can't avoid it, e.g. when I want additional data on the cross table.
Make sure the data id is correct and Exists
GroupMembers groupMember = new GroupMembers
{
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
There is less line of code and you have to assume that the object is completely independent when inserted

Query joined tables c# code first

I have these tables i have made in c# using code first approach.
Employee class:
public int id { get; set; }
public string name { get; set; }
Department class:
public int id { get; set; }
public string deptName { get; set; }
public IQueryable<Employee> { get; set; }
This generates a DepartmentID in my Employee table in my sql database. I cannot however access this field in c# as DepartmentID is not a field in the employee class/model.
My question is how do i access this variable. I wish to do some various joins etc but am struggling with this.
You can certainly expose the foreign key, but it is not necessarily needed. The beauty of EF is you don't need joins.
First I would clean up your classes:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
// Exposed FK. By convention, EF know this is a FK.
// EF will add one if you omit it.
public int DepartmentID { get; set; }
// Navigation properties are how you access the related (joined) data
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now you can query your data easily:
var employeeWithDepartment = context.Employee
.Include(e => e.Department)
.FirstOrDefault(e => e.ID = 123);
var employeeName = employeeWithDepartment.Name;
var departmentName = employeeWithDepartment.Department.Name;
... etc.
var departmentWithListOfEmployees = context.Departments
.Include(d => d.Employees)
.Where(d => d.Name == "Accounting")
.ToList();
... build table or something
foreach (var employee in departmentWithListOfEmployees.Employees)
{
<tr><td>#employee.ID</td><td>#employee.Name</td>
}
... close table

Linq mystery error in EF query

I have a UserProfile class
[Key]
public int UserProfileId { get; set; }
public string AppUserId { get; set; }
...code removed for brevity
[Required]
public NotificationMethod NotificationMethod { get; set; }
public List<PrivateMessage> PrivateMessages { get; set; }
public List<Machine> OwnedMachines { get; set; }
public bool IsProfileComplete { get; set; }
public byte[] Avatar { get; set; }
public string AvatarUrl { get; set; }
public string GetFullName()
{
return $"{FirstName} {LastName}";
}
}
I also have a PrivateMessage class
public class PrivateMessage
{
[Key]
public int Id { get; set; }
public int MessageToUserId { get; set; }
public int MessageFromUserId { get; set; }
public DateTime DateSent { get; set; }
public string Message { get; set; }
}
I set up a simple test to pull the user profile out with various includes. The PrivateMessages always errors. Here is a sample method that errors.
public static UserProfile GetUserProfileIncluding(string appUserId)
{
using (RestorationContext)
{
//RestorationContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
return RestorationContext.MemberProfiles.Where(m => m.AppUserId == appUserId)
.Include(m=> m.PrivateMessages)
.FirstOrDefault();
}
}
The error noted is
InnerException {"Invalid column name 'UserProfile_UserProfileId'.\r\nInvalid column name 'UserProfile_UserProfileId'."} System.Exception {System.Data.SqlClient.SqlException}
Which I don't understand, neither table has a column "UserProfile_UserProfileId"
If I use the property OwnedMachines instead of PrivateMessages, it works perfectly fine (well not really, its only pulling in 4 records when there are 6 that match but I can figure that out later).
public static UserProfile GetUserProfileIncluding(string appUserId)
{
using (RestorationContext)
{
return RestorationContext.MemberProfiles.Where(m => m.AppUserId == appUserId)
.Include(m=> m.OwnedMachines)
.FirstOrDefault();
}
}
And you can see below, Machine is set up exactly like PrivateMessage, albeit it has two UserProfiles instead of one
public class Machine
{
[Key]
public int MachineId { get; set; }
public int OwnerProfileId { get; set; }
public int SerialNumber { get; set; }
public string YearofManufacture { get; set; }
public string ModelName { get; set; }
public Manufacturer Manufacturer { get; set; }
public DateTime DateAcquired { get; set; }
}
I've spent far to much time on this now. Does it have something to do with the fact that I have two UserProfile Id int properties in PrivateMessage? (MessageToUserId & MessageFromUserId). I originally had these set as foreign keys with a UserProfile property in there as well like this
[ForeignKey("MessageToProfile")]
public int MessageToUserId { get; set; }
public UserProfile MessageToProfile { get; set; }
[ForeignKey("MessageFromProfile")]
public int MessageFromUserId { get; set; }
public UserProfile MessageFromProfile { get; set; }
But I removed them thinking they may have been the source of the error, but apparently not.
UPDATE:
After a bunch more trial and error, it is apparent that the current method will always err as the method is looking for a navigable property which doesn't exist. Since I have the two int properties in PrivateMessage, when trying to include those in the UserProfile object, I will need to filter then by MessageToUserId and then include them. Not sure how to filter and include.
Using this method should work;
public static UserProfile GetProfileForLoggedInUser(string appUserId)
{
using (RestorationContext)
{
RestorationContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
var profile= RestorationContext.MemberProfiles.Include(m => m.OwnedMachines)
.FirstOrDefault(m => m.AppUserId == appUserId);
var pms = RestorationContext.PrivateMessages.Where(m => m.MessageToUserId == profile.UserProfileId).ToList();
if (profile != null) profile.PrivateMessages = pms;
return profile;
}
}
But it gives the same invalid column error UserProfile_UserProfileID.
Here is the TSql
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[MessageToUserId] AS [MessageToUserId],
[Extent1].[MessageFromUserId] AS [MessageFromUserId],
[Extent1].[DateSent] AS [DateSent],
[Extent1].[Message] AS [Message],
[Extent1].[UserProfile_UserProfileId] AS [UserProfile_UserProfileId]
FROM [RestorationContext].[PrivateMessages] AS [Extent1]
WHERE [Extent1].[MessageToUserId] = #p__linq__0
Since this is just querying the PrivateMessage table WHY is it looking for that UserProfileId, it has nothing to do with this table. Here are the table properties from SSMS
Where is that UserProfileID crap coming from?
Your Machine inclusion works because the Machine class has only one foreign key of UserProfile.
You have 2 foreign keys to the same table in PrivateMessage class, naturally, you would need 2 ICollection navigation properties in your UserProfile class. EntityFramework didn't know which foreign key to use in your PrivateMessage class for loading your ICollection<PrivateMessage> property in your UserProfile class.
public ICollection<PrivateMessage> FromPrivateMessages { get; set; }
public ICollection<PrivateMessage> ToPrivateMessages { get; set; }
In your context class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PrivateMessage>()
.HasRequired(m => m.MessageFromProfile)
.WithMany(t => t.FromPrivateMessages)
.HasForeignKey(m => m.MessageFromUserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<PrivateMessage>()
.HasRequired(m => m.MessageToProfile)
.WithMany(t => t.ToPrivateMessages)
.HasForeignKey(m => m.MessageToUserId)
.WillCascadeOnDelete(false);
}
UPDATE
EF uses convention over configuration, and by having navigation properties of UserProfile in your PrivateMessage class will imply a relationship and EF will try to find a foreign key in the form of <Navigation Property Name>_<Primary Key Name of Navigation Property type>, which gives you UserProfile_UserProfileId.
You should be wondering why UserProfile_UserProfileId instead of UserProfile_MessageToUserId or UserProfile_MessageFromUserId at this point. That's because of your foreign key attribute, telling EF to use the UserProfileId property in your UserProfile class.
What you can do now is, remove the foreign key attributes like this
public int MessageToUserId { get; set; }
public UserProfile MessageToProfile { get; set; }
public int MessageFromUserId { get; set; }
public UserProfile MessageFromProfile {get; set; }
and add another ICollection and do the modelBuilder configuration like how I stated before the update.

relating columns using DbContext

I cant figure out how to relate a column with another table using the include method.
i have the following models
public class Fleet
{
[Key]
public Guid OwnerId { get; set; }
public String ownerName { get; set; }
public virtual ICollection<UserAccount> UserAccount { get; set; }
}
public class UserAccount
{
[Key]
[Display(Name="UserID")]
public Guid UserID { get; set; }
public String UserName { get; set; }
public Guid SelectedFleet { get; set; }
public Guid? PrimaryFleet { get; set; }
public String Password { get; set; }
public virtual Fleet Fleet { get; set; }
}
In the Model UserAccount i have 2 foreignKeys where selectedFleet and PrimaryKey are realated to my fleet table
With this code i do get my UserAccount row back so that i can complete my login, i state this so you are aware that there is actually info to get bakc from the table with the data provided
var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).SingleOrDefault();
The problem is when i try to include the fleet my record returns null, i know i am doing something wrong since i don't even know how to tell the include what column is supposed to be related to which table i tried d => d.Fleet.OwnerId == d.SelectedFleet but i got an error telling me thats not the use of inlcude
var v = dc.UserAccounts.Where(a => a.UserName == model.UserName).Include(d => d.Fleet).SingleOrDefault();

Entity Framework Fluent API Mapping

Let's assume I have these tables:
Role
- RoleId
- Name
UserRole
- UserId
- RoleId
User
- UserId
- Username
UserEmail
- UserId
- EmailId
- IsPrimary
Email
- EmailId
- Address
Now my model, should look something like this:
public class Role {
public int RoleId { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual ICollection<UserEmail> UserEmails { get; set; }
}
public class UserEmail {
public int UserId { get; set; }
public int EmailId { get; set; }
public bool IsPrimary { get; set; }
public virtual User User { get; set; }
public virtual Email Email { get; set; }
}
public class Email {
public int EmailId { get; set; }
public string Address { get; set; }
public virtual ICollection<UserEmail> UserEmails { get; set; }
}
These are common and specific things I would like to do in this particular case:
Add the primary keys:
modelBuilder.Entity<Role>().HasKey(q => q.RoleId);
Will this have the same effect than using the Key attribute in the entity property? If so, why bother using the modelBuilder when using Data Annotations is shorter and easier to read/write? Is there any convention on when to use Data Annotations or Fluent API?
Add many to many relationships:
modelBuilder.Entity<Role>()
.HasMany(q => q.Users)
.WithMany(q => q.Roles)
.Map(q => {
q.MapLeftKey("RoleId");
q.MapRightKey("UserId");
q.ToTable("UserRoles");
});
Add one to many relationships:
modelBuilder.Entity<UserEmail>()
.HasRequired(q => q.User)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.EmailId);
Is the last line required?
Yes, that HasKey method has the same effect as the [Key] attribute. You might have a lot of configuration to do and prefer to keep it all in the ModelBuilder method. On the other hand you might have very little or prefer to use the attributes for some of it. Just gives you some flexibility.
If you're talking about the:
.HasForeignKey(q => q.EmailId);
Then in your case, yes it is required. Why? Because you've created your own foreign key property in the UserEmail entity. If you deleted the property you could remove this line and EF would create one for you in the database called Email_Id. You could still access this through the navigation property instanceOfUserEmail.Email.EmailId.

Categories