I have a class with a standard self-referential parent-child relationship; but I don't have a foreign-key column in the database table, only a unique (natural) key. I want to map parents and children based on two varchar(64) fields, Number and ParentNumber (neither of which are PKs):
public class Centre
{
public int Id { get; set; } // PK
public string Number { get; set; } // alternative identifier, required, unique, fixed length (64 chars)
public string ParentNumber { get; set; } // optional (null if no parent centre, otherwise the Number of that centre)
public virtual Centre ParentCentre { get; set; } // to be populated if this.ParentNumber is not null)
public virtual IList<Centre> ChildCentres { get; set; } // to be populated if otherCentre.Number == this.ParentNumber for at least one centre)
}
Can I specify a mapping with the Fluent API to make this happen? I don't know how to specify the two non-PK fields in the relationship.
HasOptional(c => c.ParentCentre).WithMany().HasForeignKey(c => c.ParentCentreNumber); // this surely won't work because we haven't specified that c.Number should be part of the mapping
Property(c => c.Id);
Property(c => c.Number).HasMaxLength(64).IsRequired().IsUnicode(false).HasColumnAnnotation( IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute("IX_Number") { IsUnique = true }) );
Property(c => c.ParentNumber).HasMaxLength(64).IsUnicode(false);
Edit: I am using EF 6. It looks like this may be impossible without EF Core. Is that right?
Related
According to How to add a parent record with its children records in EF Core the following should work:
Model Builder
mb.Entity<IdentifierTable>().HasKey("StockID");
mb.Entity<StockTable>().HasKey("StockID");
mb.Entity<ProductTable>().HasKey("IdentifierID");
mb.Entity<GroupsTable>().HasKey("GroupId");
mb.Entity<StockTable>()
.HasOne(x => x.Identifier)
.WithOne(y => y.Stock)
.HasForeignKey<IdentifierTable>(y => y.StockID);
mb.Entity<StockTable>()
.HasOne(x => x.Group)
.WithMany(y => y.Stock)
.HasForeignKey(y => y.GroupId);
mb.Entity<ProductTable>()
.HasOne<IdentifierTable>(x => x.Identifier)
.WithOne(y => y.Product)
.HasForeignKey<IdentifierTable>(y => y.IdentifierID);
base.OnModelCreating(mb);
Relevant parts of StockTable
public int StockID { get; set; }
public int? GroupId { get; set; }
[...]
public IdentifierTable Identifier { get; set; }
public GroupsTable Group { get; set; }
Relevant parts of IdentifierTable
public int IdentifierID { get; set; }
public ProductTable Product { get; set; }
public int StockID { get; set; }
public StockTable Stock { get; set; }
Relevant parts of ProductTable
public int? IdentifierID { get; set; }
public IdentifierTable Identifier { get; set; }
[...]
Relevant parts of GroupsTable
public int GroupId { get; set; }
public List<StockTable> Stock { get; set; }
The actual program
foreach (var item in contexts.originalContext.StockTable
.Include(x=>x.Group)
.Include(x=>x.Identifier)
.ThenInclude(y=>y.Product))
{
contexts.destinationContext.StockTable.Add(item);
contexts.destinationContext.SaveChanges();
}
However, when saving the changes, I get an exception:
violation of FOREIGN KEY constraint "FK_EST_IDENT_PROD" on table
"ProductTable" Foreign key reference target does not exist
I understood EF Core should add StockTable's child properties as well (IdentifierTable, ProductTable, and distinct GroupsTable). What am I doing wrong here?
The reason I'm using FluentAPI is because I'm working with a pre-made database, I can't edit table/column names, nor relationships. Could it be because IdentifierTable.StockID is both primary key and foreign key?
I think I might have found a clue: Checking a tentative migration, I found EF Core is creating two foreign keys alright. However, it is creating both on IdentifierTable. The relationships I aim for are:
One StockTable has one and only one IdentifierTable.
One IdentifierTable has one or none ProductTable.
One StockTable has one or none GroupsTable.
One GroupsTable has none, one, or multiple StockTable.
I think I might not be able to use this database with EF Core. I tried scaffolding the models database-first, and EF Core didn't manage to understand a single foreign key relation.
trying to implement two instances of a 1 to 1 or 1 to zero navigation property in ef core using either data annotations or fluent api.
formation table is just id and name.
public class Formation {
public long Id { get; set; }
public string Name {get; set;}
}
the well class can have but does not need to have a deepestformation of type formation
the well class can have but does not need to have a producingformation of type formation
public class Well
{
public long Id { get; set; }
public long? DeepestFormationId { get; set; }
public Formation DeepestFormation { get; set; }
public long? ProducingFormationId { get; set; }
public Formation ProducingFormation { get; set; }
}
I am struggling how to set this up using either data annotations or the fluid api
I attempted
modelBuilder.Entity<Well>()
.HasOne(c => c.DeepestFormation)
.WithOne()
.HasForeignKey<Formation>(c => c.Id);
but where do I tell it that the foreign key is DeepestFormationId?
Firstly, I'd recommend you read ef core Definition of terms.
Principal key: The properties that uniquely identify the principal entity. This may be the primary key or an alternate key.
Foreign key: The properties in the dependent entity that are used to store the principal key values for the related entity.
Then you can try to change your mapping:
modelBuilder.Entity<Well>()
.HasOne(c => c.DeepestFormation)
.WithOne()
.HasForeignKey<Well>(c => c.DeepestFormationId);
modelBuilder.Entity<Well>()
.HasOne(c => c.ProducingFormation)
.WithOne()
.HasForeignKey<Well>(c => c.ProducingFormationId);
You optionally can specify the principle key - .HasPrincipalKey(item => item.Id), but Ef core smart enough to resolve it.
Then you need to consider OnDelete behavior. Be careful with it, if you set Cascade, in your case you might get Multiple cascade path exception
I have three layers of tables in an existing database and I'm trying to include the bottom level records when I get the middle level data... This should be a one to many relationship - for shipment x with product y there are z analysis results.
public class Shipment
{
[Key]
public int Id { get; set; }
public string ShipName { get; set; }
public DateTime ShipmentDate { get; set; }
}
public class ShipmentDetails
{
[ForeignKey ("ShipmentId")]
public int Id { get; set; }
[ForeignKey ("ProductId")]
public int ProductId { get; set; }
Public double Weight { get; set; }
public virtual ShippingAnalysis Analysis { get; set; }
}
public class ShipmentAnalysis
{
[ForeignKey ("ShipmentId")]
public int Id { get; set; }
[ForeignKey ("ProductId")]
public int TenantId { get; set; }
[ForeignKey ("MetricId")]
public int MetricId { get; set; }
Public double Result { get; set; }
}
I'm using the fluent api way of defining the composite primary keys.
modelBuilder.Entity<ShippingDetail>()
.HasKey(c => new { c.ShipmentId, c.ProductlId });
modelBuilder.Entity<ShippingAnalysis>()
.HasKey(c => new { c.ShipmentId, c.ProductId, c.MetricId });
I get the Shipping detail with the (one to many) analysis records.
var results = _context.ShippingDetail.Include(sd => sd.Analysis)
.Where(sd => sd.ShipmentId == id);
This does not return a result in postman, but through the browser returns malformed JSON. If I drop the include, it works fine.
The problem is not composite key, but navigation property (hence relationship definition). The navigation property at (one) side (when present) must be a collection and navigation property at (many) side should be reference - see Relationships - Definition of Terms.
According to
modelBuilder.Entity<ShippingDetail>()
.HasKey(c => new { c.ShipmentId, c.ProductlId });
modelBuilder.Entity<ShippingAnalysis>()
.HasKey(c => new { c.ShipmentId, c.ProductId, c.MetricId });
the relationship should be ShippingDetail (one) -> (many) ShippingAnalysis, hence
public virtual ShippingAnalysis Analysis { get; set; }
property of ShippingDetail must be
public virtual ICollection<ShippingAnalysis> Analysis { get; set; }
This should be enough for EF Core to determine the correct composite FK columns. But if you want to be hundred percent sure (being explicit never hurts), add the following fluent configuration:
modelBuilder.Entity<ShippingDetail>()
.HasMany(e => e.Analysis)
.WithOne() // make sure to specify navigation property if exists, e.g. e => e.NavProp
.HasForeignKey(e => new { e.ShipmentId, e.ProductId });
P.S. Remove all these [ForeignKey] data annotations. They do different things depending on whether they are applied on FK property or navigation property, and for sure don't do what you think, and sometimes may actually lead to unexpected behaviors. Based on my experience with EF Core relationships, either let EF Core conventions do their job, or use fluent API.
I want to have a intermediate table with only two foreign keys (as a ComposedId).
But NHibernate is automatically creating a "id" property.
I have the following classes
public class Lace
{
public virtual int Id { get; set; }
public virtual string Hostname { get; set; }
public virtual IList<LaceHasCard> LaceHasCards { get; set; }
}
public class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<LaceHasCard> LaceHasCards { get; set; }
}
and this manually created intermediate table
public class LaceHasCard
{
public virtual Card Card { get; set; }
public virtual Lace Lace { get; set; }
}
Mappings
public LaceMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Property(x => x.Hostname);
Bag(x => x.LaceHasCards, col =>
{
col.Key(k => k.Column("LaceId"));
col.Inverse(true);
}, r => r.OneToMany());
}
public CardMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Native));
Property(x => x.Name);
Bag(x => x.LaceHasCards, col =>
{
col.Key(k => k.Column("CardId"));
col.Inverse(true);
}, r => r.OneToMany());
}
intermediate table mapping
public LaceHasCardMapping()
{
//ComposedId(map =>
//{
// map.Property(x => x.Card.Id, a =>
// {
// a.Column("CardId");
// });
// map.Property(x => x.Lace.Id, a =>
// {
// a.Column("LaceId");
// });
//});
ManyToOne(x => x.Card, map =>
{
map.Column("CardId");
});
ManyToOne(x => x.Lace, map =>
{
map.Column("LaceId");
});
}
If I create the schema with the ComposedId commented out, NHibernate will create a "id" property in the table.
CREATE TABLE [dbo].[LaceHasCard] (
[id] INT NOT NULL,
[CardId] INT NULL,
[LaceId] INT NULL,
PRIMARY KEY CLUSTERED ([id] ASC),
CONSTRAINT [FKDC6D54711CD160AE] FOREIGN KEY ([CardId]) REFERENCES [dbo].[Card] ([Id]),
CONSTRAINT [FKDC6D547151F8AF85] FOREIGN KEY ([LaceId]) REFERENCES [dbo].[Lace] ([Id])
);
If I try to create the schema with the ComposedId, I get the following error message:
Unable to instantiate mapping class (see InnerException):
EmpLaceMgmt.Models.Mappings.LaceHasCardMapping
What would be the right way to tell NHibernate to create a composed Id?
Let me give you suggestion, just my point of view - do not use composite id. Use standard primary key in DB and its C# / entity representation as Id { get; set; }
Chapter 24. Best Practices
...
Declare identifier properties on persistent classes.
NHibernate makes identifier properties optional. There are all sorts of reasons why you should use them. We recommend that identifiers be 'synthetic' (generated, with no business meaning) and of a non-primitive type. For maximum flexibility, use Int64 or String.
See also more about synthetic, surrogate keys at wiki.
From my experience, we should not be worry about having pairing object like this:
public class LaceHasCard
{
public virtual int Id { get; set; } // the key
public virtual Card Card { get; set; }
public virtual Lace Lace { get; set; }
}
Because later it would become so easy to access it:
session.Get<LaceHasCard>(id)
And also to use it in Subqueries (for filtering Card with Laces and vice versa)
One column in DB, autogenerated, should not have any extra bad impact. But handling such table is a bit (a lot) easier...
So, summary, my suggestion would be, make all entities first level citizens, with full rights (including synthetic/surrogate key)
I'm developing an Entity Framework Code First (v. 4.4.0.0) C# library with .Net Framework 4.0.
I don't know how to set zero-to-one relationship. My model is the following:
A Talk can be created by only one user (StarterUserId).
A Talk can have only one recipient user (RecepientUserId) or only one group (RecipientGroupId).
Note: That means that RecepientUserId is null if RecipientGroupIdis not null; or RecepientUserId is not null if RecipientGroupIdis null.
A user can be a recipient of zero or n Talks, but a group can have zero or one Talk.
This is Talk class:
[DataContract]
public class Talk
{
[DataMember]
public int TalkId { get; set; }
[DataMember]
public int StarterUserId { get; set; }
[DataMember]
public int? RecipientUserId { get; set; }
[DataMember]
[ForeignKey("RecipientGroup")]
public int? RecipientGroupId { get; set; }
public DateTime DateUtcStarted { get; set; }
[DataMember]
public string DateStarted
{
get
{
return DateUtcStarted.ToString("dd/MM/yyyy HH:mm");
}
set
{
DateUtcStarted = DateTime.Parse(value);
}
}
public User StarterUser { get; set; }
public User RecipientUser { get; set; }
public Group RecipientGroup { get; set; }
}
With this TalkConfiguration class:
class TalkConfiguration : EntityTypeConfiguration<Talk>
{
public TalkConfiguration()
{
Property(t => t.StarterUserId).IsRequired();
Property(t => t.RecipientUserId).IsOptional();
Property(t => t.RecipientGroupId).IsOptional();
Property(t => t.DateUtcStarted).IsRequired();
Ignore(t => t.DateStarted);
HasRequired(t => t.StarterUser).
WithMany(u => u.TalksStarted).
HasForeignKey(t => t.StarterUserId);
HasOptional(t => t.RecipientUser).
WithMany(u => u.InTalks).
HasForeignKey(t => t.RecipientUserId);
HasOptional(t => t.RecipientGroup).WithOptionalDependent(g => g.GroupTalk);
}
}
And this is the Group class:
[DataContract]
public class Group
{
[DataMember]
public int GroupId { get; set; }
[ ... ]
public Talk GroupTalk { get; set; }
}
And the GroupConfiguration class:
class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
[ ... ] // Nothing related to GroupTalk
}
}
With these classes and configurations I get this Talk table at database:
I want to make Talk.RecipientGroupId as a FOREIGN KEY to Group.GroupId. But this model creates another column, Talk.RecipientGroup_GroupId as FOREIGN KEY to Group.GroupId. And, I don't want that.
How can I do it?
Optional:optional one-to-one relationships are mapped as independent associations, not as foreign key associations which means that you can't have a foreign key property in your model class. That's why you can't chain HasForeignKey after WithOptionalDependent. And I'm pretty sure that the [ForeignKey] attribute on RecipientGroupId is simply ignored and EF considers RecipientGroupId as an ordinary scalar property with no relationship purpose.
In the database schema itself the relationship has a foreign key. That's the one you are seeing with an autogenerated default name: RecipientGroup_GroupId. But it's not supported to map this foreign key to a property. However, I think you can rename the column using MapKey
HasOptional(t => t.RecipientGroup)
.WithOptionalDependent(g => g.GroupTalk)
.Map(m => m.MapKey("RecipientGroupId"));
If you do that you must remove the RecipientGroupId property from the Talk class, otherwise EF will complain about two conflicting columns with the same name.
I believe, optional:optional are the only one-to-one relationships that are independent associations, all other are foreign key associations where the foreign key property is the primary key property at the same time (according to Arthur Vickers' answer at the bottom of this thread). With optional:optional relationships this would be impossible because a primary key property cannot be nullable.
Since your RecipientGroupId has a [DataMember] attribute it looks that you want to transmit the value over some service boundary and therefore need the foreign key as property value for some reason. In this case the workaround that I would choose is mapping the Talk<->Group relationship as one-to-many relationship with either no navigation property in the Group class at all (mapping it with a parameterless WithMany() call then) or with a collection navigation property and ensure then in business logic that this collection cannot contain more than one element.