I have a db schema along the lines of:
Product
ID
ProductName
Description
StoreBrand
ProductVariation
VariationID
ProductID
Size
StoreBrand
Price
Classes, predictably, look a bit like this:
public class Product
{
public virtual int ID { get; set; }
public virtual string ProductName { get; set; }
public virtual string Description { get; set; }
public virtual string StoreBrand { get; set; }
public virtual IEnumerable<ProductVariation> Variations { get; set; }
}
public class ProductVariation
{
public virtual int VariationID { get; set; }
public virtual int ProductID { get; set; }
public virtual Product Product {get; set;}
public virtual string Size { get; set; }
public virtual double Price { get; set; }
}
I've got the mapping classes like this:
public class ProductMapper : ClassMap<Product>
{
public ProductMapper()
{
Id(x => x.ID);
Map(x => x.ProductName);
Map(x => x.Description);
Map(x => x.StoreBrand);
HasMany(x => x.Variations)
.KeyColumn("ProductID");
}
}
public class ProductVariationMapper : ClassMap<ProductVariation>
{
public ProductVariation()
{
Id(x => x.ID);
Map(x => x.ProductID);
Map(x => x.Size);
Map(x => x.Price);
References(x => x.Product)
.Column("ProductID");
}
}
This is kind of working...
However, what I need to do is tie the Product.Brands together with the ProductVariation.Brands as well... (and vice versa)
So querying Product, returns a list of it's ProductVariations for that brand...
(Notice, ProductVariation doesn't have a property in the class, but it has the column for mapping)
ProductVariation.ID is non unique.
The key is ProductVariation.ID and ProductVariation.Brand (on the database)
public class Product
{
public virtual int ID { get; set; }
public virtual string StoreBrand { get; set; }
public virtual string ProductName { get; set; }
public virtual string Description { get; set; }
public virtual IEnumerable<ProductVariation> Variations { get; set; }
public override Equals(object obj)
{
return Equals(obj as Product)
}
public override Equals(Product other)
{
return (other != null) && (Id == other.Id) && (StoreBrand == other.StoreBrand);
}
public override GetHashCode()
{
unchecked
{
return Id.GetHashCode() * 397 + StoreBrand.GetHashCode();
}
}
}
public class ProductVariation
{
public virtual int ID { get; set; }
public virtual Product Product {get; set;}
public virtual string Size { get; set; }
public virtual double Price { get; set; }
}
public class ProductMapper : ClassMap<Product>
{
public ProductMapper()
{
// Id alone is not unique, hence compositeId
CompositeId()
.KeyProperty(x => x.ID)
.KeyProperty(x => x.StoreBrand);
Map(x => x.ProductName);
Map(x => x.Description);
HasMany(x => x.Variations)
.KeyColumn("ProductID", "StoreBrand");
}
}
public class ProductVariationMapper : ClassMap<ProductVariation>
{
public ProductVariation()
{
Id(x => x.ID);
Map(x => x.Size);
Map(x => x.Price);
References(x => x.Product)
.Column("ProductID", "StoreBrand");
}
}
Related
I have an Item entity:
public class Item
{
public long Id { get; set; }
public string Name { get; set; }
public string Skuid { get; set; }
public double Price { get; set; }
public double Amount { get; set; }
}
And an Order entity:
public class Order
{
public long Id { get; set; }
public DateTime Date { get; set; }
public StatusEnum Status { get; set; }
public long SellerId { get; set; }
public Seller Seller { get; set; }
public double Total { get; set; }
public IList<Item> Items { get; set; }
}
My objective is to save the Order containing the items in the Database, but when the request is made, only the reference of my entity "Seller" is saved, but not the references of the items.
I did the following mapping on the "Order" entity:
public class OrderMap : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.ValueGeneratedOnAdd()
.UseIdentityColumn();
builder.Property(x => x.Date)
.IsRequired()
.HasColumnName("DateSale")
.HasColumnType("DATETIME")
.HasDefaultValueSql("GETDATE()");
builder.Property(x => x.Total)
.IsRequired()
.HasColumnName("Total")
.HasColumnType("DECIMAL");
builder.Property(x => x.Status)
.IsRequired()
.HasConversion(
v => v.ToString(),
v => (StatusEnum)Enum.Parse(typeof(StatusEnum), v))
.HasColumnName("Status")
.HasColumnType("NVARCHAR") //verificar se nao vai dar conflito
.HasMaxLength(120);
builder
.HasOne(x => x.Seller)
.WithOne()
.HasConstraintName("FK_Seller_Order")
.OnDelete(DeleteBehavior.Cascade);
builder
.HasMany(x => x.Items)
.WithOne()
.HasConstraintName("FK_Item_Order")
.IsRequired()
.OnDelete(DeleteBehavior.Cascade); //
}
}
What is the correct mapping to do?
Please first add these properties OrderId and Order to your Item entity class. This way you are instructions the framework to create an OrderId foreign key
column in the database.
public class Item
{
public long Id { get; set; }
public string Name { get; set; }
public string Skuid { get; set; }
public double Price { get; set; }
public double Amount { get; set; }
public long OrderId { get; set; }
public Order Order {get; set; }
}
And perhaps this part
builder.HasMany(x => x.Items).WithOne()
should be like this
builder.HasMany(x => x.Items).WithOne(x => x.Order)
I have two tables - master-detail with relation one-to-many.
Here are definitions and mappings:
public class Employee
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<Project> Projects { get; set; }
public Employee()
{
Projects = new List<Project>();
}
}
public class Project
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual double EstimatedHours { get; set; }
public virtual Employee Employee { get; set; }
}
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
HasMany<Project>(x => x.Projects)
.KeyColumn("Id_emp")
.Cascade.All()
.Inverse()
.LazyLoad();
}
public ProjectMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.EstimatedHours);
References(x => x.Employee).Class<Employee>().Columns("Id_emp").ForeignKey();
}
When I save data both employee and project tables are filled with data but in project table field Id_emp is null instead to have Id of employee.
What am I missing in mappings and referencing?
I have my entities like this, they are closely linked.
public class Game
{
public int Id { get; set; }
public int FirstTeamId { get; set; }
public Team FirstTeam { get; set; }
public int SecondTeamId { get; set; }
public Team SecondTeam { get; set; }
public Stadium Stadium { get; set; }
public DateTime Date { get; set; }
public GameStatus Result { get; set; }
public Game(DateTime date , GameStatus result )
{
Date = date;
Result = result;
}
}
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime Birthday { get; set; }
public PlayerStatus Status { get; set; }
public PlayerHealthStatus HealthStatus { get; set; }
public int Salary { get; set; }
public int TeamId { get; set; }
public Team Team { get; set; }
public Player(string name , string surname, DateTime birthday, PlayerStatus status, PlayerHealthStatus healthStatus, int salary)
{
Name = name;
Surname = surname;
Birthday = birthday;
Status = status;
HealthStatus = healthStatus;
Salary = salary;
}
}
public class Stadium
{
public int Id { get; set; }
public string Name { get; set; }
public int Capacity { get; set; }
public int PriceForPlace { get; set; }
public Stadium(string name, int capacity, int priceForPlace)
{
Name = name;
Capacity = capacity;
PriceForPlace = priceForPlace;
}
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public List<Player> Players { get; set; }
public List<Game> Games { get; set; }
public Team(string name)
{
Name = name;
}
public Team(string name, List<Player> players) : this(name)
{
Players = players;
}
}
In my Context class I'm tried to describe my relationships between classes. But something isn't correct.
public class ApplicationContext : DbContext
{
public DbSet<Player> Players { get; set; }
public DbSet<Game> Games { get; set; }
public DbSet<Team> Teams { get; set; }
public DbSet<Stadium> Stadiums { get; set; }
public ApplicationContext()
{
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=best-komp;Database=FootballApplicationDataBase;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Player>()
.HasOne(p => p.Team)
.WithMany(t => t.Players)
.HasForeignKey(p => p.TeamId)
.HasPrincipalKey(t => t.Id);
modelBuilder.Entity<Team>()
.HasMany(p => p.Players)
.WithOne(p => p.Team)
.HasForeignKey(p => p.TeamId)
.HasPrincipalKey(t => t.Id);
modelBuilder.Entity<Game>()
.HasOne(g => g.FirstTeam)
.WithMany(t => t.Games)
.HasForeignKey(t => t.FirstTeamId)
.HasPrincipalKey(t => t.Id);
modelBuilder.Entity<Game>()
.HasOne(g => g.SecondTeam)
.WithMany(t => t.Games)
.HasForeignKey(t => t.SecondTeamId)
.HasPrincipalKey(t => t.Id);
}
}
What wrong with this code? Because I have "Navigation properties can only participate in a single relationship." error when I try to do something with my ApplicationContext.
You can't reuse Team.Games as the inverse property for both Game.FirstTeam and Team.SecondTeam. Think of it, if you add game to Team.Games, how would EF know which team it is, first or second?
You need two collections to describe the relationships. And that's also a chance to add some more meaning to the class model. For example (only modified code):
public class Game
{
...
public int HomeTeamId { get; set; }
public Team HomeTeam { get; set; }
public int AwayTeamId { get; set; }
public Team AwayTeam { get; set; }
}
public class Team
{
...
public List<Game> HomeGames { get; set; }
public List<Game> AwayGames { get; set; }
}
For a team it's meaningful to make a distinction between home and away games, for example to compare results in both types of games.
And the mapping:
modelBuilder.Entity<Game>()
.HasOne(g => g.HomeTeam)
.WithMany(t => t.HomeGames)
.HasForeignKey(t => t.HomeTeamId)
.HasPrincipalKey(t => t.Id);
modelBuilder.Entity<Game>()
.HasOne(g => g.AwayTeam)
.WithMany(t => t.AwayGames)
.HasForeignKey(t => t.AwayTeamId).OnDelete(DeleteBehavior.NoAction)
.HasPrincipalKey(t => t.Id);
If using Sql Server, this delete behavior instruction is necessary to prevent disallowed multiple cascade paths.
The problem is that your Team model has 2 one-to-many relationships with your Game model but you only have one navigation property on the Team.
You need to have 2 navigation properties on the Team model, one for each relationship.
(Game1, Game2...).
You will also need to define these relationships in the Game model - a Team property for each relationship.
Check this answer for extra info.
I'm building Reporting system, but having problem with removing data from db.
public class Report
{
[Key]
public int ReportId { get; set; }
public string Title { get; set; }
public int? DateRange { get; set; }
public int Layout { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
public int OwnerId { get; set; }
public DateTime DateCreated { get; set; }
public bool Active { get; set; }
public virtual List<ReportCharts> ReportCharts { get; set; }
public virtual List<ReportElements> ReportElements { get; set; }
}
public class ReportElements
{
[Key, Column(Order = 1)]
public int ReportId { get; set; }
[Key, Column(Order = 2)]
public string ElementName { get; set; }
public Boolean Active { get; set; }
}
public class ReportCharts
{
[Key, Column(Order = 1)]
public int ReportId { get; set; }
[Key, Column(Order = 2)]
public string ChartId { get; set; }
public Boolean Active { get; set; }
}
In DbContext:
public DbSet<Report> Reports { get; set; }
// Report entity mapping
protected virtual void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Report>().Property(t => t.ReportId).HasColumnName("ReportId");
modelBuilder.Entity<Report>().Property(t => t.Title).HasColumnName("Title");
modelBuilder.Entity<Report>().Property(t => t.DateRange).HasColumnName("DateRange");
modelBuilder.Entity<Report>().Property(t => t.Layout).HasColumnName("Layout");
modelBuilder.Entity<Report>().Property(t => t.DateFrom).HasColumnName("DateFrom");
modelBuilder.Entity<Report>().Property(t => t.DateTo).HasColumnName("DateTo");
modelBuilder.Entity<Report>().Property(t => t.OwnerId).HasColumnName("OwnerId");
modelBuilder.Entity<Report>().Property(t => t.DateCreated).HasColumnName("DateCreated");
modelBuilder.Entity<Report>().Property(t => t.Active).HasColumnName("Active");
modelBuilder.Entity<Report>().HasMany(t => t.ReportElements).WithRequired().HasForeignKey(c => c.ReportId);
modelBuilder.Entity<Report>().HasMany(t => t.ReportCharts).WithRequired().HasForeignKey(p => p.ReportId);
modelBuilder.Entity<ReportElements>().Property(c => c.ElementName).HasColumnName("ElementName");
modelBuilder.Entity<ReportElements>().HasKey(c => new { c.ReportId, c.ElementName, c.Active });
modelBuilder.Entity<ReportCharts>().Property(p => p.ChartId).HasColumnName("ChartId");
modelBuilder.Entity<ReportCharts>().HasKey(c => new { c.ReportId, c.ChartId, c.Active });
}
In EF repository:
public void RemoveReport(Report rep)
{
context.Reports.Remove(rep);
context.SaveChanges();
}
When I perform delete it deletes only from Report table, and not from all three. When I do save report it saves in all three tables. So don't really understand what is wrong.
Many thanks for your help
You need to look into cascading delete options in Entity Framework an apply them as you need them.
Try this:
modelBuilder.Entity<Report>().HasMany(t => t.ReportElements)
.WithRequired()
.HasForeignKey(c => c.ReportId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Report>().HasMany(t => t.ReportCharts)
.WithRequired()
.HasForeignKey(p => p.ReportId)
.WillCascadeOnDelete(true);
Domain:
public class Account
{
public virtual int AccountId { get; set; }
public virtual int UserId { get; set; }
public virtual string HostName { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual bool Deleted { get; set; }
}
public class Person
{
public Person()
{
PersonRoles = new List<PersonRole>();
}
public virtual int PersonId { get; set; }
public virtual Guid PersonGuid { get; set; }
public virtual string FirstName { get; set; }
public virtual string Surname { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual string SaltKey { get; set; }
public virtual int PersonType { get; set; }
public virtual DateTime CreatedOn { get; set; }
public virtual bool Deleted { get; set; }
public virtual bool Active { get; set; }
public virtual int? AccountId { get; set; }
public virtual ICollection<PersonRole> PersonRoles { get; private set; }
public virtual Account Account { get; set; }
}
Mapping:
public AccountMap()
{
Id(x => x.AccountId, "AccountId").Column("AccountId");
Map(x => x.UserId);
Map(x => x.HostName);
Map(x => x.CreatedOn);
Map(x => x.Deleted);
Table("crm_accounts");
}
public PersonMap()
{
Id(x => x.PersonId).Column("PersonId");
Map(x => x.PersonGuid);
Map(x => x.FirstName);
Map(x => x.Surname);
Map(x => x.Email);
Map(x => x.Password);
Map(x => x.SaltKey);
Map(x => x.PersonType);
Map(x => x.CreatedOn);
Map(x => x.Deleted);
Map(x => x.Active);
HasManyToMany<PersonRole>(x => x.PersonRoles)
.ParentKeyColumn("RoleId")
.ChildKeyColumn("PersonId")
.Cascade.All()
.Table("crm_people_roles_mapping");
//Map(x => x.AccountId);
References(x => x.Account, "AccountId").Column("AccountId");
Table("crm_people");
}
Issue:
When saving a new person with an account id everything saves OK except for the acccount id field.
A person doesn't need to have an account to exist.
What am I doing wrong?
Thanks.
HNibernate doesn't know what to do with your AccountId. On your person object, you've got an Account and an AccountId property. I'll bet if you assigned the Account to the person before you save it, it'd all start working.
Get rid of that AccountId property. You don't need it.