I'm using the Entity Framework to persist and retrieve some information, I have a one-to-one relationship.
Product and Category, where a Product has a Category and a Category may have several Products.
I have the following structure
I created the two entities and made the relationship, but when I retrieve this information it brings me the information of the products however the category comes as null
Produto.cs
public class Produto
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
Categoria.cs
public class Categoria
{
[Key]
[ForeignKey("categoriaid")]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual Produto produto { get; set; }
}
ProdutoContexto.cs
public class ProdutoContexto : DbContext
{
public ProdutoContexto(DbContextOptions<ProdutoContexto> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Produto>()
.HasOne(a => a.categoria)
.WithOne(b => b.produto)
.HasForeignKey<Categoria>(b => b.id);
}
public DbSet<Produto> Produtos { get; set; }
}
CategoriaContexto.cs
public class CategoriaContexto : DbContext
{
public CategoriaContexto(DbContextOptions<CategoriaContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
}
When I run the function to retrieve the information the following json is returned
[{"id":1,"descricao":"Coca-Cola","observacao":"Coca-Cola Gelada","status":1,"valorVenda":5.50,"categoria":null}]
My Query is:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.ToListAsync();
}
Note that the category is null, how can it be done in such a way that the category is already loaded?
Category may have several Products.
Then its not one-to-one, instead its one-to-many and your model classes should be as follows:
public class Categoria
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual ICollection<Produto> produtos { get; set; }
}
public class Produto
{
[Key]
public int id { get; set; }
[ForeignKey("categoria")]
public int categoriaId {get; set;}
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
And you don't need any FluentAPI configuration. So remove the modelBuilder.Entity<Produto>() configuration. And you also don't need two different DbContext for Produto and Categoria separately. Instead make your DbContext as follows:
public class ApplicationDbContexto : DbContext
{
public ApplicationDbContexto(DbContextOptions<ApplicationDbContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
public DbSet<Produto> Produtos { get; set; }
}
And your query should be as follows:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.Include(p => p.categoria).ToListAsync();
}
Related
I want to configure a one-to-many relationship in Ef core. As you see I have a class for order and the other one for OrderItems.
I do it when I use NHibernate.of course, I consider orderItem class as ValueObject.But I want to do it using EF Core.
public class Order
{
public long Id { get; set; }
public long CustomerId { get; set; }
public DateTime OrderDateTime { get; set; }
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public string BookId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal? Discount { get; set; }
public decimal Total { get; set; }
public Order Order { get; set; }
}
you should define meta data [ForeignKey] with type of primary key in order entity. after that ef core automatically set in db by your chosen name
public class Order
{
public long Id { get; set; }
public long CustomerId { get; set; }
public DateTime OrderDateTime { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public long Id { get; set; }
[ForeignKey(nameof(OrderId)]
public virtual Order Order { get; set; }
public long OrderId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal? Discount { get; set; }
public decimal Total { get; set; }
}
Here is a simple demo like below:
1.Model:
public class Order
{
public long Id { get; set; }
public long CustomerId { get; set; }
public DateTime OrderDateTime { get; set; }
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int OrderItemId { get; set; }//you need to define a primary key for OrderItem model
public string BookId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal? Discount { get; set; }
public decimal Total { get; set; }
public Order Order { get; set; }
}
2.DbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasMany(c => c.OrderItems)
.WithOne(e => e.Order);
}
}
3.Startup.cs:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));
4.appsettings.json:
"ConnectionStrings": {
"MyDbContext": "Server=(localdb)\\mssqllocaldb;Database=DatabaseName;Trusted_Connection=True;MultipleActiveResultSets=true"
}
5.Run command line on Package Nuget Manager:
PM>add-migration init
PM>update-database
Model
public class Order {
public long Id { get; set; }
public long CustomerId { get; set; }
public DateTime OrderDateTime { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
[Key]
public int OrderItemId { get; set; }
public string BookId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal? Discount { get; set; }
public decimal Total { get; set; }
public long OrderId{get;set;} // ForeignKey OrderId
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
}
// open PackageManager console
PM>add-migration "orderItem changed"
PM>update-database
I am using EF code first with Npgsql. Everything is fine till i try to save changes using contextclassObj
public class EmployeeRepository
{
private ESAppraisalcontext DBAccessObj = null;
public EmployeeRepository()
{
DBAccessObj = new ESAppraisalcontext();
DBAccessObj.Employees.Add(new Employee { EmployeeCode = "1", EmployeeName = "shuk" });
DBAccessObj.SaveChanges();
}
}
At the point SaveChanges() it gives an exception that the Relation \ ESTables.Employee\ doesnot exist.Here is my context class :
public class ESAppraisalcontext : DbContext
{
public DbSet<Entity> Entities { get; set; }
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("ESTables");
base.OnModelCreating(modelBuilder);
}
}
And my model:
public class Employee
{
[Key]
public string EmployeeCode { get; set; }
public string EmployeeName { get; set; }
public string EmailId { get; set; }
public string DomainName { get; set; }
public int DesignationId { get; set; }
public int DepartmentId { get; set; }
public string MgrCode { get; set; }
public int LocationId { get; set; }
public DateTime DOJ { get; set; }
public bool IsActive { get; set; }
public bool IsEligibleForCycle { get; set; }
public bool IsNonEligibleApprover { get; set; }
[ForeignKey("DepartmentId")]
public Departments department { get; set; }
[ForeignKey("DesignationId")]
public Designations designation { get; set; }
[ForeignKey("LocationId")]
public Locations Loation { get; set; }
}
I have following tables: User, UserGroups, and UserInGroups. You can see them on picture below. When i call User i want to be able to get Groups that user is in (UserInGroups). I am reading materials about EntityFramework but i am unable to make connections in code to to that, what am i missing? Do i need to connect them onModelCreating?
Currently i am not getting any data from UserInGroup or UserGroups.
DB looks like this
My classes look like this:
public class User : BaseEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public UserGroup UserGroup { get; set; }
public UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new List<User>();
UserInGroups = new List<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public List<User> Users { get; set; }
public List<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public UserInGroup()
{
Users = new List<User>();
UserGroups = new List<UserGroup>();
}
public int UserId { get; set; }
public User User { get; set; }
public int UserGroupId { get; set; }
public UserGroup UserGroup { get; set; }
public List<User> Users { get; set; }
public List<UserGroup> UserGroups { get; set; }
}
DbContext:
public DbSet<GlobalSettings> GlobalSettings { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
public DbSet<UserInGroup> UsersInGroups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<GlobalSettings>().Property(x => x.Key).HasColumnAnnotation("Index", new IndexAnnotation(new[] { new IndexAttribute("Index_VariablenName") { IsClustered = false, IsUnique = true } }));
}
public abstract partial class BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
public class User : BaseEntity
{
public string Username { get; set; }
public string Title { get; set; }
public FirstName { get; set; }
public string LasName { get; set; }
public Genders Gender { get; set; }
public string Email { get; set; }
public int RoleId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual Role Role { get; set; }
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
public class UserInGroup : BaseEntity
{
public int UserId { get; set; }
public int UserGroupId { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
public virtual User User { get; set; }
public virtual UserGroup UserGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public string Name { get; set; }
public string KeyName { get; set; }
public string TechUser { get; set; }
public DateTime TechChangeDate { get; set; }
public int TechVersion { get; set; }
public bool IsDeleted { get; set; }
}
public class Role : BaseEntity
{
public string Name { get; set; }
}
public enum Genders
{
Male = 1,
Female = 2
}
You can use two methods to fill navigation properties. First is lazy-loading and second is explicit specifying of required properties.
For lazy-loading you should declare your navigation properties as virtual:
public class User : BaseEntity
{
public int RoleId { get; set; }
public virtual Role Role { get; set; }
public virtual UserGroup UserGroup { get; set; }
public virtual UserInGroup UserInGroup { get; set; }
}
public class UserGroup : BaseEntity
{
public UserGroup()
{
Users = new HashSet<User>(); // HashSet is more effective than List
UserInGroups = new HashSet<UserInGroup>();
}
[StringLength(255)]
public string Name { get; set; }
public string KeyName { get; set; }
public virtual ICollection<User> Users { get; set; } // ICollection is less restrective
public virtual ICollection<UserInGroup> UserInGroups { get; set; }
}
Now, you can load f.e. single user:
var justUser = dbContext.Users.Single(u => u.Id == 100);
When you need its properties they will by transparently loaded:
foreach (var userInGroup in user.UsersInGroups) // here is second loading
{
. . .
}
The second way is the calling of the Include method to explicit specifying required properties:
var userWithGroups = dbContext.Users
.Include(u => u.UserInGroups) // include single navigation property
.Include(ugs => ugs.Groups.Select(ug => ug.Group)) // include collection navigation property
.Single(u => u.Id == 100); // get the user with specified id and filled specified properties
My "ShoppingCart" and "ShoppingCartItems" tables are already in my database. I am trying to add a new table called "discountCodes". Each shoppingCart can have one or zero discountCodes.
The error I am receiving is: Invalid column name 'discountId'.
[Table("ShoppingCarts")]
public class ShoppingCart
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Column("cartID")]
public string cartID { get; set; }
public virtual IList<ShoppingCartItem> CartItems { get; set; }
[Column("dateCreated")]
public DateTime? DateCreated { get; set; }
[Column("userID")]
public Guid UserID { get; set; }
public int? discountId { get; set; }
public virtual Discount discount { get; set; }
}
[Table("discountCodes")]
public class Discount
{
public int discountId { get; set; }
public string discountCode{get;set;}
[Required]
public int percentOff { get; set; }
[Required]
public Boolean isActive { get; set; }
public ShoppingCart ShoppingCart { get; set; }
}
public class ShoppingCartContext : DbContext
{
public ShoppingCartContext()
: base("MYDBConnectionString")
{
Database.SetInitializer<ShoppingCartContext>(new CreateDatabaseIfNotExists<ShoppingCartContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ShoppingCart>().HasKey(t => t.cartID)
.HasOptional(t => t.discount)
.WithOptionalPrincipal(d => d.ShoppingCart)
.Map(t => t.MapKey("cartID"));
modelBuilder.Entity<Discount>().HasKey(t => t.discountId)
.HasOptional(q => q.ShoppingCart);
}
public DbSet<Discount> discountCodes { get; set; }
public DbSet<ShoppingCart> ShoppingCart { get; set; }
public DbSet<ShoppingCartItem> ShoppingCartItems { get; set; }
}
If you are working on an existing database you have to implement a DbMigration like it's explain here: Code First Migrations.
If you are in development phase, the easiest way is to drop the database.
Here's the problem. I have table User which have quite a few fields. What I want to do is split this table into multiple entities like this:
User
-> GeneralDetails
-> CommunicationDetails
-> Address
etc.
All goes well when extracting some fields from User into GeneralDetails. However, when I try to do the same thing for CommunicationDetails EF blows up and require to establish one-to-one relationship between GeneralDetails and CommunicationDetails.
Sample entities definition:
public class User {
public int UserId { get; set; }
public string SomeField1 { get; set; }
public int SomeField2 { get; set; }
public virtual GeneralDetails GeneralDetails { get; set; }
public virtual CommunicationDetails CommunicationDetails { get; set; }
public virtual Address Address { get; set; }
}
public class GeneralDetails {
[Key]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public virtual User User { get;set; }
}
public class CommunicationDetails {
[Key]
public int UserId { get; set; }
public string Phone { get; set; }
public string DeviceToken { get; set; }
public virtual User User { get;set; }
}
public class Address {
[Key]
public int UserId { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Street { get; set; }
public virtual User User { get;set; }
}
Sample mapping:
modelBuilder.Entity<User>().
HasRequired(user => user.GeneralDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.CommunicationDetails).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().
HasRequired(user => user.Address).
WithRequiredPrincipal(details => details.User);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<GeneralDetails>().ToTable("Users");
modelBuilder.Entity<Address>().ToTable("Users");
Why on earth EF want this relationship? Is there any way this could be solved?
The correct way to actually do this is by Complex Types rather than entities. Its actually a more common problem than you think.
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelbuilder.ComplexType<CommunicationDetails>();
modelbuilder.ComplexType<GeneralDetails>();
modelbuilder.ComplexType<Address>();
modelbuilder.Entity<User>().ToTable("Users");
}
}