relating columns using DbContext - c#

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();

Related

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.

having 2 one to one relations EF

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();

InverseProperty Entity Framework 7

I am facing a problem with EF7 inverse property. There are two entities that are connected like this.
public class Employee
{
public int Id { get; set; }
}
public class Review
{
public int Id { get; set; }
[Required]
public virtual Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
I want to access a list of the reviews when I start to query my employees, so I tried to do this:
public class Employee
{
public Employee()
{
Reviews = new List<Review>();
}
public int Id { get; set; }
[InverseProperty("Employee")]
public virtual ICollection<Review> Reviews { get; set; }
}
With this, the query is not well made and return this error:
Invalid column name 'EmployeeId1'.
This is the part of the query where is the error:
SELECT [ua].[Id], [r].[EmployeeId], [r].[EmployeeId1], [r1].[EmployeeId], [r1].[EmployeeId1]
FROM [UserAssessment] AS [ua]
LEFT JOIN [Review] AS [r] ON [ua].[ReviewId] = [r].[Id]
LEFT JOIN [Review] AS [r1] ON [ua].[ReviewId] = [r1].[Id]
Anyone know what can I do?
UPDATE
This statement is generating the query:
return this.DbSet
.Include(ua => ua.Employee).ThenInclude(t => t.Role)
.Include(ua => ua.Review).ThenInclude(rt => rt.ReviewType)
.Include(ua => ua.Review).ThenInclude(rt => rt.Manager).ThenInclude(r => r.Role)
I have to access with those same includes because lazy loading is not available on EF7 yet.
You need the InverseProperty on both the Employee and Review
public class Review
{
public int Id { get; set; }
[Required]
[InverseProperty("Reviews")]
public Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
public class Employee
{
public int Id { get; set; }
[InverseProperty("Employee")]
public ICollection<Review> Reviews { get; set; }
}
Should work. I have a similar setup where it creates the navigation without creating any new fields. If this doesn't work let me know and I'll spin up a test project.
Also note, that EF7 currently ignores virtual and this does not have meaning like it did in EF6.

Display all blog articles of a specific user

The scenario: I have two tables: UserProfiles and BlogArticles. The blog article is inside UserProfile, so if I would like to get all blog articles of a specific user, I would type something like:
db.UserProfiles.SingleOrDefault(x=> x.UserName == User.Idenity.Name).BlogArticles
However, I would like to do this using the BlogArticles table, that is, I would like to get BlogArticles that come from the same UserProfile, as following:
db.BlogArticles(x=> ...) // these should be from one user only.
Solution1
One way is to do it backwards, as following:
db.UserProfiles.FirstOrDefault(d => d.BlogArticles.FirstOrDefault(x=> x.Id==BlogArticles) != null);
So, given a BlogArticle id, the UserProfile can be found.
However, how would I be able to get a list of articles that come from a particular userprofile using the BlogArticle table?
I've tried
db.BlogArticles.Where(x=> Functions.GetAuthor(x.Id) == User.Identity.Name).ToList()
but I get the following error:
An exception of type 'System.NotSupportedException' occurred in System.Data.Entity.dll but was not handled in user code
Additional information: LINQ to Entities does not recognize the method 'System.String GetAuthor(Int32)' method, and this method cannot be translated into a store expression.
How can I solve this in a better way?
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public virtual ICollection<BlogArtilce> BlogArticles { get; set; }
public virtual ICollection<Group> Groups { get; set; }
//public DateTime Created = DateTime.Today;
}
[Table("BlogArticle")]
public class BlogArticle
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual string Name { get; set; }
public string Description { get; set; }
public virtual string Contents { get; set; }
public virtual int Views { get; set; }
public virtual string Password { get; set; }
public virtual string Tags { get; set; }
}
public class Group
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; } // this must be an int.
public string Name { get; set; }
public virtual ICollection<BlogArticle> BlogArticles { get; set; }
}
You're making this much more complicated than it needs to be. From your OP you already selected an author and should therefore have access to the author's id and the means to store it.
So, your code could look something like this, assuming that BlogArticles has a property named AuthorId:
int localAuthorId = ???; // assign from wherever you fetched it
// simpler filter:
db.BlogArticles.Where(x => x.AuthorId = localAuthorId).ToList()

How to configure a foreign key that is defined in the model with one to zero or one relationship

I have some questions with relationship of ef code first.
My code:
public class user
{
public user()
{
}
public int id { get; set; }
public string code { get; set; }
public string name { get; set; }
}
public class dep
{
public int id { get; set; }
public Nullable<int> UserId { get; set; }
public virtual user User { get; set; }
}
modelBuilder.Entity<dep>()
.HasOptional(t => t.User)
.WithOptionalDependent();
The table dep that code first auto generated has a foreign key named User_Id,
but that is not what I want.
I want to use the column UserId that I defined in the model.
How will I change my code.
Think you can just add an attribute
[ForeignKey("UserId")]
on your User property.
Now, the question is why ? You're not in a Database, but in a ORM, an object world. You should work with User Navigation property, not with UserId. And if you need UserId, you should use User.Id.
But... that's your soultion ;)
Could you try the following? I didn't test it, but think it should work:
public class User
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public Dep Dep { get; set; }
}
public class Dep
{
public int Id { get; set; }
public User User { get; set; }
}
public class DepConfig : EntityTypeConfiguration<Dep>
{
public DepConfig()
{
this.HasRequired(t => t.User)
.WithOptional(t => t.Dep);
}
}
This assumes that Dep must have a User, but a User isn't required to have Dep. I added the property Dep to the User class so you can navigate in both directions.
Hope it helps.

Categories