Entity Framework incorrect seed - c#

I have 2 simple model classes:
public class Category
{
public int Id { get; set; }
public string Description { get; set; }
public ICollection<SubCategory> SubCategories { get; set; }
}
public class SubCategory
{
public int Id { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
}
And a simple context:
public class SeederContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<SubCategory> SubCategories { get; set; }
}
I'm trying to seed the following csv files
BetCategories.csv:
Id,Description
1,Sports
2,Politics
BetSubCategories:
Id,Category_Id,Description
1,1,Soccer
2,1,Basketball
3,1,Tennis
4,1,Poker
5,2,Election
I built a seeder using CsvHelper:
public void Seed(SeederContext context)
{
SeedTable<Category>("../../Data/BetCategories.csv", t =>
{
context.Categories.AddOrUpdate(c => c.Id, t);
context.SaveChanges();
});
SeedTable<SubCategory>("../../Data/BetSubCategories.csv", t =>
{
context.SubCategories.AddOrUpdate(c => new { c.Description}, t);
context.SaveChanges();
});
}
public void SeedTable<T>(string path, Action<T[]> callback)
{
var filename = path;
if (filename != null)
using (var reader = new StreamReader(filename, Encoding.UTF8))
{
var csvReader = new CsvReader(reader);
csvReader.Configuration.WillThrowOnMissingField = false;
var list = csvReader.GetRecords<T>().ToArray();
callback(list);
}
}
The problem occurs when seeding the sub categories.
On the first seed (Categories) it goes OK and 2 categories written to the database.
On the second seed, the SubCategories goes OK and 5 sub categories are written to the database. BUT, they are also written to the Categories table - so now I have 2+5=7 categories in total.
I'm not sure what I'm doing wrong.
This is a very simple code-first with no configuration at all.
Please advise.
Small update
when doing the seed manually - it works
SeedTable<Category>("../../Data/BetCategories.csv", t =>
{
context.Categories.AddOrUpdate(c => c.Id, t);
context.SaveChanges();
});
var subCategory = new SubCategory
{
Category = context.Categories.FirstOrDefault(c => c.Description == "Sports"),
Description = "Soccer"
};
context.SubCategories.AddOrUpdate(c => c.Description, subCategory);
context.SaveChanges();
subCategory = new SubCategory
{
Category = context.Categories.FirstOrDefault(c => c.Description == "Sports"),
Description = "Basketball"
};
context.SubCategories.AddOrUpdate(c => c.Description, subCategory);
context.SaveChanges();

I wonder if this will solve
public class SubCategory
{
public int Id { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public string Description { get; set; }
public virtual Category Category { get; set; }
}

Related

How to return objects from many to many relation Db - entityframework

Im trying to return a List of products of a specific user by user Id, but that seems to not work
My Product class
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public List<User>? Users { get; set; }
}
My user class
public class User
{
[Key]
public Guid Id { get; set; }
[Required] [MaxLength(15)]
public string Username { get; set; }
[Required]
public string Name { get; set; }
public string Surname { get; set; }
public string PasswordHash { get; set; }
public string Salt { get; set; }
public List<Product>? Products { get; set; }
}
So im adding and Product to Db, this is working
And then im adding the product to Order by this method
Guid id is a user's id
public void AddProduct(Product product, Guid id)
{
var user = _context.Users.First(u => u.Id == id);
var p = _context.Products.First(p => p.Id == product.Id);
if (user.Products == null || p.Users == null)
{
user.Products = new List<Product>();
p.Users = new List<User>();
}
user.Products.Add(p);
p.Users.Add(user);
_context.SaveChanges();
}
And this also seems to work:
image of ProductUser table from db
So how can I return a List of Products which specific user have?
I've tried this:
private Order BuildOrder(Guid id)
{
var user = _context.Users.First(u => u.Id == id);
/*if (user.Products is null)
{
user.Products = new List<Product>();
}*/
var x = _context.Products.Where(p => p.Id == 1);
/*
var products = user.Products.ToList();*/
var order = new Order
{
Products = x.ToList()
};
return order;
But this is returning me null, like Adding Products is not working
Result of this method
Order class:
public class Order
{
public List<Product> Products { get; set; }
}
DbContext:
using Application.Api.Models;
using Microsoft.EntityFrameworkCore;
namespace Application.Api.Data;
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseSerialColumns();
modelBuilder.Entity<User>(eb =>
{
eb.HasMany(u => u.Products).WithMany(p => p.Users);
});
}
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
}
If that's not enough informations comment what I need to add
you need to explicitly include the Products for the user
var speceficUserWithProducts = context.Users.Include(u => u.Products).FirstOrDefault(u => u.Id == id);

EF Core 5 single property projection

I want to reduce duplicated code. In order to achieve that I want to reference the projections of my Entities.
Entities
public class Category
{
public string Id { get; set; }
public string CategoryName { get; set; }
public static Expression<Func<Category, Category>> Proj() => c => new Category
{
CategoryName = c.CategoryName
};
}
public class Image
{
public string Id { get; set; }
public string Url { get; set; }
public static Expression<Func<Image, Image>> Proj() => i => new Image
{
Url = i.Url
};
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Image> Images { get; set; }
public Category Category { get; set; }
}
Projection Query
var categoryProjection = Category.Proj().Compile();
var products = _ctx.Products.Select(p => new Product
{
Id = p.Id,
Name = p.Name,
Images = p.Images.AsQueryable().Select(Image.Proj()).ToHashSet(),
Category = categoryProjection.Invoke(p.Category)
});
When I execute the projection then it will work correctly for Product and Images. But for Category the genereted SQL will contain all Columns (Id and CategoryName).

LINQ statement issue for dynamic view component menu

I have three level (Category- Subcategory - Nestedcategory) dropdown navigation menu on my website for which data must come dynamically from database. My main problem in generation of InvokeAsync() method to make it work. I can write two levels which work fine as I checked, but confused in defining Nestedcategories - need to get it from subcategories which derived from categories.
Here is my Controller
public class MenuViewComponent: ViewComponent
{
private readonly SamirDbContext _samirDbContext;
public MenuViewComponent(SamirDbContext samirDbContext)
{
_samirDbContext = samirDbContext;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var menu = await _samirDbContext.Categories.Include(x => x.Subcategories).ThenInclude(y => y.NestedCategories).
Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
**NestedCategories = ...**
}).ToListAsync();
return View(menu);
}
}
Here are models:
public class Category
{
public Category()
{
Subcategories = new HashSet<Subcategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Subcategory> Subcategories { get; set; }
}
public class Subcategory
{
public Subcategory()
{
Posts = new HashSet<Post>();
NestedCategories = new HashSet<NestedCategory>();
}
public int Id { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public ICollection<Post> Posts { get; set; }
public ICollection<NestedCategory> NestedCategories { get; set; }
}
public class NestedCategory
{
public NestedCategory()
{
Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Post> Posts { get; set; }
public Subcategory Subcategory { get; set; }
public int SubcategoryId { get; set; }
}
Menu ViewModel
public class MenusModel
{
public int Id { get; set; }
public Category Category { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Subcategory> Subcategories { get; set; }
public Subcategory Subcategory { get; set; }
public IEnumerable<NestedCategory> NestedCategories { get; set; }
public NestedCategory NestedCategory { get; set; }
}
Please, help in completion InvokeAsyinc() method in order to get work for 3 level menu.
You can use SelectMany() method, change the linq like below:
var menu = await _samirDbContext.Categories
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories,
NestedCategories = x.Subcategories.SelectMany(s => s.NestedCategories).ToList()
}).ToListAsync();
Looking at your models for Category, Subcategory, and NestedCategory, I'm asking myself why you actually could need to have a separate output property (**NestedCategories = ...**) in your final Select statement.
Let's think this way if the NestedCategory is defined inside the Subcategory collection, then every Subcategory element should have its own list of NestedCategory-ies, which will be available when you will check some Subcategory from the dropdown.
So, my advice here is to leave the result as follows:
var menu = await _samirDbContext.Categories
.Include(x => x.Subcategories)
.ThenInclude(y => y.NestedCategories)
.Select(x => new MenusModel()
{
Category = x,
Id = x.Id,
Subcategories = x.Subcategories
.Select(sb => new SubcategoryDTO
{
sb.Id,
sb.Name,
...
NestedCategories = sb.NestedCategories
.Select(nst => new NestedCategoriesDTO
{
nst.Id,
nst.Name,
...
})
}),
}).ToListAsync();
Then you can use the above model in your UI.
Hope this will help ))

How to join two tables with linq?

I am trying to join two of my tables with linq based on an id, so far unseccesfully.
Here is how my models look :
public class WorkRole
{
public int WorkRoleId { get; set; }
public string RoleName { get; set; }
public string RoleDescription { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<WorkRolesUsersDetails> WorkRolesUsersDetails { get; set; }
}
public class WorkRolesUsersDetails
{
public int WRUDId { get; set; }
public int? WorkRoleId { get; set; }
public string UserDetailsId { get; set; }
public virtual WorkRole WorkRole { get; set; }
public virtual UserDetails UserDetails { get; set; }
public DateTime FocusStart { get; set; }
public DateTime FocusEnd { get; set; }
public bool isActive { get; set; }
}
I am trying to get in one view WorkRoleId, RoleName, RoleDescription and CompanyId from the first table and UserDetailsId, FocusStart, FocusEnd and isActive from the second table.
The farthest i got with my ideas was :
var query = db.WorkRoles.Join(db.WorkRolesUsersDetails,x => x.WorkRoleId,y => y.WorkRoleId,(x, y) => new { wr = x, wrud = y });
But sadly, it didn't work. I just don't know enough linq and couldn't get much out of other questions/answers here. Please, help.
Code for joining 2 tables is:
var list = db.WorkRoles.
Join(db.WorkRolesUsersDetails,
o => o.WorkRoleId, od => od.WorkRoleId,
(o, od) => new
{
WorkRoleId= o.WorkRoleId
RoleName= o.RoleName,
RoleDescription= o.RoleDescription,
CompanyId= o.CompanyId,
WRUDId= od.WRUDId,
UserDetailsId= od.UserDetailsId,
FocusStart=od.FocusStart,
FocusEnd=od.FocusEnd
})
If you are using EF may I suggest the Includes statement it works wonders. IF you have a foreign key assigned. It basically gets the other data with it.
static void Main(string[] args)
{
using (var context = new TesterEntities())
{
var peopleOrders = context.tePerson.Include("teOrder").First(p => p.PersonId == 1).teOrder.ToList();
peopleOrders.ForEach(x => Console.WriteLine($"{x.OrderId} {x.Description}"));
}
}
Combining manually without navigation context option.
public class Student
{
public int StudentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<StudentTestScore> Scores { get; set; }
}
public class StudentTestScore
{
public int StudentID { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main(string[] args)
{
var students = new List<Student>
{
new Student { StudentID = 1, FirstName = "Brett", LastName = "X" },
new Student { StudentID = 2, FirstName = "John", LastName = "X" }
};
var grades = new List<StudentTestScore> { new StudentTestScore { StudentID = 1, Score = 98 } };
var combined = students.Join(grades, x => x.StudentID, y => y.StudentID,
(x, y) => new
{
Student = $"{x.FirstName} {x.LastName}",
Grade = y.Score
}).ToList();
combined.ForEach(x => Console.WriteLine($"{x.Student} {x.Grade}"));
Console.ReadLine();
}

Can't create many-to-many relationship

I have two entites with a many-to-many relationship. Company and SearchKeyword.
Here are the models:
class SearchKeyword
{
public int ID { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
class Company
{
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}
I am trying to add a SearchKeyword to a company but it won't let me. I tried this:
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
It says Object reference not set to an instance of an object. I am not sure what is null. In inspector I can see c and sk both have full values. I guess I must be missing a fundamental of how the many-to-many relationship works with EF.
What am I doing wrong?
This is because SearchKeywords is null.
Either you can assign a List to it before adding a new instance
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords = new List<SearchKeyword>();
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
Or you can do it constructor method
class Company
{
public Company()
{
SearchKeywords = new List<SearchKeyword>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}

Categories

Resources