Update related data when AutoDetectChangesEnabled = false - entity-framework

I'm trying to update a simple model:
public class DayOff
{
public int Id { get; set; }
public List<User> Users { get; set; }
public DayOff()
{
Users = new List<User>();
}
}
my service layer is:
public void Update(DayOff dayOff, IEnumerable<int> users)
{
var dayOffToUpdate = _db.DayOff.Include("Users").Single(d => d.Id == dayOff.Id);
dayOffToUpdate.Users.Clear();
_db.Entry(dayOffToUpdate).CurrentValues.SetValues(dayOff);
dayOffToUpdate.Users = _db.User.Where(u => users.Contains(u.Id)).ToList();
foreach (var user in dayOffToUpdate.Users)
{
_db.Entry(user).State = EntityState.Modified;
}
_db.SaveChanges();
}
but it is NOT updating the users, so how can I?

Related

How to insert payload data in many-to-many relationships with EF Core 5

I have this relationship between Licitadores and Ofertas
public class Licitador
{
public int Id { get; set; }
public string Nombre { get; set; }
[StringLength(maximumLength: 15)]
public string CodigoSAP { get; set; }
public List<Oferta> Ofertas { get; set; } = new List<Oferta>();
}
public class Oferta
{
[StringLength(maximumLength:6)]
public string Id { get; set; }
[StringLength(maximumLength: 5)]
public string IdPresentada { get; set; }
....
public List<Licitador> Licitadores { get; set; } = new List<Licitador>();
}
And the join table in the context
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LicitacionesEnSolitario>().ToTable("LicitacionesSolitario");
modelBuilder.Entity<Licitador>()
.HasMany(o => o.Ofertas)
.WithMany(of => of.Licitadores)
.UsingEntity<LicitacionesEnSolitario>
(oo => oo.HasOne<Oferta>().WithMany(),
oo => oo.HasOne<Licitador>().WithMany())
.Property(oo => oo.Adjudicado)
.IsRequired();
}
I need this data in my entity/table LicitacionesEnSolitario in addition to PK y FK
public class LicitacionesEnSolitario
{
public int LicitadorId { get; set; }
public string OfertaId { get; set; }
public bool Adjudicado { get; set; }
public string Plazo { get; set; }
public decimal PresupuestoOfertado { get; set; }
public DateTime? FechaAdjudicacion { get; set; }
}
Here I insert the data importing them from another database
public int ImportarLicitacionesEnSolitario()
{
try
{
int registrosAñadidos = 0;
var registrosSAP = _contextSAP.LicitacionesEnSolitario
.FromSqlRaw("sql")
.ToList();
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
oferta.Licitadores.Add(licitador);
registrosAñadidos +=1;
}
_contextBoletus.SaveChanges();
return registrosAñadidos;
}
catch (Exception ex)
{
throw ex;
}
}
This works fine and insert data in "LicitacionesEnSolitario" but with this fields Adjudicado, Plazo, PresupuestoPfertado y FechaAdjudicacion with nulls.
I don't know how to insert them at the time I insert Licitadores and if I try to update after the Add method using the PKs I just added
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
oferta.Licitadores.Add(licitador);
var ls = _contextBoletus.Set<LicitacionesEnSolitario>()
.SingleOrDefault(ls => ls.OfertaId == oferta.Id & ls.LicitadorId == licitador.Id);
ls.Adjudicado = registroSAP.Adjudicado;
ls.PresupuestoOfertado = registroSAP.PresupuestoOfertado;
ls.FechaAdjudicacion = registroSAP.FechaAdjudicacion;
registrosAñadidos +=1;
}
_contextBoletus.SaveChanges();
return registrosAñadidos;
I get this error System.NullReferenceException: Object reference not set to an instance of an object.
Any idea, please?
Thanks
This is the best way I found
foreach (var registroSAP in registrosSAP)
{
var oferta = _contextBoletus.Ofertas.Find(registroSAP.OfertaId);
var licitador = _contextBoletus.Licitadores.Where(l => l.CodigoSAP == registroSAP.CodigoSAP).FirstOrDefault();
var ls = _contextBoletus.Set<LicitacionesEnSolitario>().Add(
new LicitacionesEnSolitario
{
LicitadorId = licitador.Id,
OfertaId = oferta.Id,
Adjudicado = registroSAP.Adjudicado,
Plazo = registroSAP.Plazo,
PresupuestoOfertado = registroSAP.PresupuestoOfertado,
FechaAdjudicacion = registroSAP.FechaAdjudicacion
});
registrosAñadidos += 1;
}
Thanks

issue when adding an item to a many to many relationship .net core

i have a course management app and i have an issue when i try to add a student to a course studentsList.
when save changes run the student list changes from a list which contains old elements+ the new element to a list with only the new element (the last student added) and then saves it to database.
using EntityFramework 5.0.0
relationship declaration and seeding in context OnModelCreating()
modelBuilder.Entity<Course>().HasMany<Student>(c => c.StudentsList).WithMany(s => s.Courses).UsingEntity(t => t.HasData(
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id1"},
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id2"},
new { CoursesCourseId = Convert.ToInt64(1), StudentsListStudentId="id3"}
));
models
public class Course
{
public long CourseId { get; set; }
public string CourseName { get; set; }
public IEnumerable<Lesson> LessonsList { get; set; }
public DateTime CourseStartDate { get; set; }
public DateTime CourseEndDate { get; set; }
public List<Student> StudentsList { get; set; }
public class Student
{
[Key]
public string StudentId { get; set; }
public List<Course> Courses { get; set; }
}
controller action
[HttpPatch("{id}")]
public async Task<IActionResult> RegisterStudentToCourse(long id, [FromBody] List<string> studentsIds)
{
Course course = await _courseRepo.GetCourse(id);
if (course == null || !studentsIds.Any())
return BadRequest();
IEnumerable<Lesson> list = await _lessonRepo.GetLessons(id);
List<Lesson> lessonsList = list.ToList();
List<Student> studentsList = course.StudentsList.ToList();
foreach (string studentId in studentsIds)
{
Student student = _studetRepo.GetStudent(studentId);
if (student == null)
return BadRequest();
if (studentsList.Contains(student))
continue;
//HERE IS THE CALL TO THE REPOSITORY ADDSTUDENT
await _courseRepo.AddStudent(id, studentId);
foreach (Lesson l in lessonsList)
{
Attendance attendance = await _attendanceRepo.CreateAttandance(studentId);
await _lessonRepo.AddAttendance(l.LessonId, attendance);
}
}
_context.SaveChanges();
return Ok();
}
repository function
public async Task<bool> AddStudent(long id, string studentId)
{
Course course = await _context.Courses.Include(c => c.StudentsList).FirstOrDefaultAsync(c => c.CourseId == id);
Student student = await _context.Students.FirstOrDefaultAsync(s => s.StudentId == studentId);
if (course == null || student==null)
return false;
course.StudentsList.Add(student);
return await _context.SaveChangesAsync() > 0;
}

Entity Framework Core 2.0: Error when enumerating over query with group

I have the following method which simply iterates over orders grouped by client ID.
static void LinqWithInto()
{
using (var db = new EFContext())
{
var orders = db.Orders.Include(o => o.Client);
orders.Load();
var query = from order in orders
group order by order.ClientId into g
select new { ClientId = g.Key, Count = g.Count(), Orders = g };
foreach (var group in query)
{
WriteLine($"Client Id: {group.ClientId}, Number of orders: {group.Count}");
foreach (var order in group.Orders)
WriteLine($"\tOrder Id: {order.OrderId}, Client Id: {order.Client.ClientId}, Client Name: " +
$"{order.Client.Name} Payment: {order.Payment}");
}
}
}
The query fetches orders with associated clients:
[Table("Order")]
public class Order
{
public int OrderId { get; set; }
public int ClientId { get; set; }
public double Payment { get; set; }
[ForeignKey("ClientId")]
public Client Client { get; set; }
}
[Table("Client")]
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public List<Order> Orders { get; set; }
}
The code works well in EF6, but in EF Core I get the following error (the variable query in foreach loop is highlighted):
System.ArgumentException: 'Expression of type 'System.Object' cannot be used for parameter of type 'Microsoft.EntityFrameworkCore.Metadata.IEntityType' of method 'Void StartTracking(System.Object, Microsoft.EntityFrameworkCore.Metadata.IEntityType)''
I wonder what's wrong here?
This seems to be a bug in EF Core 2.0 which got addressed in this issue (Contains workaround):
https://github.com/aspnet/EntityFrameworkCore/issues/9551
You can get the testfeed 2.0.3 here (Contains fix):
https://github.com/aspnet/Announcements/issues/274
Here is a repro (which you should post over at GitHub), and a workaround. As Ivan said, that code doesn't make much sense, as the grouping query will still be sent to the database.
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Collections.Generic;
namespace efCoreTest
{
[Table("Order")]
public class Order
{
public int OrderId { get; set; }
public int ClientId { get; set; }
public double Payment { get; set; }
[ForeignKey("ClientId")]
public Client Client { get; set; }
}
[Table("Client")]
public class Client
{
public int ClientId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public List<Order> Orders { get; set; } = new List<Order>();
}
class Db : DbContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.;Database=efCoreTest;Integrated Security=true");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
for (int i = 0; i < 10; i++)
{
var client = new Client() { Address = "Address", Name = $"Client{i}" };
db.Clients.Add(client);
for (int j = 0; j < 5; j++)
{
var order = new Order() { Client = client, Payment = 20 };
db.Orders.Add(order);
}
}
db.SaveChanges();
}
using (var db = new Db())
{
//works
var orders = db.Orders.Include(o => o.Client).ToList() ;
//fails
//var orders = db.Orders.Include(o => o.Client);
// orders.Load();
var query = from order in orders
group order by order.ClientId into g
select new { ClientId = g.Key, Count = g.Count(), Orders = g };
foreach (var group in query)
{
Console.WriteLine($"Client Id: {group.ClientId}, Number of orders: {group.Count}");
foreach (var order in group.Orders)
Console.WriteLine($"\tOrder Id: {order.OrderId}, Client Id: {order.Client.ClientId}, Client Name: " +
$"{order.Client.Name} Payment: {order.Payment}");
}
Console.WriteLine("Complete");
Console.ReadKey();
}
}
}
}

EF Code First mapping for collection

I'm using EF 4.1 RC Code first. I have a many to many relation working with a composite PK in the junction table Friends. We explicitly need a separate Friends class (don't ask) which represents our junction table. Our goal is to be able to control the delete process from the User entity. Please read this before reading the rest: http://mocella.blogspot.com/2010/01/entity-framework-v4-object-graph.html. So, we managed to create our composite PK but this broke our mapping for the collection. The question is how to map FriendsCol?
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollecion<Friends> FriendsCol { get; set; }
}
public class Friends
{
public int User1Id { get; set; }
public int User2Id { get; set; }
public User User1 { get; set; }
public User User2 { get; set; }
}
Have a composite key mapping
public class FriendsMap : EntityTypeConfiguration<Friends>
{
HasKey(m => new { m.userId1 , m.userId2 });
//this.HasRequired(x => x.User1)
//.WithMany()
//.HasForeignKey(x => x.User1Id)
//.WillCascadeOnDelete(false);
//this.HasRequired(x => x.User2)
// .WithMany()
// .HasForeignKey(x => x.User2Id)
// .WillCascadeOnDelete(false);
}
public class UserMap : EntityTypeConfiguration<UserNew>
{
public UserMap()
{
ToTable("users");
Property(user => user.Name).HasColumnName("name");
// HasMany<Friends>(user => user.FriendsCol).WithMany();
}
}
What about this:
public class FriendsMap : EntityTypeConfiguration<Friends>
{
HasKey(m => new { m.userId1 , m.userId2 });
this.HasRequired(x => x.User1)
.WithMany()
.HasForeignKey(x => x.User1Id)
.WillCascadeOnDelete(false);
this.HasRequired(x => x.User2)
.WithMany(u => u.FriendsCol)
.HasForeignKey(x => x.User2Id)
.WillCascadeOnDelete(false);
}
public class UserMap : EntityTypeConfiguration<UserNew>
{
public UserMap()
{
ToTable("users");
Property(user => user.Name).HasColumnName("name");
}
}
Edit:
I just made very simple example and it works without any problem:
class Program
{
static void Main(string[] args)
{
using (var context = new Context())
{
context.Database.Delete();
context.Database.CreateIfNotExists();
var u1 = new User() { Name = "A" };
var u2 = new User() { Name = "B" };
var u3 = new User() { Name = "C" };
var f1 = new Friends() { User1 = u1, User2 = u2};
var f2 = new Friends() { User1 = u1, User2 = u3 };
context.Friends.Add(f1);
context.Friends.Add(f2);
context.SaveChanges();
}
}
}
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Friends> FriendsCol { get; set; }
}
public class Friends
{
public int User1Id { get; set; }
public int User2Id { get; set; }
public User User1 { get; set; }
public User User2 { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Friends> Friends { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Friends>()
.HasKey(m => new { m.User1Id, m.User2Id });
modelBuilder.Entity<Friends>()
.HasRequired(x => x.User1)
.WithMany()
.HasForeignKey(x => x.User1Id)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Friends>()
.HasRequired(x => x.User2)
.WithMany(u => u.FriendsCol)
.HasForeignKey(x => x.User2Id)
.WillCascadeOnDelete(false);
}
}
Ok, here is what really should happen:
class Program
{
static void Main(string[] args)
{
int id1;
int id2;
using (var context = new Context())
{
context.Database.Delete();
context.Database.CreateIfNotExists();
var u1 = new User() { Name = "A" };
var u2 = new User() { Name = "B" };
var u3 = new User() { Name = "C" };
var f1 = new Friends() { User1 = u1, User2 = u2 };
var f2 = new Friends() { User1 = u1, User2 = u3 };
u1.FriendsCol.Add(f1);
u1.FriendsCol.Add(f2);
context.SaveChanges();
id1 = u1.Id;
id2 = u2.Id;
}
using (var context = new Context())
{
var u1 = context.Users.Find(id1);
var friendsToRemove = u1.FriendsCol.Where(f => f.User2.Id == id2).ToList();
foreach (var friend in friendsToRemove)
{
u1.FriendsCol.Remove(friend);
}
context.SaveChanges();
}
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Friends> FriendsCol { get; set; }
public User()
{
FriendsCol = new List<Friends>();
}
}
public class Friends
{
public int User1Id { get; set; }
public int User2Id { get; set; }
public User User1 { get; set; }
public User User2 { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Friends> Friends { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Friends>()
.HasKey(m => new { m.User1Id, m.User2Id });
modelBuilder.Entity<Friends>()
.HasRequired(x => x.User1)
.WithMany()
.HasForeignKey(x => x.User1Id);
modelBuilder.Entity<Friends>()
.HasRequired(x => x.User2)
.WithMany(u => u.FriendsCol)
.HasForeignKey(x => x.User2Id);
}
}
Here is another fail to delete related entities. And this is the error: *A relationship from the 'Order_Lines' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a corresponding 'Order_Lines_Target' must also in the 'Deleted' state.*
class Program
{
static void Main(string[] args)
{
int orderid1;
int Lineid2;
using (var context = new Context())
{
var u1 = new Order() { Name = "A" };
var l1 = new OrderLine() { Name = "L1" };
var l2 = new OrderLine() { Name = "L2" };
u1.Lines.Add(l1);
u1.Lines.Add(l2);
context.Orders.Add(u1);
context.SaveChanges();
Orderid1 = u1.Id;
Lineid2 = l2.Id;
}
using (var context = new Context())
{
var u1 = context.Orders.Find(Orderid1);
foreach (var item in u1.Lines)
{
if (item.Id == Lineid2)
{
u1.Lines.Remove(item);
break;
}
}
context.SaveChanges();
}
}
}
public class OrderLine
{
public int Id { get; set; }
public string Name { get; set; }
public Order Order { get; set; }
}
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<OrderLine> Lines { get; set; }
public Order()
{
Lines = new List<OrderLine>();
}
}
public class Context : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> OrderLiness { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasMany<OrderLine>(o => o.Lines).WithRequired(l => l.Order);
}
}

EF 4 Feature CTP 4 - Associate Many to Many

I am using EF 4 Feature CTP 4.
I have the following database schema:
[Users] 1-M [UserRoles] M-1 [Roles]
I have a User and Role class (both POCOs).
When I try to associate an existing role to a user, a new record is getting inserted in the Roles table, instead of only inserting a new record in UserRoles.
So say I have User 1 and want to Associate with Role 2. When I try to save, I end up with a new record in Roles named "Role 2" with a record in UserRoles to the newly created Role. From the code below I was expecting only a new record in UserRoles with the mapping between User and Role.
Here is my POCO classes, mapping and test.
POCO Classes:
public class User {
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public byte[] DataVersion { get; set; }
private List<Role> roles = new List<Role>();
public virtual IList<Role> Roles {
get {
return roles;
}
}
}
public class Role {
public int Id { get; set; }
public string Name { get; set; }
public List<User> Users { get; set; }
}
Mapping:
public class UserConfiguration : EntityConfiguration<Domain.User> {
public UserConfiguration() {
this.MapSingleType(user => new {
UserId = user.Id,
FirstName = user.FirstName,
LastName = user.LastName,
DataVersion = user.DataVersion
});
this.Property(u => u.DataVersion).IsConcurrencyToken().HasStoreType("timestamp").StoreGeneratedPattern = StoreGeneratedPattern.Computed;
//Users <--> Roles
this.HasMany(u => u.Roles).WithMany(r => r.Users)
.Map("UserRoles", (u, r) => new {
UserId = u.Id,
RoleId = r.Id
});
}
}
public class RoleConfiguration : EntityConfiguration<Domain.Role> {
public RoleConfiguration() {
this.MapSingleType(role => new {
RoleId = role.Id,
RoleName = role.Name
});
}
}
Test:
public void AssignRoleTest() {
var userId = 1;
User user;
var userRepo = new UsersRepository();
user = userRepo.GetUser(userId);
var roleRepo = new RolesRepository();
var roleId = 2;
var role = roleRepo.GetRole(roleId);
user.Roles.Add(role);
userRepo.SaveUser(user);
}
Code for Repository Save:
public void SaveUser(User user) {
if (user.Id > 0) {
dbContext.Users.Attach(user);
dbContext.MarkAsModified(user);
}
else {
dbContext.Users.Add(user);
}
Try to use context.DetectChanges() in your SaveUser method. It should work because you are using same context for both loading and saving entity graph.