Breeze is trying to update a computed database column - entity-framework

A friend reported a problem with a computed column, Entity Framework, and Breeze
We have a table with a "FullName" column computed by the database. When creating a new Person, Breeze sends the FullName property value to the server, even though it’s not being set at all, and that triggers an error when trying to insert the new Person instance. The database throws this exception:
The column "FullName" cannot be modified because it is either a computed column or is the result of a UNION operator.
Here is the relevant portion of the SQL Table definition:
CREATE TABLE [dbo].[Person](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](100) NULL,
[MiddleName] [varchar](100) NULL,
[LastName] [varchar](100) NOT NULL,
[FullName] AS ((([Patient].[LastName]+',') + isnull(' '+[Patient].[FirstName],'')) + isnull(' '+[Patient].[MiddleName],'')),
...
My friend tells me the corresponding "Code First" class looks something like this:
public class Person {
public int ID {get; set;}
public string FirstName {get; set;}
public string MiddleName {get; set;}
public string LastName {get; set;}
public string FullName {get; set;}
...
}
The answer to this question explains the problem and offers a solution.

Design issues
Everyone looking at this wonders why there is a computed column for FullName and, secondarily, why this property is exposed to the client.
Let's just assume there is a good reason for the computed column, a good reason for the model to get the value from the table instead of calculating the value itself, and a good reason to send it to the client rather than have the client calculate it. Here's what he told me about that;
"We need to include the FullName in queries"
Life works out this way sometimes.
Consequences
Notice that the FullName property has a public setter. The EF metadata generator for the Person class cannot tell that this is a read-only property. FullName looks just like LastName. The metadata say "this is normal read/write property."
Breeze doesn't see a difference either. The client app may not touch this property, but Breeze has to send a value for it when creating a new Person. Back on the server, the Breeze EFContextProvider thinks it should pass that value along when creating the EF entity. The stage is set for disaster.
What can you do if (a) you can't change the table and (b) you can't change the model's FullName property definition?
A Solution
EF needs your help. You should tell EF that this is actually a database computed property. You could use the EF fluent interface or use the attribute as shown here:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public String FullName { get; set; }
Add this attribute and EF knows this property is read-only. It will generate the appropriate metadata and you can save a new Person cleanly. Omit it and you'll get the exception.
Note that this is only necessary for Code First. If he'd generated the model Database First, EF knows that the column is computed and doesn’t try to set it.
Be aware of a similar issue with store-generated keys. The default for an integer key is "store-generated" but the default for a Guid key is "client generated". If, in your table, the database actually sets the Guid, you must mark the ID property with [DatabaseGenerated(DatabaseGeneratedOption.Identity)]

Related

How does EF Core build its internal model?

Background:
We're doing an annoying merge due to a poor branching strategy. Note: all code is pseudocode
We updated an entity class to change the Submittal table. We removed the specific report Id and it's foreign key and replaced it with a generic 'ReportId' with no foreign key.
public class Submittal{
int Id {get;set}
string Status {get;set;}
int SpecificReportId {get;set}
}
There were some migrations that were applied that changed the db (db has the updated column and no FK)
The db context has no reference to the old column. The entity now looks like:
public class Submittal{
int Id {get;set}
string Status {get;set;}
int ReportId {get;set}
}
The modelBuilder in the context has no reference to the old column name.
The model snapshot with the migrations has no reference to the old column name.
The repository method for adding the submittal:
public async Task AddSubmittal(Submittal submittal){
using var context = new ReportContext(dbContextOptions);
context.Add(model);
context.SaveChangesAsync();
}
When debugging, the submittal object has ReportId and no SpecificReportId, but when it tries to run SaveChangesAsync() it fails with an exception of "SqlException: Invalid column name 'SpecificReportId'.".
The dbOptions connection string is pointing at the database I expect, and when I ran sql profiler I got the following sql for my query:
INSERT INTO [Schema].[Submittal] ([Status], [SpecificReportId], [ReportId])
VALUES (#p0, #p1, #p2);
SELECT [Id]
FROM [Schema].[Submittal]
WHERE ##ROWCOUNT = 1 AND [Id] = scope_identity();
What the heck is going on? It feels like I must have missed some piece of how the internal model is generated. I have searched the code for the original id, but did not see anything that looked related.
Notice that the query is trying to insert both ReportId and SpecificReportId. That means you have some lost (or inherited) public SpecificReport SpecificReport { get; set; } inside Submittal class (or opposite reference in SpecificReport class), and EF creates shadow foreign key for it. Full dump of what EF knows about your model is available in DbContextModelSnapshot.cs file, updated with each migration (try adding new migration and make sure it's generated as empty, before you rely on it).
Your pseudocode should work as posted, there must be something else in your project.
PS: you can also set Microsoft.EntityFrameworkCore.Database.Command log level to information, as another debugging tool.

Why collections of owned types generate an Id and how can I avoid that?

As Microsoft says here :
Owned entities are essentially a part of the owner and cannot exist without it, they are conceptually similar to aggregates
It means in DDD architecture (Domain Driven Design) we can use owned types (or collection of owned types ) as an entity inside an aggregation or as a value object. On the other hand, we know that ValueObject in DDD has no Identity value because of its structure and its Immutability. I want to know if I decide to use the Owned type to implement the value object how can I force it to avoid making Id in the creation table?
For example, as you see in the following picture (that Microsoft mentioned here) when we use a collection of owned types, EF makes an "Id" field in the table that no sense in Address Value Object! How to avoid it? and Is it really a correct option?
That example is from the OwnsMany scenario where it clearly explains that it needs a FK in the table to associate addresses back to their Distributor. How else would an Address record associate back to the Distriburor when loading the entities?
If a Distributor only has 0-1 address then you don't need an OwnerId on Address, the Address' Id column would serve as PK and FK back to the Distributor. EF needs a "Key" on each table to uniquely identify each row. You could possibly avoid an "Id" column by mapping a composite key, essentially:
public class Address
{
[Key, Column(0), ForeignKey("Owner")]
public int OwnerId { get; set; }
[Key, Column(1)]
public string Street { get; set; }
[Key, Column(2)]
public string City { get; set; }
public virtual Distributor Owner { get; set; }
}
A dedicated unique, and DB Generated Id column for the Address table IMO makes more sense than a large, composite key of strings and FK.
Ownership as far as the database is concerned is identical to HasOne / HasMany in the way the schema is laid out & relational rules. What differentiates OwnsMany from HasMany is how EF will allow you to access those owned entities. You cannot have a DbSet<Address>, only access Addresses through it's Distributor. It serves no real purpose except to scratch particular design pattern itches. :)

key by navigation property using annotation (entity framework)

I have a class as
public class fooClass
{
[Key]
public virtual fooRefClass staff { get; set; }
public Int64 fooProp1{ get; set; }
public DateTime fooProp2{ get; set; }
}
when i do the migration it give me error as "no key defined" but i had added the key attonation already .My intention is to make the referenced entity "fooRefClass " as primary key as well as foreign key, i know there is a way by mentioning the attribute id of the referenced entity and writing the foreign-key annotate over there, but i want to deal with entity directly ,rather than id only, how can i attain this functionality ,please help me with the issue.
Since there seems to be confusion, I decided to write an answer as well.
With the class you defined, EF would expect a table like
fooClass(bigint fooRefClass_Id, bigint fooProp1, datetime fooProp2);
... which is not valid, because this has no key column (the key annotation on your navigation property does nothing, because you see, it won't appear in the table... it will just tell EF there is a relationship to this table, and because you didn't provide a FK, it creates one to create this relationship). You also can't create this relationship yourself in your current model, because you don't even have a FK... how would you access a property you know nothing about, just that it will be created at some point (usually upon model creating with first accessing the database).
You have to tell EF you want the property, that will be created, to also be a key, not only a foreign key. You can do this by simply creating the property yourself and telling EF you want to use this, for example (I'm not too familiar with Data Annotations, I usually use Fluent API, so please excuse maybe occuring errors)
[Key, Foreign Key(fooRefClass)]
public Int64 StaffId {get; set;}

How to add Foreign Key Properties subsequently to a Code First Model?

Given the Model:
Public Class Customer
Property Id() As Guid
Property FirstName() As String
Property MiddleName() As String
Property LastName() As String
Property Addresses() As ICollection(Of Address)
End Class
Public Class Address
Property Id() As Guid
Property Name() As String
Property Street() As String
Property City() As String
Property Zip() As String
Public Property Customer() As Customer
End Class
Entity Framework 6 Code First has created a column called Customer_Id in my table Addresses. Now, I'd like to add a Property Customer_Id to my class Address that represents the existing foreign key relation:
Public Class Address
Property Id() As Guid
Property Name() As String
Property Street() As String
Property City() As String
Property Zip() As String
Public Property Customer() As Customer
//Added
Public Property Customer_Id() As Guid
End Class
Unfortunately this results in an InvalidOperationException while creating the DbContext saying:
The model backing the 'DataContext' context has changed since the database was created.
I tried different property names (with and without underscore, different casing). But still no luck. So, what is the correct way to add those properties subsequently without the need for migrations? I assume it's possible, because the model does not really change, I am only changing from an implicit declaration of a property to an explicit...
Update:
The responses show me, that I did not explain the problem very well. After some more reading I found the correct names now: I have an application which is installed several times at customer locations (therefore dropping and recreating the database is no option). Currently, it depends on Entity Framework's Independent Associations, but I want to have the Foreign Key in my entity as well (this is no change to the model, the foreign key is already there, but does not exist as a property in my entity, since this is currently only relying on the IA instead). I did not manage to add it without EF thinking my Database is outdated.
for me two ways :
drop table __MigrationHistory : that is have the new model runs, but forget migration functionalities
create a new db by changing the connection string of the application. Replace old __MigrationHistory by __MigrationHistory of the newly created db
Never tested the second solution, but it should work.
Before using any solution:
backup you db.
Before using first solution: are you sure you will never need migration functionalities ?
This exception is because you change your model. You have to set migration strategy. Please look at:
http://msdn.microsoft.com/en-us/data/jj591621#enabling
(edited)
First of all you have to remove that exception. Even if you didn't add any new column to your database your model has changed because you added new property to Address class. If you check your DB you will find dbo.__MigrationHistory table with Model column. Last (earliest) value from that column is used for checking that your model and DB are compatible. I'm not sure but I think that EF stores there binary serialized model. So the solution is - recreate DB or add migration (probably empty migration).
(edited)
When you want to set FK you can do this very simple by Data Annotations
// c# example
public class Address
{
...
public string CustomerId { get; set; }
[ForeignKey("CustomerId")]
public Customer Customer { get; set; }
}
or in fluent api
// c# example
modelBuilder.Entity<Address>()
.HasRequired(arg => arg.Customer)
.WithMany()
.HasForeignKey(arg => arg.CustomerId);
or look at:
http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx
http://msdn.microsoft.com/en-us/data/hh134698.aspx

How to insert ID in EF 4.1

I am trying something with EF 4.1 that should be relatively easy. I have a class like this
class Entity
{
public int Id {get;set;}
public string Name {get;set;}
}
The Id property should be the primary key in the DB and this all works. But when I insert new entities with given IDs EF4.1 ignores the ID and creates a new one. So I would like something like identity_insert?
Greeting,
Martijn
I'm tempted to closed as a duplicate of this question, but it's not clear from the text if you want to have a property that is always created by your code, or just sometimes (as it is in that question)