EF4.1 multiple nested entity Includes gets NotSupportedException? - c#

Edit: Updated problem description based on testing - 12 Sep 2011.
I have this query that throws a NotSupportedException ("Specified method is not supported.") whenever I call .ToList().
IQueryable<FileDefinition> query = db
.FileDefinitions
.Include(x => x.DefinitionChangeLogs)
.Include(x => x.FieldDefinitions.Select(y => y.DefinitionChangeLogs)) // bad
.Include(x => x.FieldDefinitions.Select(y => y.FieldValidationTables)) // bad
.Where(x => x.IsActive);
List<FileDefinition> retval = query.ToList();
If I comment out either line that I have commented as "bad", then the query works. I have also tried including different nested entities in my object model with the same effect. Including any 2 will cause a crash. By nested, I mean a navigation property of a navigation property. I also tried using the .Include methods with a string path: same result.
My table structure looks like this:
This is using MySQL 5.1 (InnoDB tables obviously) as the database store with MySQL Connector/NET 6.3.4.
So my question is: Why doesn't this work?
Note: I can get it to work if I explicitly load the related entities like in this link. But I want to know why EF hates my data model.
ANSWER: MySQL Connector is apparently not capable of handling the 2nd nested entity include. It throws the NotSupportedException, not .NET EF. This same error was also present when I tried this using EF4.0, but my research at the time led me to believe it was self-tracking entities causing the issue. I tried upgrading to latest Connector, but it started causing an Out of Sync error. This is yet another reason for me to hate MySQL.

Maybe a little late to the party but i found the following workaround fairly useful in a current project:
IQueryable<FileDefinition> query = db.FileDefinitions
.Include(x => x.FieldDefinitions.Select(y => y.DefinitionChangeLogs.Select(z => z.FieldDefinition.FieldValidationTables)))
Where rather than using a second row of includes, use Select to get back to the original navigation property and another Select to go forwards to the property you need to include.

I have made a little console application to test your scenario and this test application works:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace EFIncludeTest
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildLevel1> ChildLevel1s { get; set; }
}
public class ChildLevel1
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildLevel2a> ChildLevel2as { get; set; }
public ICollection<ChildLevel2b> ChildLevel2bs { get; set; }
}
public class ChildLevel2a
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ChildLevel2b
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
}
class Program
{
static void Main(string[] args)
{
// Create entities to test
using (var ctx = new MyContext())
{
var parent = new Parent
{
Name = "Parent",
ChildLevel1s = new List<ChildLevel1>
{
new ChildLevel1
{
Name = "FirstChildLevel1",
ChildLevel2as = new List<ChildLevel2a>
{
new ChildLevel2a { Name = "FirstChildLevel2a" },
new ChildLevel2a { Name = "SecondChildLevel2a" }
},
ChildLevel2bs = new List<ChildLevel2b>
{
new ChildLevel2b { Name = "FirstChildLevel2b" },
new ChildLevel2b { Name = "SecondChildLevel2b" }
}
},
new ChildLevel1
{
Name = "SecondChildLevel1",
ChildLevel2as = new List<ChildLevel2a>
{
new ChildLevel2a { Name = "ThirdChildLevel2a" },
new ChildLevel2a { Name = "ForthChildLevel2a" }
},
ChildLevel2bs = new List<ChildLevel2b>
{
new ChildLevel2b { Name = "ThirdChildLevel2b" },
new ChildLevel2b { Name = "ForthChildLevel2b" }
}
},
}
};
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
// Retrieve in new context
using (var ctx = new MyContext())
{
var parents = ctx.Parents
.Include(p => p.ChildLevel1s.Select(c => c.ChildLevel2as))
.Include(p => p.ChildLevel1s.Select(c => c.ChildLevel2bs))
.Where(p => p.Name == "Parent")
.ToList();
// No exception occurs
// Check in debugger: all children are loaded
Console.ReadLine();
}
}
}
}
My understanding was that this basically represents your model and the query you are trying (taking also your comments to your question into account). But somewhere must be an important difference which is not visible in the code snippets in your question and which makes your model fail to work.
Edit
I have tested the working console application above with MS SQL provider (SQL Server 2008 R2 Express DB), not MySQL Connector. Apparently this was the "important difference".

Related

Using a nuget package produces Core SqlClient Data Provider invalid column error but tests within package are fine

[Update: I am able to reproduce in tests by using a dev database - a real SQL db]
I have a project that gets packaged up for use in other projects. The entities in question are:
public class PlatformQuizConfigurationEntity
{
public int Id { get; set; }
...
}
public class PlatformEnrollmentEntity
{
public int Id { get; set; }
public int PlatformQuizConfigurationId { get; set; }
public int? CourseSectionId { get; set; }
[ForeignKey("PlatformQuizConfigurationId")]
public PlatformQuizConfigurationEntity PlatformQuizConfiguration { get; set; }
...
public int PlatformEnrollmentStatusId { get; set; }
[ForeignKey("PlatformEnrollmentStatusId")]
public PlatformEnrollmentStatusEntity PlatformEnrollmentStatus { get; set; }
public DateTime CreatedDate { get; set; }
public int PlatformEnrollmentDeliveryId { get; set; }
[ForeignKey("PlatformEnrollmentDeliveryId")]
public PlatformEnrollmentDelivery PlatformEnrollmentDeliveryEntity { get; set; }
}
Within the DbContext, I have
public virtual DbSet<PlatformEnrollmentEntity> PlatformEnrollments { get; set; }
...
public virtual DbSet<PlatformQuizConfigurationEntity> PlatformQuizConfigurations { get; set; }
...
I have test coverage for this which uses in memory sqlite. I seed PlatformEnrollments with:
PlatformQuizConfigurationEntity pqc = _db.PlatformQuizConfigurations
.SingleOrDefault(s => s.ContinuingEducationData == "dummy-data");
LtiUserEntity lti = _db.LtiUsers.SingleOrDefault(s => s.PlatformUserId == this.defaultUser.PlatformUserId);
_db.PlatformEnrollments.Add(
new PlatformEnrollmentEntity
{
PlatformQuizConfigurationId = pqc.Id,
CourseSectionId = 401,
UserId = lti.Id,
PlatformEnrollmentStatus = new PlatformEnrollmentStatusEntity
{
IsActive = true,
IsActiveUpdated = DateTime.UtcNow,
IsComplete = false,
ReceivedLastChance = false
},
PlatformEnrollmentDelivery = new PlatformEnrollmentDeliveryEntity
{
Mode = "email",
ModeTarget = "test#example.org",
Hour = 15,
Minute = 00
}
}
);
_db.SaveChanges();
defaultPlatformEnrollment = _db.PlatformEnrollments.SingleOrDefault(p => p.UserId == lti.Id && p.CourseSectionId == 401);
And then a simple test
public IPlatformEnrollmentRepository repo = null;
public IPlatformEnrollmentService service = null;
public PlatformEnrollmentsTests()
{
repo = new PlatformEnrollmentRepository(_db, mapper, loggerFactory);
service = new PlatformEnrollmentService(repo);
}
...
[Fact]
public async Task PlatformEnrollment_Get()
{
var existing = await service.GetPlatformEnrollment(defaultPlatformEnrollment.Id);
Assert.IsType<PlatformEnrollment>(existing);
Assert.Equal("dummy-data", existing.PlatformQuizConfiguration.ContinuingEducationData);
}
The test calls the service
public async Task<PlatformEnrollment> GetPlatformEnrollment(int id)
{
return await _platformRepository.GetPlatformEnrollment(id);
}
which calls the repository and returns the Platform enrollment
var result = await _db.PlatformEnrollments
.AsNoTracking()
.Include(p => p.PlatformEnrollmentDelivery)
.Include(p => p.PlatformEnrollmentStatus)
.Include(p => p.PlatformQuizConfiguration)
.Include(p => p.PlatformQuizConfiguration.QuizType)
.Include(p => p.PlatformQuizConfiguration.ScheduledQuestions)
.SingleOrDefaultAsync(p => p.Id == id);
return _mapper.Map<PlatformEnrollmentEntity, PlatformEnrollment>(result);
The test passes. However, when I package this and install in another project (Azure Functions project, HTTP Trigger), then call it like:
var result = await _platformEnrollmentService.GetPlatformEnrollment(id);
if (result == null)
{
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
return new OkObjectResult(result);
I get a 500 error:
Executed 'Get_PlatformEnrollment' ()
System.Private.CoreLib: Exception while executing function: Get_PlatformEnrollment. Core Microsoft SqlClient Data Provider: Invalid column name 'PlatformQuizConfigurationEntityId'.
Invalid column name 'PlatformQuizConfigurationEntityId'.
No table in the database has a PlatformQuizConfigurationEntityId column, only PlatformQuizConfigurationId so I'm assuming I just have something in my models/db context misconfigured.
I'm not sure if I need configuration in the project that uses the package, or if I'm missing something in the package.
If I'm missing something in the package, I'm confused why the test is passing.
If I'm missing something in the project that uses the package, that might confuse me even more since the project really doesn't care what the entities look like - it's just passing along relevant status codes and data.
As expected, I messed up a configuration. In order to reproduce, I updated the test suite to use a real database: our dev instance...
var options2 = new DbContextOptionsBuilder<OurDbContext>()
.UseSqlServer("your database connection string here")
.Options;
_db = new OurDbContext(options2);
Now that I am using this database, I ran the test with a known id:
var existing = await service.GetPlatformEnrollment(363);
Assert.IsType<PlatformEnrollment>(existing);
Assert.Equal("dummy-data", existing.PlatformQuizConfiguration.ContinuingEducationData);
This resulted in the same error I got in testing the project that uses this package. I commented out all of the includes in the method which resulted in the test not getting the exception. One by one I added them in until, of course, the last one caused the exception to be thrown.
public async Task<PlatformEnrollment> GetPlatformEnrollment(int id)
{
var result = await _db.PlatformEnrollments
.AsNoTracking()
.Include(p => p.PlatformEnrollmentDelivery)
.Include(p => p.PlatformEnrollmentStatus)
.Include(p => p.PlatformQuizConfiguration)
.Include(p => p.PlatformQuizConfiguration.QuizType)
.Include(p => p.PlatformQuizConfiguration.ScheduledQuestions)
.SingleOrDefaultAsync(p => p.Id == id);
return _mapper.Map<PlatformEnrollmentEntity, PlatformEnrollment>(result);
}
I was missing the Foreign Key Attribute on the property within ScheduledQuestionEntity:
[ForeignKey("PlatformQuizConfigurationId")]
public PlatformQuizConfigurationEntity PlatformQuizConfiguration { get; set; }
I have come across some information that leads me to believe foreign keys in sqlite aren't very dependable, which is why the tests were passing in this isolated environment.

Many-to-Many EF Core already being tracked - C# Discord Bot

So I'm using Entity Framework Core to build a database of Guilds (Another name for Discord Servers) and Users, with the Discord.NET Library. Each Guild has many users, and each user can be in many guilds. First time using EF and I'm having some teething issues. The two classes are:
public class Guild
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public string Username { get; set; }
public ushort DiscriminatorValue { get; set; }
public string AvatarId { get; set; }
public ICollection<Guild> Guilds { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
With the goal of having 3 tables: Guild, Users, and GuildUsers. This is my current function for getting the guilds:
using var context = new AutomataContext();
var discordGuilds = this.client.Guilds.ToList();
var dbGuilds = context.Guilds;
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users.Select(gu => new User
{
Id = context.Users.AsNoTracking().FirstOrDefault(u => u.Snowflake == gu.Id)?.Id ?? default(int),
}).ToList(),
}).ToList();
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingDbGuild = dbGuilds.AsNoTracking().FirstOrDefault(g => g.Snowflake == guild.Snowflake);
if (existingDbGuild != null)
{
guild.Id = existingDbGuild.Id;
dbGuilds.Update(guild); // Hits the second Update here and crashes
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
I should note, a 'snowflake' is a unique ID that discord uses, but I wanted to keep my own unique ID for each table.
High level overview, guilds are collected into Discord.NET models. These are then transformed into internalGuilds (my guild class, which includes the list of users). Each of these is looped through and upserted to the database.
The issue arises in the second guild loop, where an error is thrown in the "Update" that a User ID is already being tracked (Inside the guild). So the nested ID is already being tracked? Not sure what's going on here, any help would be appreciated. Thanks.
This exception is most likely occurring because you are loading Users without tracking then looping through and potentially trying to update or insert guilds /w the same user reference, especially using the Update method.
I would suggest removing the use of AsNoTracking. Working with detached entity references via AsNoTracking is more of a performance tweak for when reading large amounts of data. You can pre-fetch all of the User references by their snowflake:
using (var context = new AutomataContext())
{
var discordGuilds = this.client.Guilds.ToList();
// Get the user snowflakes from the guilds, and pre-fetch them.
var userSnowflakes = discordGuilds.SelectMany(g => g.Users.Select(u => u.Id)).ToList();
var users = await context.Users
.Where(x => userSnowflakes.Contains(x.Snowflake))
.ToListAsync();
// We need to add references for any New user snowflakes.
var existingSnowflakes = users.Select(x => x.Snowflake).ToList();
// If more detail is needed for new user records, it will need to be fetched from the passed in Guild.User.
var newUsers = userSnowflakes.Except(existingSnowFlakes)
.Select(x => new User { SnowflakeId = x }).ToList();
if(newUsers.Any())
users.AddRange(newUsers);
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users
.Select(gu => users.Single(u => u.Snowflake == gu.Id))
.ToList(),
}).ToList(),
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingGuildId = context.Guilds
.Where(x => x.Snowflake == guild.Snowflake)
.Select(x => x.Id)
.SingleOrDefault();
if (existingGuildId != 0)
{
guild.Id = existingGuildId;
dbGuilds.Update(guild);
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
This should help ensure that the User references for existing users are pointing at the same instances, whether existing users or new user references that will be associated to the DbContext when first referenced.
Ultimately I don't recommend using Update for "Upsert" scenarios, instead since the Db Record needs to be fetched anyways, updating values on the fetched instance or inserting a new one. Update will want to send all fields from an entity to the database each time, rather than just sending what has changed. It means enforcing a bit more control over what can possibly be changed vs. what should not be.

ElasticSearch set parent for child object using ElasticSearch NEST .NET library

Hi have a basic one to many structure ..
public class Person {
public int PersonId { get; set; }
public string Name { get; set; }
public List<Skill> Skills { get; set; }
}
public class Skill{
public int PersonId { get; set; }
public int SkillId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Installed ElasticSearch NEST 5.x .. using .NET Framework 4.5 ..
Exploring the web from last 2 days but not able to find the way to set Person as a parent for Skill ..
I was assuming that NEST will automatically map the parent child relationship so I tried following
private ElasticLowLevelClient client = new ElasticLowLevelClient();
public void CreatePerson(Person person)
{
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("personskills").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Document for person creating without any issue but at the time of personskill doc I am getting this error:
Can't specify parent if no parent field has been configured
While exploring I found may articles saying that I need to set the parent type to the child in mapping .. but how .. what is the procedure to custom map the index and how and where I should do that .. not getting any hint .. please guide
The issue was that I was unable to map the parent-child documents properly .. we can map them by creating index with predefined mapping or we can update the mapping
Create Index
private ElasticLowLevelClient client = new ElasticLowLevelClient();
private CreateIndexDescriptor descriptor = new CreateIndexDescriptor("myindex")
.Mappings(ms => ms
.Map<Person>("person", m => m.AutoMap())
.Map<Skill>("personskills", m => m.AutoMap().Parent("person"))
);
public void CreatePerson(Person person)
{
client.CreateIndex(descriptor); //I am creating it here but one can create it in the class where we will create ElasticClient
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("personskills").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Update mapping
public void CreatePerson(Person person)
{
client.Map<Skill>(m => m
.Parent("person").Index("myindex")); //this will put the default mapping of default index
var parentResponse = client.Index(person, i => i.Index("myindex").Type("person").Id(person.PersonId));
foreach (var skill in person.Skills)
{
var skillResponse = client.Index(skill, i => i.Index("myindex").Type("skill").Parent(person.PersonId.ToString()).Id(skill.SkillId)); //here I am getting error
}
}
Here I have changed child document type to default value .. but one can set it in mapping. Hope this can help others.

How to get foreign key objects with entity framework into a custom object?

I am trying to make a select from the database using the entity framework 5.0.
I have a table called Persons that is referenced by PersonsImages, so basically one record from Persons can have many PersonsImages.
I've made a select statement that gives the Persons, but I would also like to get the PersonsImages as a List<PersonsImages>, and put them in a custom object.
This is the code that I have so far:
var person = new Persons();
using (var context = new PersonEntities())
{
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages // there's an error here
})
.FirstOrDefault();
}
The Persons and the PersonsImages classes look like that (they are copies of the ones generated by the entity framework):
public partial class Persons
{
public Persons()
{
this.PersonsImages = new HashSet<PersonsImages>();
}
public string personName { get; set; }
public string personLastName { get; set; }
public virtual ICollection<PersonsImages> PersonsImages { get; set; }
}
public partial class PersonsImages
{
public string imageName { get; set; }
public byte[] image { get; set; }
public virtual Persons Persons { get; set; }
}
I know I can make a second select and "manually" find them, but isn't it possible to do it in one, just as what the entity framework normally does?
Assuming your error is "can't construct an object in a LINQ to Entities query" - project into an anonymous type, call the ToArray() method to enumerate the results, then project into new instances of Persons:
person = context.Persons.Where(x => x.personId == 555)
.Select(xxx => new
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.ToArray() // data now local
.Select(xxx => new Persons
{
personName = xxx.personName,
personLastName = xxx.personLastName,
PersonImages = xxx.PersonsImages
})
.FirstOrDefault();

View complex Many to Many relationship on ADO.net

I'm trying to update related database on many to many relationship using ADO.net
this is my database design:
as you guys notice, entity framework wont mapping the class_student & subject_course, i've been searching the method and found this website: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
the website told me to make a viewModel, and i do so:
namespace Test.Models.ViewModels
{
public class AssignedStudentData
{
public int ID { get; set; }
public string course_code { get; set; }
public bool Assigned { get; set; }
}
}
It's work flawlessly, but my problem is this line of code:
private void PopulateAssignedStudentData(ms_class ms_class)
{
var allStudent = db.ms_student; //this line is the problem
var ClassStudent = new HashSet<int>(ms_class.ms_student.Select(c => c.ID));
var viewModel = new List<AssignedStudentData>();
foreach (var student in allStudent)
{
viewModel.Add(new AssignedStudentData
{
ID = student.ID,
course_code = student.ms_course.course_name,
Assigned = ClassStudent.Contains(student.ID)
});
}
ViewBag.Students = viewModel;
}
in var allStudent, i've tried to make so the system not generate all the student, but instead, student THAT ASSIGNED WITH A SUBJECT so for example:
private void PopulateAssignedStudentDataBySubject(ms_class ms_class, int subject_id)
{
//var allStudent = db.ms_student; //this line is the problem
//My Version:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Where(m => m.ms_course.ms_subject.subject_id == subject_id); //this code is not working
var ClassStudent = new HashSet<int>(ms_class.ms_student.Select(c => c.ID));
var viewModel = new List<AssignedStudentData>();
foreach (var student in allStudentByCourse )
{
viewModel.Add(new AssignedStudentData
{
ID = student.ID,
course_code = student.ms_course.course_name,
Assigned = ClassStudent.Contains(student.ID)
});
}
ViewBag.Students = viewModel;
}
i think the code won't work because the ms_course and ms_subject is a many-to-many relationship..
Thank you very much
Class
public partial class ms_course
{
public ms_course()
{
this.ms_student = new HashSet<ms_student>();
this.ms_subject = new HashSet<ms_subject>();
}
public int course_id { get; set; }
public string course_code { get; set; }
public string course_name { get; set; }
public virtual ICollection<ms_student> ms_student { get; set; }
public virtual ICollection<ms_subject> ms_subject { get; set; }
}
I understand that you're looking for students having a course that has at least one specific subject assigned to it. That would be:
db.ms_student
.Where(s => s.ms_course.ms_subject
.Any(sb => sb.subject_id == subject_id)))
It always helps me to articulate the problem clearly in terms of the object model first, as I did in the first sentence. It usually reveals what the query should look like.
What does the error message say?
You can try tis:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Include("ms_course.ms_subject").Where(m => m.ms_course.ms_subject.subject_id == subject_id);
alternativ2 (this only works if ms_course has a fk property to ms_subject):
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Where(m => m.ms_course.subject_id == subject_id);
Update:
var allStudentByCourse = db.ms_student.Include(m => m.ms_course).Include("ms_course.ms_subject").Where(m => m.ms_course.ms_subject.Any(s => s.subject_id == subject_id));

Categories