Table Splitting / Optional Relationship - entity-framework

Currently, I have the following in my code:
modelBuilder.Entity<Client>().HasRequired(e => e.Logo).WithRequiredPrincipal();
This relationship definition is used for table splitting the Logo column (which is VARBINARY(MAX)) into a separate entity. Everything works as expected.
I have chosen to make the Logo column nullable in the database. I tried updating the code listed above to:
modelBuilder.Entity<Client>().HasOptional(e => e.Logo).WithOptionalPrincipal();
When I run the code, I receive the following message:
Additional information: The entity types 'ClientLogo' and 'Client' cannot share table 'clients' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
The problem is that I am not entirely sure what this message is trying to tell me. Why does it work when the Logo column is non-nullable but does not work when it is not? Am I mapping the relationship incorrectly?

Even if the Logo column is optional, the entity relationship between ClientLogo and Client needs to remain the same, it's the property itself that needs to be nullable:
// This should stay the same
modelBuilder.Entity<Client>().HasRequired(e => e.Logo).WithRequiredPrincipal();
// If you used to have a line like this or a [Required] attribute, then it needs to be removed
// modelBuilder.Entity<ClientLogo>().Property(t => t.Logo).IsRequired();

Related

How to model Database where a table entry can refer to another entry in the same table

I've a web app, being used by engineers for Asset(machines, scales) calibrations on a Site & certificate can be generated based on those readings. Up till now, the requirement was so that for a given SITE includes multiple ASSET and each asset has its own CALIBRATION.
So had my Modal like this.
With a recent change to include a new type of certificate. Where an ASSET can have multiple calibration (Two to be exact) one before Adjustment and One after if needed.
My question is, what is the best way to accommodate this change? Should I change the relation between ASSET one-to-one CALIBRATION to one-to-many with multiplicity (1..2) which basically requires to change lot of code check. or should adding another column in ASSET table which points to another entry within the same table. Or is there any other approach to opt ?
I'm using ASP.MVC, with Entity Framework.
Mapping
public Report_AssetMap()
{
HasKey(one => one.report_asset_id);
// Site_Report one-to-many-rel Report_Asset
HasRequired(one => one.Site_Report).WithMany(one => one.Report_Assets).HasForeignKey(one => one.site_report_id);
// Report_Asset one-to-one-rel Asset_Calcert
HasOptional(one => one.Asset_Calcert).WithRequired(ad => ad.Report_Asset).WillCascadeOnDelete(true);
}
public Asset_CalcertMap()
{
HasKey(one => one.report_asset_id);
// User one-to-many Asset_Calcert (with nullable Calcert_handled_by_id at many End)
HasOptional(o => o.Calcert_Handled_By).WithMany(r => r.Handled_Calcert).HasForeignKey(o => o.calcert_handled_by_id);
}
I would have a separate table for each of the pre and post adjustment certs; this is a solid use case for table-per-concrete-class inheritance (which is not yet included in EF Core but I'm guessing you're using EF6). Put common properties in a base class, derive a class for your pre and post adjustment certs (can be empty if no differentiation other than table names), then MapInheritedProperties and specify different table names for the derived classes in the model configuration.
https://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-3-table-per-concrete-type-tpc-and-choosing-strategy-guidelines
If you then want to query against all Asset_CalCerts, you can specify a single DbSet<Asset_Caclert> DbSet in the DbContext to query while your Report_Asset entity can have a property referencing each derived Asset_CalCert type.
In this way you can keep your 1:? relationship while maintaining the ability to query all Asset_CalCerts as though they were in a single table.

Rails 5.1.2 - Single Table Inheritance: No migration is getting generated

I am trying to generate scaffolding for STI implementation. I issue the following.
rails g scaffold user1 type name email
rails g scaffold member company subscription --parent user1
Every thing gets generated file except for the migration file my 'member' model.
When I try to create a member record like this
Member.create(name: "My Name", email: "myname#example.com",
company: 'Example LLC', subscription: 'Monthly Gold' )
I get this error:
ActiveModel::UnknownAttributeError: unknown attribute 'company' for Member. from (irb):1
Any ideas on what is going on?
I use rails 5 and db is postgres
The --parent option assumes that you are already all setup for single table inheritance, i.e. the parent class has a table with a type column (or whatever column you are using for this).
Since the model will be stored in the parent's table, there is no need to create a new table for the subclass, hence no migration
I got this answer similar to this question asked by someone.
To my understanding, you are on the wrong track. In single table inheritance, all the attributes must be present in the parent model table with an additional column name 'type' to indicate the type of inherited model. The column name 'type' can be changed with appropriate settings but ActiveRecord by default looks for 'type' column. You are getting 'UnknownAttributeError' error cause the parent model does not have the following column in its table. You need to write a migration to add the new columns. Hope you understand the concept of STI. For further exploration, I am providing you the link of the official guide. Hope your problem will be solved.
http://edgeguides.rubyonrails.org/association_basics.html#single-table-inheritance

Entity Framework object graph deletion with Breeze

I am encountering a recurring problem that just makes no sense, and hoping someone (in the Breeze team?) can shed some light.
The following model illustrates the entities in question.
As you can see, I'm adhering pretty strictly to Entity Framework conventions in my property names, and as a result, if I check in SQL the cascade on delete rules are set by EF code first when it creates the db.
Now, when I try to delete a BusUnit manually in SQL, the delete cascades correctly and the corresponding BusUnitDimensions are also deleted, as it should be. Likewise, if I delete a Dimension in SQL, the corresponding BusUnitDimensions are also deleted.
However, in my application, if I mark a BusUnit as setDeleted with Breeze and then try saveChanges, I get the following error.
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
Strangely though, if I mark a Dimension for deletion and then save (within Breeze), the cascaded delete works correctly and both the Dimension and its corresponding BusUnitDimensions are deleted.
So, why the inconsistency? Why are the cascaded delete rules in SQL not being applied for BusUnits but yet they're working for Dimensions? I've read elsewhere that Breeze does not support cascaded deletes, but then why is my Dimensions case working?
EDIT:
I've removed my previous edits as they weren't relevant. The changes below follow on from Ward's answer...
My model now looks like this, and BusUnitDims now uses BusUnitId and DimId as a compound key, and I've added a bool, IsBud for the purposes of payload.
I haven't yet implemented deletes for BusUnits, but already if I try delete a Dim, I'm getting the same error message:
The operation failed: The relationship could not be changed because one
or more of the foreign-key properties is non-nullable. When a change is
made to a relationship, the related foreign-key property is set to a null
value. If the foreign-key does not support null values, a new relationship
must be defined, the foreign-key property must be assigned another
non-null value, or the unrelated object must be deleted.
I have noticed that cascaded deletes is no longer enabled, and in fact, to get EF to build the database I to add the following configuration:
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.BusUnit)
.WithMany(bu => bu.BusUnitDims)
.HasForeignKey(bud => bud.BusUnitId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<BusUnitDim>()
.HasRequired(bud => bud.Dim)
.WithMany(d => d.BusUnitDims)
.HasForeignKey(bud => bud.DimId)
.WillCascadeOnDelete(false);
So, with cascading now explicitly not in place, I can understand why the error occurs. Does that imply that in the controller, one has to specifically mark each map for deletion when deleting a parent Dim or BusUnit and before saveChanges is called, or is there some way to configure EF to take advantage of cascaded deletes as this would hugely simplify the code in my controller?
(PS: it gets even more complex, because BusUnitDims ends up having a further join table of its own, MetricBusUnitDims to accommodate yet another entity in the model and their relationships. This is why I'm trying to get the principles right early on)
EDIT: (A CONTROLLER SOLUTION FOR BUSUNITS)
So, the following approach works for BusUnits:
function deleteBusUnit(busUnitVm) { // note that you pass in the item viewmodel, not the entity
var busUnit = busUnitVm.busUnit;
var mapVms = busUnitVm.dimMapVms;
var dimHash = createBusUnitDimHash(busUnit);
mapVms.forEach(function (mapVm) {
var map = dimHash[mapVm.dim.id];
if (map) {
datacontext.markDeleted(map);
}
});
datacontext.markDeleted(busUnit);
save().then(function() { getDBoardConfig(); });
}
}
Is this the correct approach? if so, I'll still have to figure out the following:
How to approach Dims. These are different becuase the item viewmodel is defined for BusUnits.
How to approach the situation where there is a join tabel one level down, e.g. MetricBusUnitDIm.
EDIT: (A CONTROLLER SOLUTION FOR DIMS)
function deleteDim(dim) {
return bsDialog.deleteDialog(dim.name, true)
.then(function () {
vm.busUnitVms.forEach(function (busUnitVm) {
busUnitVm.busUnit.busUnitDims.forEach(function (bud) {
if (bud.dimId === dim.id) {
datacontext.markDeleted(bud);
}
});
});
datacontext.markDeleted(dim);
save().then(function () { getDboardConfig(); });
});
}
I believe your problems are traceable to the fact that your mapping table BusUnitDimension has its own primary key, Id, as opposed to the more typical approach in which the BusUnitId and DimensionId FK properties together comprise the compound primary key of BusUnitDimension.
Observe that OrderDetails in Northwind and the HeroPoweMap in the Breeze many-to-many example have compound keys.
Your choice creates complications.
First, it becomes possible to create multiple BusUnitDimension entities representing the same association between BusUnit and Dimension (i.e., they all have the same pair of FKs). The database may be able to prevent this (it's been a long time since I looked) but whether it does or doesn't, it won't prevent you from creating those duplicates in Breeze ... and maybe not in EF either.
Secondly, it opens you up to the problem you're currently facing. If those mapping entities are in the DbContext when you perform the delete, EF may (apparently does) try to null their FK properties as it sets either BusUnit or Dimension to the deleted state.
You can get around this, as has been suggested, by making both the BusUnitId and DimensionId FK properties nullable. But that is contrary to the semantics as a BusUnitDimension must link a real BusUnit to a real Dimension; they aren't optional. The practical consequence may be that you don't get cascade delete from the EF perspective if you do this (not sure if the DB will enforce that either). That means you'd have orphaned BusUnitDimension rows in your database with one or both FKs being null. I speculate because I'm not used to getting into this kind of trouble.
Another approach would be to set their FK values to zero (I think Breeze does this for you). Of course this implies the existence of BusUnit and Dimension table rows with Id == 0, if only during the delete operation.
Btw, you could actually have such "sentinel entities" in your DB.
You must make sure that these BusUnitDimension are in the deleted state or EF (and the DB) will either reject them (referential integrity constraint) or orphan them (you'll have BusUnitDimension rows in your database with one or both FKs being zero).
Alternatively, if you know that the DB will cascade delete them, you can simply remove them from the DbContext (remove from the EntityInfoMap in the EFContextProvider). But now you have to tell the Breeze client to get rid of them too if it happens to have them hanging around.
Enough Already!
These wandering thoughts should tell you that you've got yourself in a jam here with way too much bookkeeping ... and all because you gave BusUnitDimension its own Id primary key.
It gets a lot easier if you give BusUnitDimension the compound key, {BusUnitId, DimensionId}. You must also give it a payload property (anything will do) to prevent EF from hiding it in its "many-to-many" implementation because Breeze doesn't handle that. Adding any nonsense property will do the trick.
HTH
That has nothing to do with Breeze.. The originating message is from Entity Framework..
inside BusUnitDimension Model update BusUnitId property to:
public Nullable<int> BusUnitId { get; set; }
Notice the Nullable struct..

Entity Framework "Invalid Column Name"

We have converted an existing DB to use EF, so we write classes to map to our tables. I've added a new column, a nullable int called 'iframeVideoId'. I mapped it with
public int? iframeVideoId { get; set; }
and when I do a search of that table, it blows up with an '
invalid column name' error
. This is a simple property it's not an index or related to any other table. I tried renaming it in the DB to 'DisplayVideo', and also making it not nullable. Then I had
[Column("DisplayVideo")]
public int iframeVideoId { get; set; }
Now I get an error that DisplayVideo is not a valid column. Of course I have double checked that I am pointing to the right DB, and confirmed that every DB that my code COULD point to, has this column. I've also done a clean and rebuild all, and rebooted my machine. What could be wrong ? This is a basic column, and it's there. I know from past experience that if the types did not match ( they are both int ), I'd get an error related to failed conversion between types. This makes no sense to me at all. I do not have an edmx file to refresh, because we're writing classes to map to the DB directly
Just to add, I changed the column to a string and it makes no difference. This is a new column, with a simple value, and EF claims it's not there.
And, I added the column to another table ( suboptimal, obviously ) and it just plain worked, immediately. I'd still like to know why I have a table I can't add columns to in EF, though
I had an error in my mapping class from copy/paste situation. As you can see, mapped TranferredToEVP to 3 different columns. I was getting an "invalid column name" on the last column TranferredToGUID but was looking for TranferredToGUID1. Hope this helps.
this.Property(t => t.TransferredToEVP).HasColumnName("TransferredToEVP");
this.Property(t => t.TransferredToEVP).HasColumnName("TransferredToName");
this.Property(t => t.TransferredToEVP).HasColumnName("TransferredToGUID");
I had the same issue like Amy Jonas had, if you happen to come across this, make sure the mapping is correct.
My code looked like this before :
Property(d => d.LastUpdatedOn).HasColumnName("LastUpdatedOn");
Property(d => d.LastUpdatedOn).HasColumnName("LastUpdatedBy");
It should be :
Property(d => d.LastUpdatedOn).HasColumnName("LastUpdatedOn");
Property(d => d.LastUpdatedBy).HasColumnName("LastUpdatedBy");
Sometimes we get too busy that we forget to look at these details.

Entity Framework Code first mapping without foreign key

I have two tables:
Requirement
ID (int) PK
ClientID (int)
JobNumber (int)
Comment
ID (int) PK
Job_ID (int)
Comment (varchar)
The tables don't have foreign keys and there's no possibility of adding any. I'm trying to map them in EF. I have classes for each and I'm trying to define the relationship in fluent code to map the Comment.Job_ID to the Requirement.JobNumber. A requirement can have many comments. Requirement has a list of Comments and Comment has a Requirement property.
I have this mapping setup:
modelBuilder.Entity<Comment>().HasRequired(c => c.Requirement)
.WithMany(s => s.Comments)
.HasForeignKey(f => f.Job_ID);
I'm stuck trying to get Comment.Job_ID to map to Requirement.JobNumber.
Any help appreciated.
It's not possible. With Entity Framework the entity that the Comment.Requirement navigation property is refering to is generally identified by the (primary) key property in Requirement, i.e. by ID. There is no mapping option to define that the target property is anything else than the key property - like JobNumber or another non-key property.
I could only imagine that you could "fake" the primary key property in the model to be JobNumber instead of ID (given that JobNumber is unique in the Requirement table):
modelBuilder.Entity<Requirement>().HasKey(r => r.JobNumber);
I don't know if that could have other unwished side effects. (For sure it doesn't work if JobNumber is not unique because EF wouldn't allow to have more than one entity with the same key attached to a context and updates/deletes and so on wouldn't find the correct record in the database.) It feels wrong and hacky to me. I honestly wouldn't even try that, live with the fact that you don't have a real foreign key relationship in the database, forget the navigation properties Requirement.Comments and Comment.Requirement and use manual joins in LINQ to relate the table data/entities as I need them in a given situation.