I have the following four tables:
Customer
Id
FirstName
...
ConsumptionPoint
Id
Address
...
Invoice
Id
InvoiceNumber
CustomerId
ConsumptionPointId
...
ContractAccount
Id
ContractAccountNumber
CustomerId
ConsumptionPointId
IsCurrentDelivery
...
I want to get the ContractAccountNumber for an Invoice.
Is it possible to create some kind of a relation between these both to access the ContractAccount(s) of an Invoice directly?
Currently I'm doing something like:
invoice.Customer.ContractAccounts
.Where(ca => ca.ConsumptionPoint == invoice.ConsumptionPoint &&
ca.IsCurrentDelivery == true).FirstOrDefault();
update in SQL I would simply do a join with multiple conditions:
SELECT i.Id AS InvoiceId, ca.Id AS ContractAccountId,
ca.ContractAccountNumber
FROM Invoices i
LEFT JOIN ContractAccounts ca
ON i.ConsumptionPointId = ca.ConsumptionPointId
AND i.CustomerId = ca.CustomerId
WHERE ca.IsCurrentDelivery = 1
update 2:
Basically I just want to get rid of the ca.ConsumptionPoint == invoice.ConsumptionPoint in the Where-Clause and want to define this inside the relation.
Actually this is a many-to-many relationship: one Invoice can link to many ContractAccounts (via different Customer/ConsumptionPoint combinations) and one ContractAccount can link to many Invoices. Is there no way to tell .net to build a many-to-many relationship, based on the combination of two custom columns?
I do not think that you need two keys for that. Using EF core you can create a relationship like that:
Invoice:
public class Invoice
{
public int Id {get; set;}
public int InvoiceNumber {get; set;}
public int CustomerId {get; set;}
public Customer Customer {get; set;}
public int ConsumptionPointId {get; set;}
public int ContractAccountId {get; set;}
public ContractAccount ContractAccount {get; set;}
}
public class ContractAccount
{
public int Id {get; set;}
public int ContractAccountNumber {get; set;}
public bool IsCurrentDelivery {get; set;}
}
Then configuration:
modelBuilder.Entity<Invoice>()
.HasOne(b => b.ContractAccount)
.WithOne()
.HasForeignKey<Invoice>(b => b.ContractAccountId);
//probably already exists in your code
modelBuilder.Entity<Invoice>()
.HasOne(b => b.Customer )
.WithMany()
.HasForeignKey(b => b.CustomerId);
And then you can access it directly from invoice: invoice.ContractAccount
After some deeper investigation I have found a solution.
Basically it's just a many-to-many relationship. So we can add the appropriate properties to Invoice and ContractAccount Model:
public class Invoice
{
[Key]
public long Id { get; set; }
public Customer Customer { get; set; }
public ConsumptionPoint ConsumptionPoint { get; set; }
public virtual ICollection<ContractAccount> ContractAccounts { get; set; }
}
public class ContractAccount
{
[Key]
public long Id { get; set; }
public Customer Customer { get; set; }
public ConsumptionPoint ConsumptionPoint { get; set; }
public ICollection<Invoice> Invoices { get; set; }
}
Now, we just have to configure the relationship manually:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Invoice>()
.HasMany(i => i.ContractAccounts)
.WithOne()
.HasForeignKey(ca => new { ca.CustomerId, ca.ConsumptionPointId })
.HasPrincipalKey(i => new { i.CustomerId, i.ConsumptionPointId });
modelBuilder.Entity<ContractAccount>()
.HasMany(ca => ca.Invoices)
.WithOne()
.HasForeignKey(i => new { i.CustomerId, i.ConsumptionPointId })
.HasPrincipalKey(ca => new { ca.CustomerId, ca.ConsumptionPointId });
}
And that's it. Now I can do something like:
invoice.ContractAccounts
.Where(ca => ca.IsCurrentDelivery == true).FirstOrDefault();
which is way better than before.
Thanks for your comments, which pointed me to the right direction.
Related
I have a problem with many to many relationship in EF core. I have the following models:
public class Institution
{
[Key]
public int Id { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
public class InstitutionDepartment
{
[Column("Institution_Id")]
public int InstitutionId { get; set; }
[Column("Department_Id")]
public int DepartmentId { get; set; }
public Institution Institution { get; set; }
public Departments Department { get; set; }
}
public class Departments
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Published { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
I followed the many tutorials explaining how to correctly map these classes:
modelBuilder.Entity<InstitutionDepartment>()
.HasKey(x => new { x.DepartmentId, x.InstitutionId});
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Institution)
.WithMany(p => p.InstitutionDepartments)
.HasForeignKey(pt => pt.InstitutionId);
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Department)
.WithMany(t => t.InstitutionDepartments)
.HasForeignKey(pt => pt.DepartmentId);
I wrote my query:
var institutions = _context.Institutions
.Include(i => i.InstitutionDepartments)
.ThenInclude(id => id.Department);
But no matter what I do, I get the following error:
Invalid column name 'InstitutionId'.
Can anyone tell me what I'm doing wrong here ? D:
Note I don't get the error if I don't write the .ThenInclude(id => id.Department); part.
But that make the data incomplete
The issue came from a line of code that I didn't deem relevant at the time (I'll know better next time)
This was in the Institution model without the [NotMapped] annotation:
[NotMapped]
public IEnumerable<Departments> Departments
=> InstitutionDepartments?.Select(o => o.Department);
This was causing EF to look for a missing One to Many relationship between Institution and Department
I'm trying to model a many to many relationship. This is my context:
modelBuilder.Entity<Description>(entity =>
{
entity.HasKey(e => new { e.DescriptionID, e.Language });
});
modelBuilder.Entity<Company>(entity =>
{
entity.HasKey(e => e.CompanyID);
});
modelBuilder.Entity<CompanyDescription>()
.HasKey(cd => new { cd.CompanyID, cd.DescriptionID });
modelBuilder.Entity<CompanyDescription>()
.HasOne(cd => cd.Company)
.WithMany(c => c.CompanyDescriptions)
.HasForeignKey(bc => bc.CompanyID);
modelBuilder.Entity<CompanyDescription>()
.HasOne(cd => cd.Description)
.WithMany(c => c.CompanyDescriptions)
.HasForeignKey(bc => bc.DescriptionID);
CompanyDescription is the cross table.
Both Company and Description contain
public ICollection<CompanyDescription> CompanyDescriptions { get; set; }
A company can have many descriptions and a description can have many companies. The way the database is currently set up is that a description does not contain a foreign key and a company only contains 'DescriptionID', but not language. So this 'foreign key' does not agree with the primary key of the description.
Because of that I get:
The relationship from 'CompanyDescription.Description' to
'Description.CompanyDescriptions' with foreign key properties
{'DescriptionID' : int} cannot target the primary key
{'DescriptionID' : int, 'Language' : string} because it is not
compatible. Configure a principal key or a set of compatible foreign
key properties for this relationship.
What is the best way to fix this problem?
Edit, the model classes:
public partial class Company
{
public int CompanyID { get; set; }
public int DescriptionID { get; set; }
public ICollection<CompanyDescription> CompanyDescriptions { get; set; }
}
public partial class Description
{
public int DescriptionID { get; set; }
public ICollection<CompanyDescription> CompanyDescriptions { get; set; }
}
The cross table:
public class CompanyDescription
{
public int CompanyID { get; set; }
public RN_Company Company { get; set; }
public int DescriptionID { get; set; }
public string Language { get; set; }
public Description Description { get; set; }
}
As it is many-to-many relationship between Company and Description, Company should not contain either DescriptionID or both DescriptionID and Language. As you said company only contains 'DescriptionID', but not language. Then please remove the DescriptionID from the Company model class.
Then your CompanyDescription entity should be as follows:
public class CompanyDescription
{
public int CompanyID {get; set;}
public int DescriptionID {get; set;}
public string Language {get; set;}
public Company Company {get; set;}
public Description Description {get; set;}
}
Then your CompanyDescription entity configuration should be as follows:
modelBuilder.Entity<CompanyDescription>()
.HasKey(cd => new { cd.CompanyID, cd.DescriptionID, cd.Language }); // <-- Here it is
modelBuilder.Entity<CompanyDescription>()
.HasOne(cd => cd.Company)
.WithMany(c => c.CompanyDescriptions)
.HasForeignKey(bc => bc.CompanyID);
modelBuilder.Entity<CompanyDescription>()
.HasOne(cd => cd.Description)
.WithMany(c => c.CompanyDescriptions)
.HasForeignKey(bc => new { bc.DescriptionID, bc.Language}); // <-- Here it is
This is the scenario:
I have a products table and a categories table. The relationship is many-to-many: a category can have 1 or more products....and a product can be in 1 or more categories...
The Code-First mapping looks like this....
public class Product
{
//...additional properties...
public virtual ICollection<Category> AssociatedCategories {get; set;}
}
public class Category
{
//...additional properties...
public virtual ICollection<Product> AssociatedProducts {get; set;}
}
Now, under the hood, entity framework will create a join table called ProductCategory with columns ProductID and CategoryID. That's great....
Here's the thing though, I need to introduce a sort order...basically just a cardinal positioning index, but this number exists only at the part in the relationship where product and category meet each other. For example, a product X might have a sort order value of "5" in Category Y, but that some product--X--could have a different sort value--say 10--in Category Z.
Naturally, I could create an entity specifically for this type of thing...but it would require a new table be made...there would be 3 columns for the Category ID, Product ID, and sort order. What I'd really like to be able to do is tap into the table that entity framework already made....it will already keep track of products IDs and category IDs in the join table....is there any way to make use of the table that already exists?
You need to create a specific entity for the join table in order to do this.
public class Product
{
//...additional properties...
public virtual ICollection<ProductCategoryXref> AssociatedCategories {get; set;}
}
public class Category
{
//...additional properties...
public virtual ICollection<ProductCategoryXref> AssociatedProducts {get; set;}
}
public class ProductCategoryXref
{
public int ProductId { get; set; }
public int CategoryId { get; set; }
public int SortOrder { get; set; }
// Additional Columns...
public virtual Product Product { get; set; }
public virtual Category Category { get; set; }
}
If you are using the Fluent API to configure your entities it will look something like this:
public class ProductCategoryXrefMap : EntityTypeConfiguration<ProductCategoryXref>
{
ProductCategoryXrefMap()
{
HasKey(pk => new { pk.ProductId, pk.CategoryId });
HasRequired(p => p.Product).WithMany(p => p.AssociatedCategories).HasForeignKey(fk => fk.ProductId);
HasRequired(p => p.Category).WithMany(p => p.AssociatedProducts).HasForeignKey(fk => fk.CategoryId);
ToTable("ProductCategoryXref");
}
}
For example I have Poduct entity:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
...
}
The idea is I want to create the editable collection of similar products for product. So it is like many-to-many but on the same entity - product So I updated my model like below:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
public ICollection<Product> SimilarProducts { get; private set; }
public void AddSimilar(Product product)
{
SimilarProducts.Add(product);
}
...
}
also I updated my DbContext class:
modelBuilder.Entity<Product>()
.HasMany(p => p.SimilarProducts)
.WithOptional()
.WillCascadeOnDelete(false);
implemented edit product action:
public ActionResult Edit(ProductEditModel productEditModel)
{
if(!string.IsNullOrEmpty(productEditModel.SelectedSimilarProductLinkName))
{
var similarProduct = _productRepository.GetProduct(productEditModel.SelectedSimilarProductId);
product.AddSimilar(similarProduct);
}
_productRepository.AddProduct(product);
}
void IProductRepository.AddProduct(Product product);
public void AddProduct(Product product)
{
_repository.InsertOrUpdate(product);
}
But I get strange results: To product was added Product_Id field in my database and there is no such as ProductProduct table or something like that which stores related products ids as in usual many to many entities implementations. How can I create this table manually? What am I missing or what am doing wrong?
Thanks Swell for advice, I've already figured out the solution:
Model:
public class Product : DatabaseEntity
{
public int Id {get; set;}
public int Name {get; set;}
public decimal Price {get; set;}
public ICollection<Product> ParentSimilars { get; set; }
public ICollection<Product> ChildSimilars { get; set; }
[NotMapped]
public IEnumerable<Product> SimilarProducts
{
get
{
return ChildSimilars.Concat(ParentSimilars);
}
}
...
}
DbContext setup:
modelBuilder.Entity<Product>()
.HasMany(p => p.ChildSimilars)
.WithMany(p => p.ParentSimilars)
.Map(m =>
{
m.MapLeftKey("Product_Id");
m.MapRightKey("SimilarProduct_Id");
});
That's, basically, all.
I'm trying to use the awsome EF5 with code first - where I need to make a many-to-many table with extra fields.
I've got a products table, orders table and need a table of products that are in orders with a "size" field.
What I've done is created a new class of "ProductOrder" that is the connection table between them, and made a reference.
It WORKS when creating a new order, but is not working when fetching an order - it doesn't get the connected orders (that are present in the DB after the insertion).
Ideas why? :)
My Classes are:
public class Order
{
public int ID { get; set; }
...
public ICollection<ProductOrder> Products { get; set; }
public Order()
{
Products = new HashSet<ProductOrder>();
}
}
public class Product
{
public int ID { get; set; }
public ICollection<ProductOrder> Orders { get; set; }
}
public class ProductOrder
{
public int ID { get; set; }
public int ProductID { get; set; }
public int OrderID { get; set; }
public int Size { get; set; }
[ForeignKey("OrderID")]
public Order order { get; set; }
[ForeignKey("ProductID")]
public Product product { get; set; }
}
and in onModelCreating
modelBuilder.Entity<Order>()
.HasMany(p => p.Products)
.WithRequired(o => o.order)
.HasForeignKey(o => o.OrderID);
modelBuilder.Entity<Product>()
.HasMany(o => o.Orders)
.WithRequired(p => p.product)
.HasForeignKey(p => p.ProductID);
Your navigational properties need to be virtual
From what I've seen, marking the navigational properties as virtual turns lazy loading on for those properties.
The other way to get this to work without lazy loading is to add .include() statements to your select. This tells EF to pull back the extra data that you want.