EF Core include list of list - c#

I have a very simple model.
the main class, Recipe, contains a list of RecipeItem. Every RecipeItem has a list of RecipeItemComponents.
Using the Entity Framework context, I can do this:
var ret = await _context.Recipes
.Include(x => x.RecipeItems)
.ToListAsync();
This code returns The recipes with the RecipeItems, but for every RecipeItems I have not RecipeItemsComponent list.
It makes sense since I'm not including those, but I'm, not sure no how to do it.
Thanks

Here is my working code sample
Models
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Child1> Child1s { get; set; }
}
public class Child1
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public virtual List<Child2> Child2s { get; set; }
}
public class Child2
{
public int Id { get; set; }
public string Name { get; set; }
public int Child1Id { get; set; }
public Child1 Child1 { get; set; }
}
In the DB context class
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options)
: base(options)
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().HasMany(x => x.Child1s).WithOne(x => x.Parent).HasForeignKey(x => x.ParentId);
modelBuilder.Entity<Child1>().HasMany(x => x.Child2s).WithOne(x => x.Child1).HasForeignKey(x => x.Child1Id);
this.InitialData(modelBuilder);
base.OnModelCreating(modelBuilder);
}
protected void InitialData(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().HasData(new Parent[]
{
new Parent
{
Id = 1,
Name = "Parent 1",
}
});
modelBuilder.Entity<Child1>().HasData(new Child1[]
{
new Child1
{
Id = 1,
Name = "Child 1 1",
ParentId = 1,
}
});
modelBuilder.Entity<Child2>().HasData(new Child2[]
{
new Child2
{
Id = 1,
Name = "Child 2 1",
Child1Id = 1
}
});
}
public DbSet<Parent> Parent { get; set; }
public DbSet<Child1> Child1s { get; set; }
public DbSet<Child2> Child2s { get; set; }
}
Controller
public class ParentsController : Controller
{
private readonly TestDbContext _context;
public ParentsController(TestDbContext context)
{
_context = context;
} public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var parent = await _context.Parent
.Include(x=>x.Child1s).ThenInclude(x=>x.Child2s)
.FirstOrDefaultAsync(m => m.Id == id);
if (parent == null)
{
return NotFound();
}
return View(parent);
}
}
Here is the output

You can't use the strongly typed extension methods to include everything. In some cases you need to use a string:
.Include("RecipeItems.RecipeItemsComponents")
For those curious, the documentation for this overload is here:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.include?view=efcore-2.1#Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions_Include__1_System_Linq_IQueryable___0__System_String_

Related

.Net core EF, saving records to many-to-many type table

I am new to EF and .Net core and I'm having trouble with many-to-many relationship in my project. I used microsoft documentation to setup the relationship, but i have trouble inserting any data. Project is a kanban board and i am trying to set up relations between users and tasks. Both of them already exist. The goal is to have a table with userId and taskId. Here are my models:
KanbanTask Model:
public class KanbanTask : Entity
{
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Status { get; set; }
public int ProgressStatus { get; set; }
public List<UserTask> UserTask { get; set; }
}
User Model:
public class User : Entity
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public List<UserTask> UserTask { get; set; }
}
Entity Model:
public class Entity
{
public int Id { get; set; }
}
UserTaskModel:
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int KanbanTaskId { get; set; }
public KanbanTask KanbanTask { get; set; }
}
My DbContex:
public DbSet<KanbanTask> KanbanTasks { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTask>()
.HasKey(t => new { t.UserId, t.KanbanTaskId });
modelBuilder.Entity<UserTask>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserTask)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserTask>()
.HasOne(pt => pt.KanbanTask)
.WithMany(t => t.UserTask)
.HasForeignKey(pt => pt.KanbanTaskId);
}
}
My function in service:
public async Task<ResultDTO> AssignTaskToUser(int taskId, int userId)
{
var result = new ResultDTO()
{
Response = null
};
try
{
var user = await _repo.GetSingleEntity(x => x.Id == userId);
var kanbanTask = await _taskrepo.GetSingleEntity(y => y.Id == taskId);
if (user != null && kanbanTask != null)
{
var usertask = new UserTask()
{
KanbanTaskId = taskId,
UserId = userId
};
kanbanTask.UserTask.Add(usertask);
user.UserTask.Add(usertask);
await _repo.Patch(user);
}
else
result.Response = "Task or user not found";
}
catch (Exception e)
{
result.Response = e.Message;
return result;
}
return result;
}
My repository:
public async Task Patch(T entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
Like this
var usertask = new UserTask()
{
KanbanTaskId = taskId,
UserId = userId
};
db.UserTasks.Add(usertask);
db.SaveChanges();
What you need to is to make sure that your middle entity (UserTask) always saves the Keys of both entities so I strongly suggest to add that logic in UserTask constructor.
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int KanbanTaskId { get; set; }
public KanbanTask KanbanTask { get; set; }
public UserTask() { }
public UserTask(User user, KanbanTask kanbanTask)
{
KanbanTask = kanbanTask;
KanbanTaskId = kanbanTask.Id;
User = user;
UserId = userId;
}
}
//
var usertask = new UserTask(user, kanbanTask);
kanbanTask.UserTask.Add(usertask);
user.UserTask.Add(usertask);
await _repo.Patch(user);
//
I have wrote an example for this common problem. Here https://github.com/salsita18/ManyToManyNetCore you can check the approach I took, using a single generic MiddleEntity class.
I also added it to nuget in order to reuse it, but you can just make your own implementation following the pattern

Can't add item with existing Id to the database

I have a comparsion list. I can add a product to it, but when I try to add another product to this list, I'm getting error:
The instance of entity type 'ProductToCompare' cannot be tracked because another instance with the key value '{ProductComparsionId: 13}' is already being tracked
What I am doing wrong?
Models:
public class ProductComparsion
{
public int Id { get; set; }
public int? UserId { get; set; }
public Guid SessionId { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public ICollection<ProductToCompare> ProductsToCompare { get; set; }
}
public class ProductToCompare
{
public int ProductComparsionId { get; set; }
public ProductComparsion ProductComparsion { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
EF:
public class AppDbContext : CmsDbContextBase, ILocalizedDbContext
{
public DbSet<ProductComparsion> ProductsComparsion { get; set; }
public DbSet<ProductToCompare> ProductsToCompare { get; set; }
}
public class AppDbContextModelProvider : ModelProvider
{
protected override void OnModelCreating(DbContext dbContext, ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductComparsion>(typeBuiler =>
{
typeBuiler.ToTable(nameof(AppDbContext.ProductsComparsion));
typeBuiler.HasKey(z => z.Id);
});
modelBuilder.Entity<ProductToCompare>(typeBuilder =>
{
typeBuilder.ToTable(nameof(AppDbContext.ProductsToCompare));
typeBuilder.HasKey(z => z.ProductComparsionId);
typeBuilder.HasOne(z => z.ProductComparsion).WithMany(z => z.ProductsToCompare).HasForeignKey(z => z.ProductComparsionId);
});
}
}
Adding data to DB:
public async Task<ProductComparsionVM> AddProductToComparsionList(List<int> productIds, int listId = 0)
{
var comparsionList = await _dbContext.ProductsComparsion
.AsNoTracking()
.Include(z => z.ProductsToCompare)
.FirstOrDefaultAsync(z => z.Id.Equals(listId));
Guid sessionId = default;
Guid.TryParse(_httpContextAccessor.HttpContext.Session.Id, out sessionId);
var products = _dbContext.Products.Include(z => z.ProductCategories);
foreach (var productId in productIds)
{
comparsionList.ProductsToCompare.Add(new ProductToCompare { ProductId = productId });
comparsionList.SessionId = sessionId;
var user = _userManager.GetUserAsync(_httpContextAccessor.HttpContext.User).GetAwaiter().GetResult();
comparsionList.UserId = user == null ? null : (int?)user.Id;
}
await _dbContext.AddAsync(comparsionList);
await _dbContext.SaveChangesAsync();
return null;
}
Edit 1
The key on the ProductToCompare model is configured as ProductComparisonId, and there is also a relationship configured between the ProductToCompare and the ProductComparison models on that property.
So when you add a ProductToCompare instance to ProductComparison.ProductsToCompare, the ProductToCompare instance is getting the ProductComparison instance's ProductComparisonId. Once you add more than one ProdcutToCompare to a ProductComparison you have two instances of ProductToCompare with the same ProductComparisonId... the same key. This is why you're getting the error.
Add an Id to ProductToCompare and make that the key, or maybe make the key composite between ProductId and ProductComparisonId to fix it.

How do I get all child collection with generic repository pattern?

I'm using EF Core 2.1 and I have these class in my Domain.
public class HomeSection2
{
public HomeSection2()
{
HomeSection2Detail = new List<HomeSection2Detail>();
}
public Guid ID { get; set; }
public string Title { get; set; }
public string Header { get; set; }
public List<HomeSection2Detail> HomeSection2Detail { get; set; }
}
public class HomeSection2Detail
{
public Guid ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public int? Sequence { get; set; }
public HomeSection2 HomeSection2 { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.RemovePluralizingTableNameConvention();
//HomeSection2
modelBuilder.Entity<HomeSection2>().HasKey(s => s.ID);
modelBuilder.Entity<HomeSection2>().Property(s => s.ID).ValueGeneratedOnAdd();
modelBuilder.Entity<HomeSection2>().Property(s => s.Title).IsRequired();
modelBuilder.Entity<HomeSection2>().Property(s => s.Header).IsRequired();
//HomeSection2Detail
modelBuilder.Entity<HomeSection2Detail>()
.HasOne(p => p.HomeSection2)
.WithMany(b => b.HomeSection2Detail);
modelBuilder.Entity<HomeSection2Detail>().HasKey(s => s.ID);
modelBuilder.Entity<HomeSection2Detail>().Property(s => s.ID).ValueGeneratedOnAdd();
modelBuilder.Entity<HomeSection2Detail>().Property(s => s.Title).IsRequired();
modelBuilder.Entity<HomeSection2Detail>().Property(s => s.Sequence).IsRequired();
}
And I have a generic repo
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
Context = context;
}
public IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().ToList();
}
}
When I call GetAll from the Application var obj = _uow.HomeSection2s.GetAll() like this, it won't fill the Detail.
What you mean is reffered to as 'Lazy Loading'. It would require you to make those properties virtual, like:
public virtual List<HomeSection2Detail> HomeSection2Detail { get; set; }
You can also take a look at this anwser
More documentation on loading related data

What is my mistake in implementing Foreign Key with EF 6?

I am currently in the process of learning ASP.NET MVC 5 with EF 6. Right now I am stuck with declaring a foreign key with Fluent API and then seeding data to the declared tables.
Here's the code I have:
Models:
public class Person
{
public int Id { get; set; }
public ICollection<Anime> DirectedAnimes { get; set; }
}
public class Anime
{
public int Id { get; set; }
public int DirectorId { get; set; }
public Person Director { get; set; }
}
public class AnimeDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Anime> Animes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Anime>()
.HasRequired(a => a.Director)
.WithMany(p => p.DirectedAnimes)
.HasForeignKey(p => p.DirectorId);
base.OnModelCreating(modelBuilder);
}
}
Seeding data:
public class AnimeInitializer : DropCreateDatabaseAlways<AnimeDbContext>
{
protected override void Seed(AnimeDbContext context)
{
var persons = new List<Person>
{
new Person { Id = 1 },
new Person { Id = 2 }
};
var animes = new List<Anime>
{
new Anime { DirectorId = 1 },
new Anime { DirectorId = 2 }
};
persons.ForEach(p => context.Persons.Add(p));
context.SaveChanges();
animes.ForEach(a => context.Animes.Add(a));
context.SaveChanges();
}
}
But when I fetch Anime objects they have expected DirectorId values, but their Director properties arenull:
var director = (new AnimeDbContext()).Animes.First().Director; //null
var directorId = (new AnimeDbContext()).Animes.First().DirectorId; //1
Entity framework knows about the foreign key though, because adding new Anime {DirectorId = 3} in the Seed method results in a runtime error.
I am pretty sure that my mistake is very dumb and is caused by me not following examples precisely, but I've been struggling with this problem for a while already and still can't figure it out. I would very much appreciate some help.
Your navigation-property is not virtual and thus cannot be overridden by the DynamicProxy.
Change it like this:
public class Person
{
public int Id { get; set; }
public virtual ICollection<Anime> DirectedAnimes { get; set; }
}
public class Anime
{
public int Id { get; set; }
public int DirectorId { get; set; }
public virtual Person Director { get; set; }
}

How can I combine multiple same functionality classes (controllers) into one that takes a data object as a parameter?

I have coded methods looking like this:
var result = await db.TaskStatuses
.Select(e => new
{
Id = e.TaskStatusId,
Name = e.Name
})
.ToListAsync();
and
var result = await db.ContentStatuses
.Select(e => new
{
Id = e.ContentStatusId,
Name = e.Name
})
.ToListAsync();
var result = await db.<xxx>
.Select(e => new
{
Id = e.<xxx>Id,
Name = e.Name
})
.ToListAsync();
All the methods are the same except for the database object they get data from. Is there a way that I could combine all these into one method where I just pass in the object as a parameter?
Here's how db is created:
private myContext db = new MyContext();
public partial class MyContext : DbContext
{
public DbSet<TaskStatuse> TaskStatuses { get; set; }
public DbSet<ContentStatuses> ContentStatuses { get; set; }
Here's the full code of a controller (Asp.Net Web-API):
namespace WebRole1.Controllers
{
[Route("api/ContentStatus/{id?}", Name = "api_ContentStatus")]
public class ContentStatusController : ApiController
{
private MyContext db = new MyContext();
public async Task<IHttpActionResult> GetMapData()
{
var result = await db.ContentStatuses
.Select(e => new
{
Id = e.ContentStatusId,
Name = e.Name
})
.ToListAsync();
return Ok(result);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
My problem is I have many of these controllers and they are all the same except for the data source. ContentStatus, TaskStatus etc.
Sample classes:
public class ContentStatus
{
public ContentStatus()
{
this.Contents = new List<Content>();
}
public int ContentStatusId { get; set; }
public string Name { get; set; }
public virtual ICollection<Content> Contents { get; set; }
}
public class TaskStatus
{
public TaskStatus()
{
this.Tasks = new List<Task>();
}
public int TaskStatusId { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
You can create a common interface for your classes that exposes the properties you need in your methods. You can then consume this interface in a single method that can deal with items of either type
public interface IIdentifiableStatus
{
int Id { get; }
string Name { get; }
}
public class ContentStatus : IIdentifiableStatus
{
public ContentStatus()
{
this.Contents = new List<Content>();
}
public int ContentStatusId { get; set; }
public string Name { get; set; }
public virtual ICollection<Content> Contents { get; set; }
int IIdentifiable.Id
{
get { return ContentStatusId; }
}
string IIdentifiable.Name
{
get { return Name; }
}
}
public class TaskStatus : IIdentifiableStatus
{
public TaskStatus()
{
this.Tasks = new List<Task>();
}
public int TaskStatusId { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
int IIdentifiable.Id
{
get { return TaskStatusId; }
}
string IIdentifiable.Name
{
get { return Name; }
}
}
Your method would then look like this:
public async Task<IHttpActionResult> GetMapData(IEnumerable<IIdentifiableStatus> statuses)
{
var result = await statuses
.Select(e => new {
Id = e.Id,
Name = e.Name
})
.ToListAsync();
return Ok(result);
}
You could call the method like so:
GetMapData(db.ContentStatuses)
or:
GetMapData(db.TaskStatuses)

Categories

Resources