I'm using Entity Framework Core 3.1.7 and created an entity called Event, which I set up like this:
public class Event
{
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; }
}
The entity configuration looks like this:
builder.Property(e => e.FirstOccurred)
.IsRequired();
I use my dbContext to persist the entity like this:
await dbContext.Events.AddAsync(new Event());
In this scenario, I was incorrectly expecting that an exception would be thrown at the Database level because the value can't be null.
What actually happens is: the entity is happily persisted with FirstOccurred set to 0001-01-01T00:00:00+00:00
This makes sense, because the default value of DateTimeOffset is used.
Now my question: How could I improve my design to prevent this default value from being inserted?
Some ideas I had already:
Leave the above code as is, but make sure that wherever the entity is used, I'm setting the values correctly. Downside: no guarantee that this will be applied consistently in a team over time.
Make DateTimeOffset nullable, which in the above AddAsync() call would actually cause an SQL exception. Downside: At first glance, DateTimeOffset? FirstOccurred might be confusing because the actual DB constraints don't allow null
Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)
I think you're on the right track with your last idea.
Remove set; for FirstOccurred and create a constructor that requires this property to be set, e.g. new Event(DateTimeOffset.Now)
It doesn't make sense to track an Event without the timestamp and it certainly doesn't make sense to use the default value for the timestamp.
Changing your model to require a value for the timestamp ensures that you are not writing default data to the record and prevents confusion from seeing a nullable model field when the corresponding table column is non-nullable.
public class Event {
public Event (DateTimeOffset firstOccurred) { FirstOcurred = firstOcurred; }
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; }
}
Just a note from the documentation, don't remove the set; accessor, just mark it private if you don't want the value to change after construction.
Once properties are being set via the constructor it can make sense to make some of them read-only. EF Core supports this, but there are some things to look out for:
Properties without setters are not mapped by convention. (Doing so tends to map properties that should not be mapped, such as computed properties.)
Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities.
An easy way to avoid these things is to use private setters.
Of course, you could also maintain the flexibility of the parameter-less constructor by overriding the default value for the property.
public class Event {
public long Id { get; set; }
public DateTimeOffset FirstOccurred { get; set; } = DateTimeOffset.Now;
}
Related
Using EF Core 5.0, I have a PK-less entity (from a SQL view) OrderInfo, which has a column OrderDetailId. I also have an entity DiscountOrder which a PK from the columns OrderDetailId and DiscountId.
I would like to create a navigation property from Order to DiscountOrders. Such as:
public class OrderInfo
{
public int OrderDetailId { get; set; }
public virtual ICollection<DiscountOrder> DiscountOrders { get; set; }
}
public class DiscountOrder
{
public int DiscountId { get; set; }
public int OrderDetailId { get; set; }
}
// For reference, this entity also exists
public class Discount
{
public int DiscountId { get; set; }
}
Obviously, there are no FKs to make use of, but I should be able to create a navigation property anyway.
I think I should be able to do this:
modelBuilder.Entity<OrderInfo>(e =>
{
e.HasNoKey();
e.HasMany(x => x.DiscountOrders)
.WithOne()
.HasPrincipalKey(o => o.OrderDetailId)
.HasForeignKey(pb => pb.OrderDetailId)
.IsRequired(false);
});
But a query on DbSet<OrderInfo> results in a NullReferenceException with the breakpoint landing on the HasMany() line. That said, I don't do anything with the DiscountOrders property, so the exception seems like it would have to be configuration related.
I've looked at answers to similar questions, but most answers use HasOne().WithMany() where as I'd like to keep this definition on OrderInfo since we don't really care about the other direction. How can I correctly set up this mapping?
Keyless entities (entity without key) cannot be principal of a relationship, because there is no key to be referenced by the FK property of the dependent.
Note that by EF Core terminology key is primary key. There are also alternate (unique) keys, but EF Core does not enable them for keyless types.
So basically HasNoKey() disables alternate keys and relationships to that entity. Just the exception is unhandled, hence not user friendly. For instance, if you try to predefine the alternate key referenced by .HasPrincipalKey(o => o.OrderDetailId) in advance
e.HasNoKey();
e.HasAlternateKey(o => o.OrderDetailId);
you'll get much better exception message at the second line
The key {'OrderDetailId'} cannot be added to keyless type 'OrderInfo'.
Shortly, e.HasNoKey(); and `.HasPrincipalKey(o => o.OrderDetailId); are mutually exclusive.
The only way to make it work is to define PK for OrderInfo even though it does not exist in database. In fact if OrderDetailId was supposed to be alternate key, in other words, is unique in the returned set, then you can safely map it as PK
//e.HasNoKey();
e.HasKey(o => o.OrderDetailId);
If it is not unique, then nothing can be done - you cannot map and use navigation property, and will be forced to use manual joins in L2E queries.
Update: EF Core also blocks changing "keyless"-ness once it's been set via fluent API (which has the highest configuration priority). So if you can't remove HasNoKey() fluent call because of it being generated by reverse engineering, you have to resort to metadata API to make it again "normal" entity by setting the IsKeyless property to false before defining the key, e.g.
e.HasNoKey(); // generated by scaffolding
e.Metadata.IsKeyless = false; // <--
e.HasKey(o => o.OrderDetailId); // now this works
I am getting SQL errors when trying to use REST to get to FSAppointmentDet.InventoryID, either as a Field Service service item or as an Inventory Item.
The InventoryID field exists in the table, however, it looks like the DACs have been inherited, for example as FSAppointmentDetService.
Other fields work, it just seems that the fields with an ID are causing the SQL error.
In this case, the SQL error is a multi-step identifier not found. Running a SQL Profiler trace and looking at the SQL, it looks like the table has been aliased in one part of the query and not in another. Obviously this is occurring at a level much lower than we can get to, so looking for a workaround or ideas on how to get the InventoryID for Field Service detail records.
I've seen this happen when one DAC herits (herits as in class inheritance not extend as in DAC extension) from another DAC without redeclaring it's key fields. The way to fix that is to add the parent keys abstract class fields in the children.
FSAppointmentDetService seems to be missing AppointmentID key declaration. When the ORM builds the SQL query it generates Alias for the herited DAC but it gets confused becaused the key fields of the parent were not all re-declared in the child.
In FSAppointmentDet you have 2 key fields:
#region AppointmentID
public abstract class appointmentID : PX.Data.IBqlField
{
}
[PXDBInt(IsKey = true)]
[PXParent(typeof(Select<FSAppointment, Where<FSAppointment.appointmentID, Equal<Current<FSAppointmentDet.appointmentID>>>>))]
[PXDBLiteDefault(typeof(FSAppointment.appointmentID))]
[PXUIField(DisplayName = "Appointment Nbr.")]
public virtual int? AppointmentID { get; set; }
#endregion
#region AppDetID
public abstract class appDetID : PX.Data.IBqlField
{
}
[PXDBIdentity(IsKey = true)]
public virtual int? AppDetID { get; set; }
#endregion
But in FSAppointmentDetService only one of them is redeclared. Notice how it's using 'override' to redeclare compared to FSAppointmentDet which do not override:
#region AppDetID
public new abstract class appDetID : PX.Data.IBqlField
{
}
[PXDBIdentity(IsKey = true)]
public override int? AppDetID { get; set; }
#endregion
In this case we can't add field to that DAC though because it's part of the base product. I think it would be possible to create a new DAC that herits from FSAppointmentDetService, add the missing key in there and use that new herited DAC instead of FSAppointmentDetService.
However I don't know if that would be possible when working with Web Services. If not the change will have to be made in Acumatica base product. You could fill a bug report with Acumatica support to have that done in future versions.
I have two entities:
//The master table/entity
[TABLE("POSITIONS")]
public class Position{
[Key,Column("POSITIONID")]
public int PositionId{get;set;}
[Column("POSITIONNAME")]
public string PositionName{get;set;}
}
//The detail table/entity
[TABLE("SLAVE_POSITIONS")]
public class SlavePosition{
[Key,Column("MASTERPOSID",Order=0)]
public int MasterPosId{get;set;}
[KEY,Column("SLAVEPOSID",Order=1)]
public string SlavePosId{get;set;}
[ForeignKey("MasterPosId")]
public virtual Position MasterPosition {get;set;}
[ForeignKey("SlavePosId")]
public virtual Position SlavePosition {get;set;}
}
In the SlavePosition, as you can see, there two columns over which this entity is in FK relationship with Position. This layout works great. Now I also need to add this collection property to Position entity:
public virtual ICollection<SlavePosition> SlavePositions{get;set;}
But apparently EF gets confused and I get {"ORA-00904: \"Extent1\".\"Position_PositionId\": invalid identifier"} error.
If I declare it like this:
[ForeignKey("SlavePositionId")]
public virtual ICollection<SlavePosition> SlavePositions { get; set; }
and then fetch a Position with PositionId =1 like this:
Position pos= dbContext.Positions.SingleOrDefault(x=>x.PositionId==1);
I get no error, but I get SlavePOsitions count 0, when it should be 5 because in the database I have 5 rows in the detail table. I am able to confirm this by running the below code:
IEnumerable<SlavePositions> slavePositions= dbcontext.SlavePositions.Where(x=>x.MasterPositionId==1);
I get five SlavePosition.
What should be the correct attribute for this collection property?
I finally figured it out. My mistake was in the referenced dependent property name. Instead of SlavePositionId I should put MasterPositionId.
This makes sense, because the Position entity acts as a master table and in real world Foreign Key relationship is set up on detail tables, not master ones. As there's no property in the dependent entity that has the same name as the PK in the master entity and there're more than one properties that have Foreignkey to the same master entity, EF needs more information.By specifying ForeignKey("MasterPositionId") to the ICollection navigation property, I instruct EF that Dependent end point property should be considered MasterPositionId. So I changed this
[ForeignKey("SlavePositionId")]
public virtual ICollection<SlavePosition> SlavePositions { get; set; }
to this
[ForeignKey("MasterPositionId")]
public virtual ICollection<SlavePosition> SlavePositions { get; set; }
In fact the former one itself is not wrong either, it just does not fit in this situation. But if I wanted to have a collection for MasterPositions this would fit perfectly fine.
What's the best way to set default properties for new entities in DDD? Also, what's the best way to set default states for complex properties (eg. collections)?
My feeling is that default values should be in the models themselves as they are a form of business rule ("by default, we want X's to be Y & Z"), and the domain represents the business. With this approach, maybe a static "GetNew()" method on the model itself would work:
public class Person {
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public bool IsAlive { get; set; }
public List Limbs { get; set; }
public static Person GetNew() {
return new Person() {
IsAlive = true,
Limbs = new List() { RightArm, LeftArm, RightLeg, LeftLeg }
}
}
}
Unfortunately in our case, we need the collection property to be set to all members of another list, and as this model is decoupled from its Repository/DbContext it doesn't have any way of loading them all.
Crappy solution would be to pass as parameter :
public static Person GetNew(List<Limb> allLimbs) {
return new Person() {
IsAlive = true,
Limbs = allLimbs
}
}
Alternatively is there some better way of setting default values for simple & complex model properties?
This is an instance of the factory pattern in DDD. It can either be a dedicated class, such as PersonFactory, or a static method, as in your example. I prefer the static method because I see no need to create a whole new class.
As far as initializing the collection, the GetNew method with the collection as a parameter is something I would go with. It states an important constraint - to create a new person entity you need that collection. The collection instance would be provided by an application service hosting the specific use case where it is needed. More generally, default values could be stored in the database, in which case the application service would call out to a repository to obtain the required values.
Take a look at the Static Builder in Joshua Bloch's Effective Java (Second Edition). In there, you have a static builder class and you chain calls to set properties before construction so it solves the problem of either having a constructor that takes a ton of arguments or having to put setters on every property (in which case, you effectively have a Struct).
When creating POCO classes that contain collections of primitive types and are persisted by EF Code First, the best advice I have found so far is to create a new class that has an ID plus the primitive type:
Entity Framework and Models with Simple Arrays
If I now have several classes that require properties of type ObservableCollection<string> and replace them with ObservableCollection<EntityString> (where EntityString is a custom type with an Id and a string property), I end up with a table EntityString that has multiple foreign key columns, one for each property of type ObservableCollection<EntityString> across all concrete types with such properties.
This leads to a bloating of mostly-null foreign key columns in the EntityString table.
One approach would be to create a subclass of EntityString and use the Table per Type model for those subclasses. However, that requires making awkward changes to the object model simply to accommodate Entity Framework.
Questions:
Is the encapsulating type the best way to manage Collection<PrimitiveType>?
If so, what are the pro's and con's of allowing multiple (many) foreign key columns vs. creating custom tables per type (at the cost of an awkward model)?
Promoting simple type to entity is one option. If you want to use that new primitive type entity in more relations it is better to completely remove navigation properties from that entity and use independent association (no FK properties).
public class StringEntity
{
public int Id { get; set; }
public string Text { get; set; }
}
and mapping:
modelBuilder.Entity<Foo1>().HasMany(f => f.Strings).WithOptional();
modelBuilder.Entity<Foo2>().HasMany(f => f.Strings).WithOptional();
In database you will get new nullable FK per related principal - there is no way to avoid it except create special StringEntity class per principal (don't use inheritance for that because it affects performance).
There is an alternative:
public class StringEntity
{
public int Id { get; set; }
public List<string> Strings { get; private set; }
public string Text
{
get
{
return String.Join(";", Strings);
}
set
{
Strings = value.Split(";").ToList();
}
}
}
In this case you don't need related entity type (and additional table) but your entity is polluted with additional property Text which is only for persistence.