I need to create a unique constraint on multiple fields, and those fields are ValueObjects.
Let's say I have this
public class MyEntity
{
public EntityCode Code {get;set;}
public SecondaryCode Second {get;set;}
}
public class EntityCode : ValueObject<string>
{
public string Value {get;set;}
public string Description {get;set;}
}
public class SecondaryCode: ValueObject<string>
{
public string Value {get;set;}
public string Description {get;set;}
}
I can create a unique constraint like this
public class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
...
builder.OwnsOne(p => p.Code)
.HasIndex(p => p.Value)
.IsUnique()
...
}
But I would like a composite key and I can't figure out how to do this with value object.
Take a look at this sample, it is not working as I expected
builder.OwnsOne(p => p.Code);
builder.OwnsOne(p => p.Second);
builder
.HasIndex(p => new { p.Code.Value, p.Seconde.Value })
.IsUnique()
Any help would be appreciated
A key serves as a unique identifier for each entity instance. Most entities in EF have a single key, which maps to the concept of a primary key in relational databases. You can also configure multiple properties to be the key of an entity - this is known as a composite key.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
builder.Entity<MyEntity>()
.HasKey(p => new { p.Code.Value, p.Seconde.Value });
}
Composite keys can only be configured using the Fluent API, conventions will never set up a composite key, and you can not use Data Annotations to configure one.
To create this composite primary key with these two columns, override DbContext.OnModelCreating(). This method is called when the model for a derived context has been initialized, but before the model has been locked down and used to initialize the context.
public class MyDbContext : DbContext
{
// Normal DbContext stuff here
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
builder.Entity<MyEntity>()
.HasKey(p => new { p.Code.Value, p.Seconde.AnotherValue });
}
}
after that generate the migration and update database.
Related
This is a tale of optional owned entities and foreign keys.
I'm working with EF 5 (code first) and I do this :
public class Parent {
public Guid Id { get; private set; }
public OwnedType1? Owned1 { get; private set; }
public OwnedType2? Owned2 { get; private set; }
public Parent(Guid id, OwnedType1? owned1, OwnedType2? owned2) {
Id = id; Owned1 = owned1; Owned2 = owned2;
}
}
public class OwnedType1 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType1 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class OwnedType2 {
public Guid? OptionalExternalId { get; private set; }
public OwnedType2 (Guid? optionalExternalId) {
OptionalExternalId = optionalExternalId;
}
}
public class Shared {
public Guid Id { get; private set; }
public Shared (Guid id) {
Id = id;
}
}
Now, the configuration :
//-------- for Parent ------------
public void Configure(EntityTypeBuilder<Parent> builder) {
builder
.ToTable("Parents")
.HasKey(p => p.Id);
builder
.OwnsOne(p => p.Owned1)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
builder
.OwnsOne(p => p.Owned2)
.HasOne<Shared>()
.WithMany()
.HasForeignKey(x => x.OptionalExternalId);
}
//-------- for OwnedType1 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for OwnedType2 ------------
// (there's no builder as they're owned and EntityTypeBuilder<Parent> is enough)
//-------- for Shared ---------------
public void Configure(EntityTypeBuilder<Shared> builder) {
builder
.ToTable("Shareds")
.HasKey(p => p.Id);
}
Side note : If you're wondering why OwnedType1 and OwnedType2 don't each have a property called 'ParentId', it's because it's created implicitly by the "OwnsOne".
My problem is this :
When I create a new Migration, then OwnedType1 works like a charm, but for OwnedType2 (which is quasi-identical), I get his error :
The property 'OptionalExternalId' cannot be added to the type
'MyNameSpace.OwnedType2' because no property type was specified and
there is no corresponding CLR property or field. To add a shadow state
property, the property type must be specified.
I don't understand what it's complaining about. And why it's complaining only for one of them.
I know that you probably can't work it out with this simplified version of my schema, but what I'm asking is what you think it might be (follow your guts of EF guru) :
Some missing constructor?
Incorrect visibility on one of the fields?
Bad navigation definition?
A typo?
Something tricky (like : If you're going to have TWO different entity classes having a one-to-many relation with Shared, then they can't use the same name for external key. Or I need to use a composite key. Or whatnot).
It was a configuration issue that had nothing to do with Owned entities. Another case of "EF error message is obscure but issue is somewhere there in plain sight".
Unfortunately I don't remember how I fixed it. But it was along the lines of "Need an extra constructor with all the paramaters" or "one of the fields had a different name in the constructor parameters" or one of those classic EF mishaps.
How can i create a composite primary key in parent using fields in a valueobject ?
The valueobject will not have an own table in the database, i want these two props to be inserted into the parents table.
ie.
Entities
public class Parent
{
public string Name { get;set; }
public MyValueObject MyValueObj { get;set; }
}
public class MyValueObject
{
public int Id { get;set; }
public int SSN { get;set; }
}
DbContext for parent
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().Property(new { p.MyValueObj.Id, p.MyValueObj.SSN}).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
I want the Parent table to look like this:
Composite PK
-----------
Id SSN Name
1 000000 Mikael
If you can use inheritance then it should look like this:
public class Parent : MyValueObject
{
public string Name { get;set; }
}
public class MyValueObject
{
public int Id { get;set; }
public int SSN { get;set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().Property(new { p.Id, p.SSN}).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
The reason why your implementation won't work is pretty simple - Entity Framework just can't transform your complex object (which is set as property) to SQL field, so it will try to find it as a referenced object in other table. In the code I've provided you don't have any complex objects as a property, so EF will easily map all properties to SQL columns.
UPDATE
Actually, I did some more investigations and found an article stating that you can use complex types. I have never used this (and even seen) before, so can't describe you all sides of this question, but what I see from article is that this complex property can't be optional, so you will need always initialize that. Also I can only suppose, but probably you don't even need to mark all fields of that complex type as primary key, so you should have something just like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>().Property(p => p.MyValueObj).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
Not sure if that will work, just give it a try :)
I'm trying to create a required:optional relationship between two entities, with the required entity exposing a navigation property to the optional entity, and the optional entity containing the foreign key, used as its primary key. This is what my two entities look like:
class OptionalEntity
{
public string RequiredEntityID { get; set; }
}
class RequiredEntity
{
public string ID { get; set; }
public OptionalEntity Optional { get; set; }
}
And the way I would like to configure them in fluent API is as follows:
// Inside OptionalEntityConfiguration class
public OptionalEntityConfiguration()
{
HasKey(r => r.RequiredEntityID);
}
// Inside RequiredEntityConfiguration class
public RequiredEntityConfiguration()
{
HasKey(r => r.ID);
HasOptional(r => r.Optional)
.WithRequired();
// How can I configure this relationship to use
// the RequiredEntityID property as the foreign key?
HasOptional(r => r.Optional)
.WithRequired(o => o.RequiredEntityID);
// This is invalid because it requires a navigation property, not an ID
HasOptional(r => r.Optional)
.WithRequired()
.HasForeignKey(o => o.RequiredEntityID);
// The HasForeignKey method isn't available here
}
First of all is this possible, and if so what's the correct way to configure this relationship using fluent API?
I think what you are trying to do is use the same key used on the Required Entity on the Optional Entity table, so that they share the same key.
If that is the case, I think you are on the right track. Your entity classes look alright. You can map them like these:
public OptionalEntityConfiguration()
{
HasKey(r => r.RequiredEntityID);
Property(r => r.RequiredEntityID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
public RequiredEntityConfiguration()
{
HasKey(r => r.ID);
HasOptional(r => r.Optional);
}
This implies, however, that there can only be 0 or 1 OptionalEntity for each RequiredEntity.
Try this:
modelBuilder.Entity<RequiredEntity>()
.HasOptional(o => o.Optional)
.WithMany()
.Map(m => m.MapKey("RequiredEntityID"));
I have an employee table that self references to determine organization structure. I'm having some trouble trying to set this up using Code-First (POCO) fluently.
An employee record has both a "Position" field and a "ReportsTo" field and neither of the columns are the primary key (employee.id).
An employee with a "ReportsTo" value of "08294" , is an employee of a direct report of an employee with "Position" value of "08294".
Can anyone offer up some info on how to set this up using EF code first, fluently...is it possible?
I tried the code below and am getting error:
Employee_Employees_Source_Employee_Employees_Target: : The types of
all properties in the Dependent Role of a referential constraint must
be the same as the corresponding property types in the Principal Role.
The type of property 'ReportsTo' on entity 'Employee' does not match
the type of property 'Id' on entity 'Employee' in the referential
constraint 'Employee_Employees'.
Employee.cs
public class Employee
{
public int Id { get; set; } //pk
public string Position { get; set; } // i.e. 06895
public string ReportsTo{ get; set; } // i.e. 08294
public virtual Employee Supervisor { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
DbContext
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.ReportsTo);
I think more than anything, I would like to keep the POCO free of EF "stuff" and be able to do something like:
employee.IsSupervisor(); // based on child employee count.
The issue is in the relationship configuration. If you want to configure your one to many relation without using a FK, you could do this:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor);
Now if you want to use a FK property, then add this property to your model class:
public class Employee
{
//...
public int SupervisorId { get; set; }
}
And map your relationship this way:
modelBuilder.Entity<Employee>()
.HasMany(e => e.Employees)
.WithOptional(e => e.Supervisor)
.HasForeignKey(e => e.SupervisorId);
To resolve your issue related with ReportTo and Position properties,I think you should handle that logic in your code. If you want to know if an Employee is a supervisor based on the count of Employees property, you could use a NotMapped property:
public class Employee
{
[NotMapped]
public bool IsSupervisor
{
get
{
return Employess.Count>0
}
}
}
You can do the same using Fluent Api:
modelBuilder.Entity<Employee>().Ignore(e => e.IsSupervisor);
PS: Remember initialize Employees in your class'constructor.
The error you get is because it is trying to map a PK of int type to a FK of string type. User int for all of your key fields.
Then, you need to declare your OnModelBuilding like this:
modelBuilder.Entity<Employee>()
.HasOptional(e => e.Supervisor)
.WithMany()
.HasForeignKey(s => s.ReportsTo);
To get something like IsSupervisor() you can take advantage of partial classes. Create another class file which is a public partial class Employee (and modify your original one to be partial), then in your new file you will add a property that does whatever you want, and decorate it with [NotMapped] attribute. Yours will probably look something like public bool IsSupervisor {get { return (Employees == null) ? false : true; } set {} } The new partial class is where you can do whatever you want for the POCO without changing the EF class (make sure you use [NotMapped] though).
I am working with Entity Framework 5 code-first and I have a situation where I have an entity which has an Identity column that is not part of the primary key. When I add a new record and invoke SaveChanges on the context, I get a ConcurrencyException. If I change the primary key on the entity map to SCHED_ID or remove SCHED_ID from the entity map altogether, SaveChanges completes without issue. I suspect that since SCHED_ID is defined as an int, it defaults to a value of zero and Entity Framework thinks it has changed when the database actually assigns it a value (thus causing a ConcurrencyException). Am I correct in my assumption? How can I work around this without changing the key on the entity map to SCHED_ID?
The entity is defined as follows:
public partial class Sched
{
public int SCHED_ID { get; set; }
public System.DateTime DATE_QUEUED { get; set; }
public string STATUS_CODE { get; set; }
}
public class SchedMap : EntityTypeConfiguration<Sched>
{
public SchedMap()
{
// Primary Key
this.HasKey(t => new { t.STATUS_CODE, t.DATE_QUEUED });
//Properties
this.Property(t => t.SCHED_ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsConcurrencyToken(false);
// Table & Column Mappings
this.ToTable("Sched");
this.Property(t => t.SCHED_ID).HasColumnName("SCHED_ID");
this.Property(t => t.STATUS_CODE).HasColumnName("STATUS_CODE");
this.Property(t => t.DATE_QUEUED).HasColumnName("DATE_QUEUED");
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SchedMap());
}