I've used EF Power Tools to auto generate my models from an existing database. Im using DataAnnotations to perform required validation, which works for most part except when validating properties that have foreign key relationship with other tables (one to many, etc..). What do I need to do in order to accomplish the validation of these properties?
In the below code, I'm trying to make DistributorId property a required field.
public class Event
{
public Event()
public int EventId { get; set; }
[Remote ("CheckDuplicateEventName","Event",AdditionalFields="InsertMode")]
[Required(ErrorMessage = "Event Name is required.")]
public string Name { get; set; }
[Required(ErrorMessage = "Distributor is required.")]
public int DistributorId { get; set; }
public virtual Distributor Distributor { get; set; }
}
Mapping class
public EventMap()
{
// Primary Key
this.HasKey(t => t.EventId);
// Properties
this.Property(t => t.Name)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
this.ToTable("Events");
this.Property(t => t.EventId).HasColumnName("EventId");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.DistributorId).HasColumnName("DistributorId");
// Relationships
this.HasRequired(t => t.Distributor)
.WithMany(t => t.Events)
.HasForeignKey(d => d.DistributorId);
}
Tnx!
This is very simply because Name is a string (which is nullable), while DistributorId is an int (not nullable). That means that DistributorId always exists (although can be not the value you looking for, but still [Required] validation is satisfied.
What you probably want to change is to
either replace [Required] with something that will validate the actual value [Range] is great example here.
or have DistributorId as string converting it to int before writing to the database.
Hope this helps
Related
I have created the following classes:
public class Character
{
public int ID { get; set; }
public string Title { get; set; }
public ICollection<Relationship> RelatedTo { get; set; }
public ICollection<Relationship> RelatedFrom { get; set; }
}
public class Relationship
{
public int ToID { get; set; }
public int FromID { get; set; }
public Character CharacterFrom { get; set; }
public Character CharacterTo { get; set; }
public string Details { get; set; }
}
In my Context I have this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasKey(r => new { r.ToID, r.FromID });
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterFrom)
.WithMany(r => r.RelatedTo)
.HasForeignKey(r => r.FromID)
.OnDelete(DeleteBehavior.ClientSetNull);
modelBuilder.Entity<Relationship>()
.HasOne(r => r.CharacterTo)
.WithMany(r => r.RelatedFrom)
.HasForeignKey(r => r.ToID)
.OnDelete(DeleteBehavior.ClientSetNull);
}
I think that it is right but I cannot apply the migration due to the following error:
Cannot create the foreign key "FK_Relationship_Character_FromID" with the SET NULL referential action, because one or more referencing columns are not nullable.
I've tried every combination of DeleteBehaviour for OnDelete. None of them work. I don't believe I can make the ICollections nullable and it doesn't seem right that I'd want to. I've spent two hours on this searching for answers. Every tutorial or explanation on EF Core that I've tried to follow seems to take a slightly different approach and be subtly incompatible with every other one. Please help!
The error is telling you that you cannot use DeleteBehavior.ClientSetNull (or DeleteBehavior.SetNull) because the corresponding FK property is not nullable - both ToID and FromID are of type int, hence does not allow setting to null (neither client nor server).
To turn off the cascade delete (in order to break the multiple cascade paths I guess) for required FK relationships, use DeleteBehavior.Restrict instead.
I have a problem, I have to create a model where we have two entities which CAN be linked together but can also exist as stand alone entities.
The model currently looks like this:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
public int? AuthorisationID{ get; set; }
}
The reason for this is that we can have an authorization record independent of a change, and some changes require authorisation and some dont, so neither side of the relationship is required.
I can configure this with the fluent API like so:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation);
And alls well, Except that the migration that it creates looks like this
CreateTable(
"dbo.Changes",
c => new
{
ChangeID = c.Int(nullable: false, identity: true),
AuthorisationID = c.Int(),
Authorisation_AuthorisationID = c.Int(),
})
.PrimaryKey(t => t.ChangeID)
.ForeignKey("dbo.Authorisations", t => t.Authorisation_AuthorisationID)
.Index(t => t.Authorisation_AuthorisationID);
EF is deciding that its going to add a new column (Authorisation_AuthorisationID) for me to use as the FK between the two entities, what I really want to be able to do is to use the change.AuthorisationID property as the FK onto the Authorisation, I cannot find a way to configure this at all (Please note that I need the FK to be in the model - for consistency with the rest of the app more than anything else).
To sum up I need to be able to create a relationship between two entities where both sides of the relationship are optional and if possible I want to be able to define the FK column myself.
Am I just approaching this wrong? Ive been staring at the same block of code for so long I could be missing something simple.
Looks like explicit foreign key property is not supported for one-to-one relationships - there is no HasForeignKey Fluent API and also if you put ForeignKey attribute on the navigation property you get exception saying that multiplicity must be *.
So the only choice you have is to remove the explicit Change.AuthorisationID property and work only with navigation properties:
Model:
public class Authorisation
{
public int AuthorisationID { get; set; }
public virtual Change Change { get; set; }
}
public class Change
{
public int ChangeID{ get; set; }
public virtual Authorisation Authorisation { get; set; }
}
Configuration:
modelBuilder.Entity<Authorisation>()
.HasOptional(t => t.Change)
.WithOptionalPrincipal(t => t.Authorisation)
.Map(a => a.MapKey("AuthorisationID"));
Unable to determine the principal end of an association between the
types 'XYZ.Models.Attachment' and 'XYZ.Models.Accounts.User'. The
principal end of this association must be explicitly configured using
either the relationship fluent API or data annotations.
Exception has been thrown by the target of an invocation.
That error I get, when I try to update-database with my EF Models.
Part of User.cs:
[Table("Users")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public int MainPhotoId { get; set; }
[ForeignKey("MainPhotoId")]
public virtual Attachment Photo { get; set; }
}
Attachment.cs
[Table("Attachments")]
public class Attachment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AttachmentId { get; set; }
public string name { get; set; }
public int UserId { get; set; }
public DateTime AddDate { get; set; }
public bool del { get; set; }
[ForeignKey("UserId")]
public virtual User Author { get; set; }
}
Why I get this error? And how to resolve it?
Regards
Mapping conventions detect a one-to-one relationship between User.Photo and Attachment.Author and cannot infer what the principal and what the dependent end is. Hence the exception.
Actually, according to your comments, you want two relationships and not a single one-to-one relationship. You can achieve that only by overriding the convention with Fluent API and you probably need to make one of the relationships optional because otherwise you have a circular mutual dependency between User and Attachment. You can, for example, make the User.Photo property optional by choosing a nullable foreign key:
public int? MainPhotoId { get; set; }
Then the mapping would look like this:
modelBuilder.Entity<User>()
.HasOptional(u => u.Photo)
.WithMany()
.HasForeignKey(u => u.MainPhotoId);
modelBuilder.Entity<Attachment>()
.HasRequired(a => a.Author)
.WithMany()
.HasForeignKey(a => a.UserId);
With this mapping you can remove the [ForeignKey] attributes because the definition of the FK properties is part of the Fluent API mapping (HasForeignKey).
I'm using the CTP 5 of EF 4 and Code first.
I don't get a many-many relation working with a composite key on one side.
modelBuilder.Entity<Item>()
.HasMany(i => i.Categories)
.WithMany(o => o.Items)
.Map(
mc =>
{
mc.ToTable("ItemCategories");
mc.MapLeftKey(i => i.Id, "ItemId");
mc.MapRightKey(o => o.TemplateID, "TemplateId");
mc.MapRightKey(o => o.ItemId, "ItemId");
}
);
So instead of having a simple key for Categories in my matching table, I've got a composite one. And one part of the composite key is also the key for the Item type,
which seems to be the problem here.
I get the error: "Each property name in a type must be unique. Property name 'ItemId' was already defined."
How can I configure EF to use a composite key in this case?
Of course you cannot have 2 columns with the same name within one table. This will work:
modelBuilder.Entity<Item>()
.HasMany(i => i.Categories)
.WithMany(c => c.Items)
.Map(m =>
{
m.MapRightKey(i => i.Id, "ItemId");
m.MapLeftKey(c => c.ItemId, "ItemId2");
m.MapLeftKey(c => c.TemplateId, "TemplateId");
});
public class Category
{
[Key]
public string ItemId { get; set; }
[Key]
public string TemplateId { get; set; }
public string Value { get; set; }
public ICollection<Item> Items { get; set; }
}
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
}
The mapping table ItemCategories is not a POCO, but used for mapping those 2 as
shown.
It has SQL columns
Id (own primary key)
ItemId (FK to Item table and Category table)
TemplateId (FK to Category table)
and another ID column which maps to a different table.
In my opinion the only difference here to "normal" many-many scenario is the composite key
in the ItemCategories table, which builds the relation to the Category table.
I'm using Fluent NHibernate in an attempt to improve testability and maintainability on a web application that is using a legacy database and I'm having some trouble mapping this structure properly:
I have two tables that really represent one entity in the domain, and so I'm using a join to map them as such, and a third table that represents a second entity.
DB Tables:
[eACCT]
ACCT_ID
ACCT_NAME
[eREPORT_TYPE]
ACCT_ID
REPORT_NO
[eREPORT_TYPE_DESC]
REPORT_NO
REPORT_TYPE
Entities:
public class Account
{
public virtual string AccountID { get; set; }
public virtual string AccountName { get; set; }
public virtual ReportType ReportType { get; set; }
}
public class ReportType
{
public virtual int Number { get; set; }
public virtual string Type { get; set; }
}
Mapping:
public AccountMap()
{
Table("eACCT");
Id(x => x.AccountID, "ACCT_ID");
Map(x => x.AccountName, "ACCT_NAME");
Join("eREPORT_TYPE", m =>
{
m.KeyColumn("ACCT_ID");
m.References(x => x.ReportType)
.Cascade.None()
.Column("REPORT_NO");
});
}
public ReportTypeMap()
{
Table("eREPORT_TYPE_DESC");
Id(x => x.Number)
.Column("REPORT_NO")
.GeneratedBy.Assigned();
Map(x => x.Type, "REPORT_TYPE");
}
This works fine for my Gets, but when I modify Account.ReportType.Number and then SaveOrUpdate() Account, I get the error: 'identifier of an instance of DataTest.Model.ReportType was altered from (old_value) to (new_value)'.
All I want to do is modify Account's reference to ReportType and I thought that by setting the Cascade.None() property on the reference to ReportType, NHibernate wouldn't attempt to save the ReportType instance as well, but I must be misunderstanding how that works. I've tried making ReportType ReadOnly(), making the reference to ReportType ReadOnly(), etc and nothing seems to help.
Any ideas?
Finally solved this problem. Turns out I wasn't thinking about this in an NHibernate way. In my mind I had a new ReportType.Number, so that's what I needed to update. In reality, what I needed to do was get the ReportType with the new ReportType.Number and set the Account.ReportType. Doing it this way worked as expected.