Entity Framework, Many-to-Optional, Without a Foreign Key? - c#

I have two tables, which are from a legacy system. These tables are periodically updated from two separate external sources and are to be used as merely "read-only" tables for look-up data in my application:
DeliverySites
public partial class DeliverySite
{
public string CustomerID { get; set; } // PK
public string CustomerName { get; set; }
public string DeliveryAddress { get; set; }
public string BillingAddress { get; set; }
//... fields removed for clarity.
// Navigational Properties.
public virtual ICollection<Item> Items { get; set; }
}
public class DeliverySiteMap : EntityTypeConfiguration<DeliverySite>
{
public DeliverySiteMap()
{
// Primary Key
this.HasKey(t => t.CustomerID);
// Properties
this.Property(t => t.CustomerID)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.CustomerName)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.DeliveryAddress)
.IsRequired();
this.Property(t => t.BillingAddress)
.IsRequired();
// Table & Column Mappings
this.ToTable("DeliverySites");
this.Property(t => t.CustomerID).HasColumnName("CustomerID");
this.Property(t => t.CustomerName).HasColumnName("CustomerName");
this.Property(t => t.DeliveryAddress).HasColumnName("DeliveryAddress");
this.Property(t => t.BillingAddress).HasColumnName("BillingAddress");
}
}
Items
public partial class Item
{
public string Item { get; set; } // PK
public string ItemDescription { get; set; }
public decimal Brand { get; set; }
public decimal Price { get; set; }
public string CustomerID { get; set; } // PK + FK
//... fields removed for clarity.
// Navigational Properties.
public virtual DeliverySite DeliverySite { get; set; }
}
public class ItemMap : EntityTypeConfiguration<Item>
{
public ItemMap()
{
// Primary Key
this.HasKey(t => new { t.Item, t.CustomerID });
// Properties
this.Property(t => t.UserItem)
.HasMaxLength(50);
this.Property(t => t.UserItemDescription)
.HasMaxLength(255);
this.Property(t => t.CCItem)
.IsRequired()
.HasMaxLength(50);
this.Property(t => t.CCItemDescription)
.IsRequired()
.HasMaxLength(255);
this.Property(t => t.CustomerID)
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Items");
this.Property(t => t.Item).HasColumnName("Item");
this.Property(t => t.ItemDescription).HasColumnName("ItemDescription");
this.Property(t => t.Brand).HasColumnName("Brand");
this.Property(t => t.Price).HasColumnName("Price");
this.Property(t => t.CustomerID).HasColumnName("CustomerID");
}
}
Given that these tables are updated independently, there may be "Items" entered for "DeliverySites" which do not exist.
Thus, I want to make an optional relation. (So I can make use of navigational properties within my application, but so that I do not prevent the tables from being independently updated.)
Within my ItemMap : EntityTypeConfiguration<Item> I've tried the following:
this.HasOptional(x => x.DeliverySite)
.WithMany(x => x.Items)
.HasForeignKey(x => x.CustomerID)
.WillCascadeOnDelete(false);
but I get this error:
System.Data.Entity.Edm.EdmAssociationType: : Multiplicity conflicts
with the referential constraint in Role 'Item_DeliverySite_Target' in
relationship 'Item_DeliverySite'. Because all of the properties in the
Dependent Role are non-nullable, multiplicity of the Principal Role
must be '1'.
How should I be implementing this relationship?
Also, it would be ideal if I could do this without adding any FK constraints in the database. Is this possible?

Most likely you are having this error because Item.CustomerID is required. Although you didn't call IsRequired() in your mapping it is required because it is part of the composite primary key...
this.HasKey(t => new { t.Item, t.CustomerID });
...and because every column of a composite key does not allow NULL in the database. Therefore EF wants to have the mapping
this.HasRequired(x => x.DeliverySite)
.WithMany(x => x.Items)
.HasForeignKey(x => x.CustomerID)
.WillCascadeOnDelete(false);
To me the real problem seems to be that you are trying to create a relationship and navigation properties with EF where no real relationship and foreign key constraint is in the database.
If Item.CustomerID can have a value in the database that does not exist as a row in table DeliverySites you can't use HasRequired because if you would try to load an Item with an Include for the DeliverySite EF will create an INNER JOIN and Items that don't refer to an existing DeliverySite via the CustomerID won't get loaded at all. So, basically you would be getting wrong query results. On the other hand you can't use an optional mapping (which would lead to LEFT OUTER JOIN) because CustomerID is part of the PK and it results in your exception.
Honestly with your legacy database schema I wouldn't try to introduce navigation properties and an artificial relationship in your EF model. If you need "related" DeliverySites and Items in one query I would probably prefer to just use manual LINQ joins with the CustomerID as an ordinary scalar property to join the data on and project the loaded DeliverySites and Items into a specialized class (or anonymous objects).

I believe this would work if you remove the foreign key and just make it:
this.HasOptional(x => x.DeliverySite)
.WithMany(x => x.Items);

I know it's not the answer you want but IMHO your time would be better spent crafting a solution that ensures the integrity of your legacy database. Looking for work-arounds in an ORM for a denormalized DB is not only bad practice but it will cause headaches for your peers and devalue your product. Not sure how the data is persisted but you could hold items in a temp table and only add to the normalised table when the corresponding Delivery Site is present.

Related

EF6 fluent relation with custom foreign key and FK property

I'm trying to migrate existing project to EF and already have strict database structure and mandatory set of properties. The problem I now fight with is on the following.
I have two classes:
public class Entity
{
public virtual long ID {get;set;}
public virtual long ContragentID {get;set;}
public virtual Contragent {get;set;}
}
public class Contragent
{
public virtual long ID {get;set;}
public virtual long EntityID {get;set;}
public virtual Entity {get;set;}
}
With the following entity mappings:
public class ClassContragentAccountMap : EntityTypeConfiguration<Contragent>
{
public ClassContragentAccountMap()
{
// Primary Key
HasKey(t => t.ID);
// Table & Column Mappings
ToTable("contragent");
Property(t => t.ID).HasColumnName("id");
Property(t => t.EntityID).HasColumnName("e_id");
HasOptional(t => t.Entity).WithOptionalDependent(t => t.Contragent);
}
}
public class ClassLegalEntityAccountMap : EntityTypeConfiguration<Entity>
{
public ClassLegalEntityAccountMap()
{
// Primary Key
HasKey(t => t.ID);
// Table & Column Mappings
ToTable("entity");
Property(t => t.ID).HasColumnName("id");
Property(t => t.ContragentID).HasColumnName("contragentid");
}
}
The problem is that relation throws an exception that Entity_ID column is not found. Is there a way to specify FK for the relation as attributes seems to not work here? I've also tried to use:
HasOptional(t => t.Entity).WithOptionalDependent(t => t.Contragent).Map(t=> t.MapKey("e_id"));
But then it conflicts with the already defined 'e_id' property. And I need both relation and the property present.
Is there any way to specify FK for relation and preserve ID properties in classes?
Probably I can go with remaking relation ID properties:
public virtual long ContragentID => Contragent?.ID;
public virtual long EntityID => Entity?.ID;
But I want to know is there any other way to do it.

Code-first 1..n mapping error

I have these 2 models:
public class Product
{
public int Id {get; set;}
public int ProductGroupId {get; set;}
}
public class ProductGroup
{
public int Id {get; set;}
public virtual ICollection<Product> Products {get; set;}
}
And the mappings:
public class ProductMap
{
this.ToTable("products");
this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(t => t.Key).HasColumnName("id_product_group")
.IsRequired();
}
public class ProductGroupMap
{
{
this.ToTable("product_groups");
this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.HasOptional(t => t.Products)
.WithMany()
.WillCascadeOnDelete();
}
The code compiles but when I start the app, I get the following exception:
Invalid column "Products_Id"
Is the mapping incorrect?
Your foreign key mapping is incorrect.
Replace HasOptional code in ProductGroupMap with:
HasMany(_ => _.Products)
.WithRequired()
.HasForeignKey(_ => _.ProductGroupId);
This should be read as "ProductGroup has a collection of Products, where product must belong to a ProductGroup, and the relation is controlled by a foreign key property Product.ProductGroupId".
HasOptional/HasRequired are intended to use for entities, having required/optional reference navigation property (e.g., Product.CountryOfOrigin), not a collection navigation property.
In other words, 1-* association between entities could be configured from both sides: either from principal entity (ProductGroup in your case), or from dependent entity (Product).
Use HasMany, when configuring from the side of principal entity.
Use HasOptional/HasRequired, when configuring from the side of dependent entity:
HasOptional when the reference to a principal entity is optional;
HasRequired - when reference is required.

One to zero-or-one with HasForeignKey

I have two models:
public class Person
{
public virtual int Id { get; set; }
public virtual Employee Employee { get; set; } // optional
}
public class Employee
{
public virtual int Id { get; set; }
public virtual int PersonId { get; set; }
public virtual Person Person {get; set; } // required
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
Property(e=>e.PersonId) // I need this property mapped
.HasColumnName("person_id")
.HasColumnType("int");
}
}
I want to map them using fluent mapping. Employee table has column 'person_id' which is non-nullable. I tried following:
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.Map(m => m.MapKey("person_id"));
But it fails with:
System.Data.Entity.ModelConfiguration.ModelValidationException : One
or more validation errors were detected during model generation:
person_id: Name: Each property name in a type must be unique. Property
name 'person_id' is already defined.
I need PersonId property on its own, so what I basically want is:
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.HasForeignKey(e => e.PersonId); // there is no such method
But there is no such method here as HasForeignKey
OK, I figured that out - you should use WithMany (yep, not obvious) in order to store foreign key in some property:
Property(e => e.PersonId).HasColumnName("person_id");
HasRequired(e => e.Person)
.WithMany()
.HasForeignKey(p => p.PersonId);
See One-to-One Foreign Key Associations article for details. BTW this will create employee foreign key column for person's table, but there is no other way to have navigation property and foreign key separately.
If you need foreign key read-only, you can change PersonId property to:
public int PersonId { get { return Person.Id; } }
And use your original mapping
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.Map(m => m.MapKey("person_id"));
There's actually a better way to do this:
HasKey(e => e.PersonId);
HasRequired(e => e.Person)
.WithOptional(p => p.Employee);
Note that you no longer need the Employee.Id property with this approach, as it was superfluous in terms of a 1 to 0 or 1 relationship in the first place.

One-to-Many relationship mapping returns validation errors

Edited with the new situation per suggestion in the comments:
Currently I have this mapping
public ShowMap() {
ToTable("Shows");
HasKey(x => x.ShowID);
Property(x => x.ShowID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired()
.HasColumnName("ShowID");
}
public EpisodeMap() {
ToTable("Episodes");
HasKey(x => x.EpisodeID);
Property(x => x.EpisodeID)
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.HasColumnName("EpisodeID");
Property(x => x.ShowID)
.IsRequired()
.HasColumnName("ShowID");
Property(x => x.EpisodeNumber)
.IsRequired()
.HasColumnName("EpisodeNumber");
}
This results in the following database:
However, when the seed method is run I receive this error. Since I can't debug the value of variables from the command line command Update-Database (at least, not as far as I know) I can't see what the property holds.
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
When I add the relationship to the mapping, I receive the following error:
One or more validation errors were detected during model generation:
System.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'Episode_Show_Source' in relationship 'Episode_Show'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
Relationship:
HasRequired(x => x.Show)
.WithMany(x => x.Episodes)
.HasForeignKey(x => x.EpisodeID);
This is the model:
public class Episode {
public int EpisodeID {get; set;}
public int ShowID {get; set;}
public int EpisodeNumber {get; set;}
public virtual Show Show { get; set; }
}
public class Show {
public int ShowID {get; set;}
public virtual ICollection<Episode> Episodes { get; set; }
}
What have I overlooked that causes validation errors?
Edit: just to be certain I haven't forgotten anything, this is the project on github.
This is the problem (from your git repository, class EpisodeMap):
HasRequired(x => x.Show)
.WithMany(x => x.Episodes)
.HasForeignKey(x => x.EpisodeID);
EpisodeID is the PK in Episode and EF expects a one-to-one mapping in this case without an Episodes collection but an Episode reference instead ("upper bound of multiplicity = 1").
For a one-to-many relationship it has to be:
HasRequired(x => x.Show)
.WithMany(x => x.Episodes)
.HasForeignKey(x => x.ShowID);

SQLException: Invalid column name

I have recently converted my existing database to code using EF Reverse Engineer Code First. After polishing most of the results, I've started writing tests. The error I'm currently having is trying to access a column name called 'Element_ElementCode', however no such column exists.
To be sure of this, I've performed a search on my entire project to avoid the possibility of accidentally declaring it as such.
Find all "Element_ElementCode", Subfolders, Find Results 1, Entire Solution, ""
Matching lines: 0 Matching files: 0 Total files searched: 120
The exact error is as follows:
System.Data.SqlClient.SqlException: Invalid column name 'Element_ElementCode'.
Invalid column name 'Element_ElementCode' is repeated 10-15 times and the stacktrace doesn't provide any clue.
This exception occurs when executing a test which contains an expression to retrieve data and performs some asserts onto it.
var doos = (dbContext.Elementen.Where(d => d.ElementCode == "DOOS9001")).FirstOrDefault();
This is the result from a query in SQL Server itself:
Element (inside Element.cs) has the following fields:
ElementCode
Doelgroep
Type
Omschrijving
Titel
Elements are mapped like this:
public class ElementenMap : EntityTypeConfiguration<Element> {
public ElementenMap() {
// Primary Key
this.HasKey(t => t.ElementCode);
// Properties
this.Property(t => t.ElementCode)
.IsRequired()
.HasMaxLength(255);
this.Property(t => t.Type)
.HasMaxLength(31);
this.Property(t => t.Doelgroep)
.HasMaxLength(255);
this.Property(t => t.Omschrijving)
.HasMaxLength(255);
this.Property(t => t.Titel)
.HasMaxLength(255);
// Table & Column Mappings
this.ToTable("Elementen");
this.Property(t => t.ElementCode).HasColumnName("ElementCode");
this.Property(t => t.Type).HasColumnName("Type");
this.Property(t => t.Doelgroep).HasColumnName("Doelgroep");
this.Property(t => t.Omschrijving).HasColumnName("Omschrijving");
this.Property(t => t.Titel).HasColumnName("Titel");
// Relationships
this.HasMany(t => t.Kernwoorden)
.WithRequired(t => t.Element)
.Map(m => m.ToTable("Kernwoorden"));
}
}
Furthermore: I am certain the database is accessed because other tests on different tables inside the same database succeed.
I've tried to provide as much relevant info as possible, let me know if I've forgotten a source. Why is it trying to access a column called Element_ElementCode? Is it perhaps a convention I've forgotten to turn off (I've already had to turn off the PluralizingTableNameConvention)?
What direction should I be looking for this?
Edit:
Kernwoord.cs
public class Kernwoord {
public string ElementCode { get; set; }
public string KernwoordString { get; set; }
public virtual Element Element { get; set; }
}
Element.cs
public partial class Element {
public Element() {
this.LeertrajectElementen = new List<LeertrajectElement>();
this.Kernwoorden = new List<Kernwoord>();
}
public string ElementCode { get; set; }
public string Type { get; set; }
public string Doelgroep { get; set; }
public string Omschrijving { get; set; }
public string Titel { get; set; }
public virtual Casus Casus { get; set; }
public virtual Document Document { get; set; }
public virtual Doos Doos { get; set; }
public virtual ICollection<LeertrajectElement> LeertrajectElementen { get; set; }
public virtual StellingenSpel StellingenSpel { get; set; }
public virtual ICollection<Kernwoord> Kernwoorden { get; set; }
}
LeertrajectElementenMap.cs snippet:
this.HasRequired(t => t.Element)
.WithMany(t => t.LeertrajectElementen)
.HasForeignKey(d => d.ElementCode);
EDIT:
The problem was solved by fixing the existing inheritance issues.
You don't specify the foreign key column name of your relationship. It should be:
this.HasMany(t => t.Kernwoorden)
.WithRequired(t => t.Element)
.Map(m => m.MapKey("ElementCode")); // FK column name in Kernwoorden table
Or, if you have a foreign key property ElementCode in Kernwoorden class:
this.HasMany(t => t.Kernwoorden)
.WithRequired(t => t.Element)
.HasForeignKey(t => t.ElementCode);
There is a covention with respect to types.
For example if you have a customer and that customer has one and only one Adress, and the Adress has a Postcode.
Then it will generate a table Customer with a field Adress_Postcode.
In your case your Class name and Table name are different, so it is putting the type Element into table Elementen.

Categories