I did not specify the DbSet in my applicationdbcontext.
However, I am able to create order payments using the following method:
public List<OrderPaymentDto> Create(CreateOrderPaymentDto createInput)
{
if (createInput == null) return null;
var orderTotalPrice = this.orderRepository.GetSingleAsync(o => o.Id == createInput.OrderId).Await()?.Price;
if (orderTotalPrice == null)
{
throw new NotFoundException($"An order with an id {createInput.OrderId} has not been found! ");
}
var list = new List<OrderPaymentDto>();
if (createInput.OrderPaymentsTemplateGroupId != null && createInput.OrderPaymentsTemplateGroupId != 0)
{
var orderTemplates = this.orderPaymentsTemplateManager.GetAll(op => op.OrderPaymentsTemplateGroupId == createInput.OrderPaymentsTemplateGroupId);
if (orderTemplates == null)
{
throw new NotFoundException("No order templates were found!");
}
//take the order repository total price
foreach (var orderTemplate in orderTemplates)
{
OrderPayment orderPaymentToBeCreated = new OrderPayment
{
Amount = ((orderTotalPrice.Value * orderTemplate.Amount) / 100),
OrderId = createInput.OrderId,
DueDate = DateTime.Now.AddDays(orderTemplate.PaymentPeriod),
PaymentType = orderTemplate.PaymentType,
Name = orderTemplate.Name
};
var addedOrderPayment = this.repository.AddAsync(orderPaymentToBeCreated).Await();
list.Add(mapper.Map<OrderPaymentDto>(addedOrderPayment));
}
}
else
{
OrderPayment orderPaymentToBeCreated = new OrderPayment
{
Amount = createInput.Amount,
OrderId = createInput.OrderId,
DueDate = DateTime.Now.AddDays(createInput.PaymentPeriod),
PaymentType = createInput.PaymentType,
Name = createInput.Name
};
var addedOrderPayment = this.repository.AddAsync(orderPaymentToBeCreated).Await();
list.Add(mapper.Map<OrderPaymentDto>(addedOrderPayment));
}
this.notificationService.OnCreateEntity("OrderPayment", list);
return list;
}
the repository addasync method is this:
public async Task<TEntity> AddAsync(TEntity entity)
{
ObjectCheck.EntityCheck(entity);
await dbContext.Set<TEntity>().AddAsync(entity);
await dbContext.SaveChangesAsync();
return entity;
}
The table itself is created in PostGre, I am able to create entities.
What is the point of including them in the ApplicationDbContext?
The model itself has a reference to Order which has a dbset in the ApplicationDbContext. If entities are related can I just include one db set and not the rest?
My previous understanding of a DBSet is that it is used to have crud operations on the database. Now my understanding is challenged.
Can someone please clarify?
My colleague helped me find an answer.
https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro?view=aspnetcore-3.1
In this documentation in the section of Creating the DbContext example :
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}
This code creates a DbSet property for each entity set. In Entity Framework terminology, an entity set typically corresponds to a database table, and an entity corresponds to a row in the table.
You could've omitted the DbSet (Enrollment) and DbSet(Course) statements and it would work the same. The Entity Framework would include them implicitly because the Student entity references the Enrollment entity and the Enrollment entity references the Course entity.
Related
I am trying to link Shipments to Road using a clean database, with fresh data unlinked, first time trying to relate these 2 entities.
public class Road
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ShipmentMode ShipmentMode { get; set; }
public string RoadName { get; set; }
public virtual List<Shipment> Shipments { get; set; }
}
public void SaveToDatabase()
{
using (var db = new DbContext())
{
foreach (var road in this.Roads)
{
road.Shipments.ForEach(shipment => shipment = db.Shipments.FirstOrDefault(s => s.Id == shipment.Id));
var input = db.Roads.Add(road);
}
db.SaveChanges();
}
}
At the line var input = db.Roads.Add(road); it will throw the error Message "The instance of entity type 'Shipment' cannot be tracked because another instance with the key value '{Id: 46}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'.
I had this error before, fixed it but I rearranged the code and now it's back to throwing this error. I am just trying to get Shipments to link to Road.
This code below has worked for me. If anyone has a better solution, please feel free to post.
public void SaveToDatabase()
{
using (var db = new DbContext())
{
foreach (var road in this.Roads)
{
var shipments = road.Shipments;
road.Shipments = null;
var input = db.Roads.Add(road);
db.SaveChanges();
var getRoad = db.Lanes.Include(p => p.Shipments).FirstOrDefault(t => t.Id == input.Entity.Id);
getRoad.Shipments.AddRange(shipments);
db.SaveChanges();
}
}
}
Alternative solution
public void SaveToDatabase()
{
using (var db = new DbContext())
{
foreach (var road in this.Roads)
{
db.UpdateRange(road.Shipments);
var input = db.Roads.Add(road);
db.SaveChanges();
}
}
}
I cannot get a table to update correctly that should be linking two of my entities. To explain in more detail...I have two entities, Class and Teacher, with a relationship in the form of:
Teacher can be assigned to many classes
Class can only have one teacher.
Below are these two entities.
public class Teacher
{
[Required, Key]
public Guid Id { get; private set; }
[StringLength(50)]
public string Name { get; set; }
public string Email { get; set; }
public List<Class> Classes = new List<Class>();
public Teacher()
{
Id = new Guid();
}
public Teacher(Guid id)
{
Id = id;
}
public void AssignClass(Class newClass)
{
Classes.Add(newClass);
}
}
public class Class
{
[Required, Key]
public Guid Id { get; private set; }
[Required, StringLength(20)]
public string Name { get; set; }
[Required, Range(5, 30)]
public int Capacity { get; set; }
public Teacher Teacher { get; set; }
public IEnumerable<StudentClass> StudentClasses { get; set; }
public Class()
{
Id = new Guid();
}
public Class(Guid id)
{
Id = id;
}
}
When I generate my migrations I get a foreign key of TeacherId in the Classes table as expected. Here is the SQL:
CREATE TABLE [dbo].[Classes] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (20) NOT NULL,
[Capacity] INT NOT NULL,
[TeacherId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Classes] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Classes_Teachers_TeacherId] FOREIGN KEY ([TeacherId]) REFERENCES [dbo].[Teachers] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Classes_TeacherId]
ON [dbo].[Classes]([TeacherId] ASC);
My class derived of DBContext looks like:
public class SchoolDatabaseContext : DbContext
{
public DbSet<Student> Students { get; private set; }
public DbSet<Class> Classes { get; private set; }
public DbSet<Teacher> Teachers { get; private set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public SchoolDatabaseContext(DbContextOptions<SchoolDatabaseContext> options) : base(options)
{
}
}
No configuration for those entities yet. I use DI to serve the DbContext to the controller and that all seems fine.
I have aimed for a DDD type structure, but to make this issue easier to debug I have stripped everything all the way back to the controller so it is basically... controller => DbContext.
Here is my code in the controller:
[HttpPost]
[Route("assign-teacher-to-class")]
public async Task<IActionResult> AssignClass([FromBody] AssignTeacherToClass assignTeacherToClass)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var teacher = await schoolDatabaseContext.Teachers.FindAsync(assignTeacherToClass.TeacherId);
var classToAssign = await schoolDatabaseContext.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
schoolDatabaseContext.Entry(teacher).State = EntityState.Modified;
await schoolDatabaseContext.SaveChangesAsync();
return Ok(teacher);
}
When I debug through the ids are fine from the post body, they are assigned correctly to the DTO AssignClass and the calls to the DbContext to find the data for each type (teacher and class) are fine. I then call a method in my teacher type to add the class to the List Classes property (see teachers entity code at beginning for reference), I then Save the changes with the DbContext method and Problem Defined Here: at no stage does the TeacherId in the database update whilst debugging/completing. I have tried all I can think of like instantiating collections in different ways, changing collection types, looking for config that might help map these entities in this way, stripping out all extra layers, changing accessibility of properties and classes and few more.
Any help would really be appreciated as I am getting a bit defeated on this one and I feel like this relationship should be fairly straight forward. I actually was able to get my many to many working with a bridge class so I was surprised to get stuck on this one :(
Thanks
try this:
var teacher = await schoolDatabaseContext.Teachers.Include(x => x.Classes).SingleOrDefaultAsync(x => x.Id == assignTeacherToClass.TeacherId);
I don't think teacher.Classes gets tracked by DbContext otherwise.
There are multiple ways to accomplish this with EF Core. It is easiest to find if you call it what the docs call it "Related Data".
Here is the parent doc: Related Data
Specifically as #Y Stroli has illustrated the Eager Loading method.
The below example is shown on the eager loading reference to load multiple levels of related data:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.ToList();
}
As of EF Core 5.0 you can also do filtered includes:
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
As the suggestion from lvan, you should change public List<Class> Classes = new List<Class>(); to public List<Class> Classes { get; set; } = new List<Class>();.
For your current code, it seems you want to add Class and return the teacher, if so, you need to include the exsiting classes to teacher like below, otherwise, it will only return the new adding class.
public async Task<IActionResult> AssignClass()
{
var assignTeacherToClass = new AssignTeacherToClass {
TeacherId = new Guid("52abe5e0-bcd4-4827-893a-26b24ca7b1c4"),
ClassId =new Guid("50354c76-c9e8-4fc3-a7c9-7644d47a6854")
};
var teacher = await _context.Teachers.Include(t => t.Classes).FirstOrDefaultAsync(t => t.Id == assignTeacherToClass.TeacherId);
var classToAssign = await _context.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
_context.Entry(teacher).State = EntityState.Modified;
await _context.SaveChangesAsync();
return Ok(teacher);
}
One more note, you need to configure SerializerSettings.ReferenceLoopHandling like
services.AddMvc()
.AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You need to define the connection between Teacher and Class.
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Class>()
.HasOne<Teacher>(p => p.Teacher)
.WithMany(q => q.Classes)
.HasForeignKey(r => r.TeacherId);
}
Also add TeacherId prop to Class.
I am trying to learn about Entity Framework 6, and I am running into an issue, that I have been able to reproduce in a test project:
A Movie has a Nameand a Revenue. A Revenue has a GrossIncome:
public class Movie
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Revenue Revenue { get; set; }
}
public class Revenue
{
[Key]
public int Id { get; set; }
public double GrossIncome { get; set; }
}
I am trying to use EF6 code-first to persist some data about movies in the database:
public class MovieContext: DbContext
{
public MovieContext(): base("name=testDB") { }
public DbSet<Movie> Movies { get; set; }
public DbSet<Revenue> Revenues { get; set; }
}
I start by inserting a new movie, with its associated revenue in the DB:
using (var context = new MovieContext())
{
Revenue revenue = new Revenue()
{
GrossIncome = 10
};
Movie movie = new Movie()
{
Name = "foo",
Revenue = revenue
};
context.Movies.Add(movie);
context.SaveChanges();
}
I can see in SQL Server that the tables are created, and that a Movies.Revenue_Id column has been created, with a foreign key relationship to Revenue.Id.
If I try to query it using SQL, it works fine:
SELECT Movies.Name, Revenues.GrossIncome
FROM Movies
LEFT JOIN Revenues ON Movies.Revenue_Id = Revenues.Id
returns
Name GrossIncome
----------------------
foo 10
However, if I try to use Entity Framework to query the data:
using (var context = new MovieContext())
{
List<Movie> movieList = context.Movies.ToList();
Console.WriteLine("Movie Name: " + movieList[0].Name);
if (movieList[0].Revenue == null)
{
Console.WriteLine("Revenue is null!");
}
else
{
Console.WriteLine(movieList[0].Revenue.GrossIncome);
}
Console.ReadLine();
}
The console reads:
Movie Name: foo <- It shows that the query works, and that the data in the main table is fetched.
Revenue is null! <- Even though the data in the DB is correct, EF does not read the data from the foreign key.
My question is simple: what am I doing wrong? How are the foreign key values supposed to be read?
Just include the child entity you want to load:
using (var context = new MovieContext())
{
List<Movie> movieList = context.Movies
.Include(m => m.Revenue) // ADD THIS INCLUDE
.ToList();
Console.WriteLine("Movie Name: " + movieList[0].Name);
if (movieList[0].Revenue == null)
{
Console.WriteLine("Revenue is null!");
}
else
{
Console.WriteLine(movieList[0].Revenue.GrossIncome);
}
Console.ReadLine();
}
This will load the movies - and also make sure that all the references to their respective .Revenue references have been loaded, too.
I am using Entity Framework (with a code-first approach) and the database is created successfully with the expected foreign keys and unique key constraints.
I have those two model classes:
public class Foo
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Bar Bar { get; set; }
}
public class Bar
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Index(IsUnique = true), StringLength(512)]
public string Url { get; set; }
}
And this application code:
var foo = GetData();
using (DatabaseContext db = new DatabaseContext())
{
db.Entry(foo).State = EntityState.Added;
if (foo.Bar != null)
{
var bar = await db.Bar.FirstOrDefaultAsync(x => x.Url == foo.Bar.Url);
if (bar != null)
{
// When I assign an existing entity ...
foo.Bar = bar;
}
}
// ... following exception will be thrown.
await db.SaveChangesAsync();
}
SqlException: Cannot insert duplicate key row in object 'dbo.Bar' with unique index 'IX_Url'. The duplicate key value is (https://api.example.com/rest/v1.0/bar/FD6FAB72-DCE2-47DB-885A-424F3D4A70B6).
The statement has been terminated.
I don't understand why Entity Framework is trying to add the navigation property Bar, even after obtaining and assigning it from the same DbContext. Similar StackOverflow questions haven't provided any working solution yet.
Please tell me if I need to provide additional information.
Did I forget to set any EF related configuration or something like that? Thank you in advance!
It's because
db.Entry(foo).State = EntityState.Added;
marks the foo.Bar (and any referenced entity not tracked by the context) as Added as well.
You should resolve the referenced entities before adding the Foo entity:
var foo = GetData();
using (DatabaseContext db = new DatabaseContext())
{
// Resolve references
if (foo.Bar != null)
{
var bar = await db.Bar.FirstOrDefaultAsync(x => x.Url == foo.Bar.Url);
if (bar != null)
foo.Bar = bar;
}
// Then add the entity
db.Entry(foo).State = EntityState.Added;
await db.SaveChangesAsync();
}
I have the following classes (I am only showing the properties that matter):
public class TypeLocation
{
[Key]
public int Id {get; set;}
public string Country {get; set;}
public string State {get; set;}
public string County {get; set;}
public string City {get; set;}
public List<Offer> Offers {get; set; }
public TypeLocation()
{
this.Offers = new List<Offer>();
}
}
public class Offer
{
public int Id { get; set; }
public ICollection<TypeLocation> LocationsToPublish { get; set; }
}
This creates the following in the database:
My Question/Problem
The TypeLocations table is prepopulated with a static list of country/state/county/city records, one or many of them can be associated with the Offer in LocationsToPublish property. When I try to add a location, using the following code, Entity Framework adds a NEW record in the TypeLocation table and then makes the association by adding a record in the OfferTypeLocations table.
public static bool AddPublishLocation(int id, List<TypeLocation> locations)
{
try
{
using (AppDbContext db = new AppDbContext())
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
}
return true;
}
catch
{
return false;
}
}
I don't want a new record added to the TypeLocations table, just a relational record creating an association in the OfferTypeLocations table. Any thoughts on what I am doing wrong?
Solution
Thanks to #Mick who answered below, I have found the solution.
public static bool AddPublishLocation(int id, List<TypeLocation> locations)
{
try
{
using (AppDbContext db = new AppDbContext())
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
//SOLUTION
TypeLocation ExistingLoc = db.AppLocations.Where(l => l.Id == loc.Id).FirstOrDefault<TypeLocation>();
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
}
return true;
}
catch
{
return false;
}
}
What happens is, using the existing AppDbContext, I retrieve an existing record from the TypeLocations table (identified here as AppLocations) and then Add it to the LocationsToPublish entity.
The key is that I was to use the current AppDbContext (wrapped with the Using() statement) for all the work. Any data outside of this context is purely informational and is used to assist in the record lookups or creation that happen within the AppDbContext context. I hope that makes sense.
The TypeLocations are being loaded from a different AppDbContext, they are deemed new entities in the AppDbContext you're constructing within your method. To fix either:-
Assuming the locations were detatched them from the instance of the AppDbContext outside of your method, you can attach these entities to the new context.
OR
Pass in the AppDbContext used to load the locations into your AddPublishLocation method.
I'd choose 2:-
public static bool AddPublishLocation(AppDbContext db, int id, List<TypeLocation> locations)
{
try
{
Offer Offer = db.Offers
.Include("LocationsToPublish")
.Where(u => u.Id == id)
.FirstOrDefault<Offer>();
//Add new locations
foreach (TypeLocation loc in locations)
{
Offer.LocationsToPublish.Add(loc);
}
db.SaveChanges();
return true;
}
catch
{
return false;
}
}