简体   繁体   中英

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

该UserProfileID废话来自哪里?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM