Why doesn't entity framework return any rows from related tables - entity-framework

I wrote this code
class Student {
public Student() {
this.Courses = new HashSet<Course>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
class Course {
public Course() {
this.Students = new HashSet<Student>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
class SchoolDBContext : DbContext {
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public SchoolDBContext()
: base("SchoolDbConnectionString") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
My Seed method looks like
protected override void Seed(ConsoleApplication6.SchoolDBContext context)
{
Course c1 = new Course { ID = 1, Name = "Chemistry" };
Course c2 = new Course { ID = 2, Name = "Maths" };
Course[] courses = new Course[2];
courses[0] = c1;
courses[1] = c2;
Student s1 = new Student { ID = 1, Name = "Student 1" };
Student s2 = new Student { ID = 1, Name = "Student 2" };
Student[] students = new Student[2];
students[0] = s1;
students[1] = s2;
c1.Students = students;
c2.Students = students;
context.Courses.AddOrUpdate(course => new { course.ID }, courses);
}
After I run Update-database I can see that the database has 3 tables. Student and Course tables have 2 rows each and StudentCourse table has 4 rows. So I guess all data is seeded correctly.
Now when I write this code in my main method
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Student s = (from student in c.Students where student.ID == 1 select student).FirstOrDefault();
List<Course> courses = s.Courses.ToList();
Console.WriteLine(s.Name);
Console.WriteLine(courses.Count);
foreach (Course co in courses) {
Console.WriteLine(co.Name);
}
}
it prints the name of the student correctly... but prints 0 for courses.Count and the forloop on courses List returns nothing.
why am I not able to get the courses for student 1?
I also tried the other way round
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
here also the name of the course is returned correctly... but it doesn't print any of the students.
So entity framework is not able to walk to the related table and fetch rows from there.
What's going on?

Found the answer myself.
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = false;
Course co = (from course in c.Courses.Include("Students") where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
putting it here so that it benefits someone....
But according to me my original code should have worked (lazy loading) so I don't understand why my original code which was doing lazy loading did not work.

OK. here is the solution with lazy loading
static void Main(string[] args) {
SchoolDBContext context = new SchoolDBContext();
context.Configuration.LazyLoadingEnabled = true;
Course co = (from course in context.Courses where course.ID == 1 select course).FirstOrDefault();
//Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
foreach (Student s in context.Entry(co).Collection(c => c.Students).Query()) {
Console.WriteLine(s.Name);
}
}
This was really useful
http://msdn.microsoft.com/en-us/data/jj574232#lazy

Related

EfCore 3 and Owned Type in same table, How do you set owned instance

How do you set owned type instance with efcore3?
In following example an exception is raised
'The entity of type 'Owned' is sharing the table 'Principals' with
entities of type 'Principal', but there is no entity of this type with
the same key value that has been marked as 'Added'.
If I set Child property inline savechanges doesn't update child properties
I can't find any example about this. I tried with several efcore3 builds and daily builds. What didn't I understand?
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace TestEF
{
class Program
{
static void Main(string[] args)
{
var id = Guid.NewGuid();
using (var db = new Ctx())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var p = new Principal {Id = id};
db.Principals.Add(p);
db.SaveChanges();
}
using (var db = new Ctx())
{
var p = db.Principals.Single(o => o.Id == id);
p.Child = new Owned();
p.Child.Prop1 = "Test2";
p.Child.Prop2 = "Test2";
db.SaveChanges();
}
}
public class Principal
{
public Guid Id { get; set; }
public Owned Child { get; set; }
}
public class Owned
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
public class Ctx : DbContext
{
public DbSet<Principal> Principals { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=TestEF;Trusted_Connection=True;Persist Security Info=true");
}
protected override void OnModelCreating(ModelBuilder mb)
{
var emb = mb.Entity<Principal>();
emb
.OwnsOne(o => o.Child, cfg =>
{
cfg.Property(o => o.Prop1).HasMaxLength(30);
//cfg.WithOwner();
});
}
}
}
}
This is a bug, filed at https://github.com/aspnet/EntityFrameworkCore/issues/17422
As a workaround you could make the child appear as modified:
db.ChangeTracker.DetectChanges();
var childEntry = db.Entry(p.Child);
childEntry.State = EntityState.Modified;
db.SaveChanges();
Try this instead:
_context.Update(entity);
This will update all the owned properties so SaveChanges() updates them, too.

Prevent EF from saving full object graph

I have a model as below
public class Lesson
{
public int Id { get; set; }
public Section Div { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
}
I also have DB Context as below
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Section> Sections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Then I use the following code to call the database
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Entry(d.Div).State = EntityState.Detached;
c.Lessons.Add(d);
c.SaveChanges();
}
I am expecting this code to save just the Lesson object, not to save the full graph of Lesson and Section, but what happens is that it saves the full graph.
How do I prevent it from doing that?
When you add an entity to DbSet, entityframework will add all of its relative. You need to detach the entity you don't want to add, after adding parent entity to DbSet.
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
}
if you want to add section, related to the lesson , you need to use the same context, or create a new context and load the lesson.
you can use this code
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
//you can use this code
ed.Lesson = d;
// or this code
d.Div = ed;
c.Sections.Add(ed);
c.SaveChanges();
}

Insert constraint failed nFOREIGN KEY constraint failed While Seeding Data

I am trying to Seed some sample Data
public class Condition
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public int ConditionId { get; set; }
public virtual Condition Condition { get; set; }
}
and in my Seed method..
protected override void Seed(AppContext context)
{
Condition condition1 = new Condition();
condition1.Name = "Cond1";
Entity.Entity newEntity1 = new Entity.Entity();
newEntity1.Name = "Test1";
newEntity1.Condition = condition1;
context.Entities.Add(newEntity1);
Condition condition2 = new Condition();
condition2.Name = "Cond2";
Entity.Entity newEntity2 = new Entity.Entity();
newEntity2.Name = "Test Entity 2";
newEntity2.Condition = condition2;
context.Entities.Add(newEntity2);
context.SaveChanges();
}
I am getting this Exception constraint failed FOREIGN KEY constraint failed, I couldn't figure out what wrong I am doing here.
I tried calling context.SaveChanges() after first insertion too and it went fine. but the error appreared only after second context.SaveChanges() only.
protected override void Seed(AppContext context)
{
Condition condition1 = new Condition();
condition1.Id=1;
condition1.Name = "Cond1";
Entity.Entity newEntity1 = new Entity.Entity();
newEntity1.Name = "Test1";
newEntity1.ConditionId=1
newEntity1.Condition = condition1;
context.Entities.Add(newEntity1);
Condition condition2 = new Condition();
condition2.Id=2
condition2.Name = "Cond2";
Entity.Entity newEntity2 = new Entity.Entity();
newEntity2.Name = "Test Entity 2";
newEntity2.ConditionId=2;
newEntity2.Condition = condition2;
context.Entities.Add(newEntity2);
context.SaveChanges();
}
Hope This works..

How To Insert Data In FluentAPI Mapping Table

I have a A Table, B Table and AB (Mapping Table)
A
public class A
{
public int AID{ get; set; }
[JsonIgnore]
public virtual ICollection<B> Bs { get; set; }
}
B
public class B
{
public int BID { get; set; }
[JsonIgnore]
public virtual ICollection<A> As { get; set; }
}
ApplicationDbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<B>()
.HasMany(s => s.As)
.WithMany(c => c.Bs)
.Map(cs =>
{
cs.MapLeftKey("AID");
cs.MapRightKey("BID");
cs.ToTable("AB");
});
}
Now things are perfectly fine, but how do I insert in this AB Mapping table?
If I try to create AB as like below, it generates two tables, AB and AB1 with same column name and all.
public class AB
{
public int ABID { get; set; }
public string AID { get; set; }
public int BID { get; set; }
}
So is there any way to do CRUD in FluentAPI Mapping Table?
If not, then can I force FluentAPI to map from Existing table? In this case I'll manually manage Employee and will change the mapping code to use existing table.
I'm unable to find any of the solution.
Edit: Since the question was changed, I'm writing up a more thorough answer. The answer to your question remains the same, however:
Now things are perfectly fine, but how do I insert in this AB Mapping
table?
You don't!
This is exactly the kind of thing that EF is good at. Instead of managing a link table yourself, now you just end up with the actual object you want. So, if you want to add a link between an A and B, all you do is add a B to the Bs collection on that A. You don't ever insert directly into the AB table, because who cares about that? That table is there so we can have relationships between different As and Bs, that's it. So, Entity Framework will create the table for it's own use, but not present it to you, because that's not how EF works: you work with your objects and let EF handle the database.
That's why when you try to define the table yourself, it creates two: it's already making a table called AB, but you're asking for another one. It can't have exactly the same name so it appends a '1' to the end of it. Since you've already used FluentAPI to define the apping, let EF worry about how to implement the mapping: all you need to care about is that you've now got a way to have an A with a set of Bs, or vice versa.
Since this still sounds confusing with names 'A' and 'B', below is the Program class for a console app that will illustrate this; all you need to do is start a fresh console app, replace the Program class with this one, install the entity framework package, and run enable-migrations -enableautomaticmigrations -force. I recommend you use this to add some objects and relate them, and then go have a look at your database: you will see the 'AB' table, with records that were added. This might help explain it better.
class Program
{
static bool quit = false;
static void Main(string[] args)
{
string s = "Please select an option:" +
"\n1: Insert an A" +
"\n2: Insert a B" +
"\n3: Add a B to an A" +
"\n4: Add an A to a B" +
"\n5: Print all As" +
"\n6: Print all Bs" +
"\n7: Print AB Table" +
"\nx: Quit.";
while (!quit)
{
Console.WriteLine();
Console.WriteLine(s);
var k = Console.ReadKey();
DoStuff(k);
}
}
private static void DoStuff(ConsoleKeyInfo i)
{
switch (i.Key)
{
case ConsoleKey.D1:
//add an A
AddA(GetName());
break;
case ConsoleKey.D2:
//add a B
AddB(GetName());
break;
case ConsoleKey.D3:
// link a B to an A
LinkB(GetBtoLink(),GetAtoLink());
break;
case ConsoleKey.D4:
//link an A to an B
LinkA(GetAtoLink(), GetBtoLink());
break;
case ConsoleKey.D5:
// print As
WriteA();
break;
case ConsoleKey.D6:
//print Bs
WriteB();
break;
case ConsoleKey.D7:
// print AB
WriteAB();
break;
case ConsoleKey.X:
quit = true;
break;
}
}
private static int GetAtoLink()
{
string x;
int z;
do
{
Console.Clear();
Console.WriteLine("Please enter the ID of the A you want to use and then press enter.");
WriteA();
x = Console.ReadLine();
} while (!int.TryParse(x, out z));
return z;
}
private static int GetBtoLink()
{
string x;
int z;
do
{
Console.Clear();
Console.WriteLine("Please enter the ID of the B you want to use and then press enter.");
WriteB();
x = Console.ReadLine();
} while (!int.TryParse(x, out z));
return z;
}
private static void WriteB()
{
Console.WriteLine("{0,10}{1,15}", "ID", "Name");
using (var db = new Context())
{
foreach (var a in db.Bs)
{
Console.WriteLine("{0,10}{1,15}", a.BID, a.Name);
}
}
}
private static void WriteA()
{
Console.WriteLine("{0,10}{1,15}", "ID", "Name");
using (var db = new Context())
{
foreach (var a in db.As)
{
Console.WriteLine("{0,10}{1,15}", a.AID, a.Name);
}
}
}
private static void WriteAB()
{
Console.WriteLine("{0,10}{1,10}", "AID", "BID");
using (var db = new Context())
{
// this is the only way we need to do this, because it's many to many,
// if an A is linked to a B, then that B is by definition linked to that A as well.
foreach (var a in db.As)
{
foreach (var b in a.Bs)
{
Console.WriteLine("{0,10}{1,10}", a.AID, b.BID);
}
}
}
}
private static void LinkB(int bToUse, int aToUse)
{
using (var db = new Context())
{
var a = db.As.First(x => x.AID == aToUse);
var b = db.Bs.First(y => y.BID == bToUse);
a.Bs.Add(b);
db.SaveChanges();
}
}
private static void LinkA(int aToUse, int bToUse)
{
using (var db = new Context())
{
var a = db.As.First(x => x.AID == aToUse);
var b = db.Bs.First(y => y.BID == bToUse);
b.As.Add(a);
db.SaveChanges();
}
}
private static string GetName()
{
Console.WriteLine("Please enter a name");
return Console.ReadLine();
}
private static void AddA(string input)
{
using (var db = new Context())
{
db.As.Add(new A {Name = input});
db.SaveChanges();
}
}
private static void AddB(string input)
{
using (var db = new Context())
{
db.Bs.Add(new B { Name = input });
db.SaveChanges();
}
}
}
public class A
{
public int AID { get; set; }
public string Name { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public int BID { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<B>()
.HasMany(s => s.As)
.WithMany(c => c.Bs)
.Map(cs =>
{
cs.MapLeftKey("AID");
cs.MapRightKey("BID");
cs.ToTable("AB");
});
}
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
}
Old Answer: You've defined an ICollection<ApplicationUser> called Employees in Company, and mapped to it with FluentAPI. This creates a table called 'Employees' as expected. You don't have to create another class called Employees; as far as Entity Framework is concerned, you've already told it to create a table called Employees. This is why
I think the step you're missing is defining your DbSet<>.
Using your code, and running Add-Migration, this is the definition I get for the Employees table:
CreateTable(
"dbo.Employees",
c => new
{
UserID = c.Int(nullable: false),
CompanyID = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserID, t.CompanyID })
.ForeignKey("dbo.ApplicationUsers", t => t.UserID, cascadeDelete: true)
.ForeignKey("dbo.Companies", t => t.CompanyID, cascadeDelete: true)
.Index(t => t.UserID)
.Index(t => t.CompanyID);
Which seems to correlate with what you wanted.
To finish it off, add (if you haven't already) this to your ApplicationDbContext file:
public DbSet<ApplicationUser> Employees;
public DbSet<Company> Companies;
Then to add an employee, you create a new ApplicationUser and add it like
ApplicationUser user = new ApplicationUser();
// do whatever here to give it the right data
ApplicationDbContext ctx = new ApplicationDbContext();
ctx.Employees.Add(user);
The Employees table itself you shouldn't ever have to interact with.
EF will manage that you don't need to insert into the mapping table directly, have a look at this sample that I have in my project:
public class Organization : Entity<int>
{
public string Name { get; set; }
public string Address { get; set; }
public string MainContact { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
//navigation property
public virtual ICollection<DevelopmentalGoal> DevelopmentalGoals { get; set; }
public virtual ICollection<ServiceActivity> ServiceActivities { get; set; }
}
public class DevelopmentalGoal : Entity<int>
{
public string Name { get; set; }
public string Icon { get; set; }
//navigation property
public virtual ICollection<Organization> Organizations { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Organization>().ToTable("Organization", "ServiceLearning")
.HasKey(t => t.ID);
modelBuilder.Entity<DevelopmentalGoal>().ToTable("DevelopmentalGoal", "ServiceLearning")
.HasKey(t => t.ID);
modelBuilder.Entity<Organization>()
.HasMany(t => t.DevelopmentalGoals)
.WithMany(t=> t.Organizations)
.Map(m =>
{
m.ToTable("OrganizationDevelopmentalGoal", "ServiceLearning");
m.MapLeftKey("OrganizationID");
m.MapRightKey("DevelopmentalGoalID");
});
}
public int SaveOrganization(OrganizationViewModel viewModel, IUserContext currentUser)
{
Organization organization;
{
if (viewModel.ID == 0)
{
organization = ObjectMapper.MapTo<Organization>(viewModel);
_context.Set<Organization>().Add(organization);
}
else
{
organization = _context.Set<Organization>()
.SingleOrDefault(t =>
t.ID == viewModel.ID
);
organization.Name = viewModel.Name;
organization.Address = viewModel.Address;
organization.MainContact = viewModel.MainContact;
organization.Phone = viewModel.Phone;
organization.Website = viewModel.Website;
UpdateOrganizationDevelopmentalGoals(organization, viewModel);
}
try
{
CommitChanges();
}
catch (DbUpdateException ex)
{
if (ex.IsDuplicateException())
throw new KeystoneDuplicateException("A Organization with the same name already exists.");
throw ex;
}
}
return organization.ID;
}
private void UpdateOrganizationDevelopmentalGoals(Organization organization, OrganizationViewModel viewModel)
{
var originalIdList = organization.DevelopmentalGoals.Select(d => d.ID).Distinct().ToList();
var modifiedIdList = viewModel.DevelopmentalGoal.Where(d => d.Selected == true).Select(d => d.ID).Distinct().ToList();
//Remove deleted Developmetal Goals.
foreach (var id in originalIdList.Except(modifiedIdList))
organization.DevelopmentalGoals.Remove(organization.DevelopmentalGoals.Single(d => d.ID == id));
//Add new Developmetal Goals.
foreach (var id in modifiedIdList.Except(originalIdList))
{
//Add director relationship without having to load entity.
var d = new DevelopmentalGoal { ID = id };
_context.Set<DevelopmentalGoal>().Attach(d);
organization.DevelopmentalGoals.Add(d);
}
}
As you can see in the UpdateOrganizationDevelopmentalGoals method I do not insert or delete data from the mapping table directly, I insert and delete from the organization.DevelopmentalGoals and as I've already defined the mapping table in fluent API on "OnModelCreating" then EF knows how to manage the relations.

Entity Framework , how to only validate specify property

I have a demo class "User" like the following:
public partial class User {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[StringLength(30)]
[Required]
public string LoginName { get; set; }
[StringLength(120)]
[Required]
[DataType(DataType.Password)]
public string Pwd { get; set; }
[StringLength(50)]
public string Phone { get; set; }
[StringLength(100)]
public string WebSite { get; set; }
...
...
}
As you can see, "LoginName" and "Pwd" are "Required".
Some time , I only want to update user's "WebSite" , So I do like this:
public void UpdateUser(User user , params string[] properties) {
this.rep.DB.Users.Attach(user);
this.rep.DB.Configuration.ValidateOnSaveEnabled = false;
var entry = this.rep.DB.Entry(user);
foreach(var prop in properties) {
var entProp = entry.Property(prop);
//var vas = entProp.GetValidationErrors();
entProp.IsModified = true;
}
this.rep.DB.SaveChanges();
this.rep.DB.Configuration.ValidateOnSaveEnabled = true;
}
Parameter "user" like this:
new User(){
ID = 1,
WebSite = "http://www.stackoverflow.com"
}
Notice , I don't specify "LoginName" and "Pwd"
This function can work fine , but I wouldn't set ValidateOnSaveEnabled to false.
Is there any way only validate "WebSite" when ValidateOnSaveEnabled is true?
Thanks.
As I know validation executed in SaveChanges always validates the whole entity. The trick to get selective validation for property is commented in your code but it is not part of the SaveChanges operation.
I get a solution.
First define PartialValidationManager:
public class PartialValidationManager {
private IDictionary<DbEntityEntry , string[]> dics = new Dictionary<DbEntityEntry , string[]>();
public void Register(DbEntityEntry entry , string[] properties) {
if(dics.ContainsKey(entry)) {
dics[entry] = properties;
} else {
dics.Add(entry , properties);
}
}
public void Remove(DbEntityEntry entry) {
dics.Remove(entry);
}
public bool IsResponsibleFor(DbEntityEntry entry) {
return dics.ContainsKey(entry);
}
public void ValidateEntity(DbEntityValidationResult result) {
var entry = result.Entry;
foreach(var prop in dics[entry]){
var errs = entry.Property(prop).GetValidationErrors();
foreach(var err in errs) {
result.ValidationErrors.Add(err);
}
}
}
}
2, Add this Manager to My DbContext:
public class XmjDB : DbContext {
public Lazy<PartialValidationManager> PartialValidation = new Lazy<PartialValidationManager>();
protected override System.Data.Entity.Validation.DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry , IDictionary<object , object> items) {
if(this.PartialValidation.Value.IsResponsibleFor(entityEntry)) {
var result = new DbEntityValidationResult(entityEntry , new List<DbValidationError>());
this.PartialValidation.Value.ValidateEntity(result);
return result;
} else
return base.ValidateEntity(entityEntry , items);
}
...
...
Update Method :
public void UpateSpecifyProperties(T t, params string[] properties) {
this.DB.Set<T>().Attach(t);
var entry = this.DB.Entry<T>(t);
this.DB.PartialValidation.Value.Register(entry , properties);
foreach(var prop in properties) {
entry.Property(prop).IsModified = true;
}
this.DB.SaveChanges();
this.DB.PartialValidation.Value.Remove(entry);
}
Ok, it work fine.