Why is Entity Framework ConcurrencyStamp not using ROWVERSION/TIMESTAMP? - entity-framework

I am new to EF and previously engineered custom ORMs that use TIMESTAMP fields for concurrency and also determining records for synchronization to other databases.
Why does EF (Core) use nvarchar(max) to store what looks like a Guid?
i.e. why does EF do work that the DB could be doing instead?
The obvious thing is at some point (maybe when scaling up to multiple servers/databases) we want to store multiple Guids in there, and/or maybe it is simply because ROWVERSION/TIMESTAMP is not consistently implemented on the DBs targeted by EF?
(on a similar note why is the ID field nvarchar(450)?)
UPDATE:
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
ConcurrencyStamp = table.Column<string>(nullable: true),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});

This seems like a questionable design decision of ASP.NET Core Identity, not a problem in Entity Framework Core. They use public virtual string ConcurrencyStamp { get; set; }, but for RowVersion/Timestamp columns, Entity Framework uses byte[] with an additional annotation or mapping to make sure EF understands the value should be re-read after updates. From one of EF's own test files:
public class Two
{
[Key]
public int Id { get; set; }
[StringLength(16)]
public string Data { get; set; }
[Timestamp]
public byte[] Timestamp { get; set; }
public virtual C NavC { get; set; }
}
If you use EF yourself, you should be able to use RowVersion/Timestamp columns without any issues.

Related

"The association has been severed but the relationship is either marked as 'Required' or is implicitly required..."

I am getting the following error when trying to add a migration:
PS C:\Code\morpher.ru\Morpher.Database> dotnet ef migrations add AddQazaqFeatures --startup-project=../Morpher.Database.Design
Build started...
Build succeeded.
System.InvalidOperationException: The association between entity types 'Service' and 'Deployment' has been severed but the relationship is either m
arked as 'Required' or is implicitly required because the foreign key is not nullable. If the dependent/child entity should be deleted when a requi
red relationship is severed, then setup the relationship to use cascade deletes. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLoggin
g' to see the key values.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.HandleConceptualNulls(Boolean sensitiveLoggingEnabled, Boolean forc
e, Boolean isCascadeDelete)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.CascadeDelete(InternalEntityEntry entry, Boolean force, IEnumerable`1 fore
ignKeys)
...
My code:
public class Deployment
{
public int Id { get; set; }
public virtual Service Service { get; set; }
public int ServiceId { get; set; }
public string Host { get; set; }
public short? Port { get; set; }
public string BasePath { get; set; }
}
public class Service
{
public int Id { get; set; }
public string Name { get; set; }
public string UrlSlug { get; set; }
public virtual ICollection<Endpoint> Endpoints { get; set; }
public virtual ICollection<Deployment> Deployments { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Service>().HasData(new Service
{
Name = "Веб-сервис «Морфер»",
UrlSlug = "ws",
Id = 1
});
modelBuilder.Entity<Deployment>().HasData(new Deployment
{
Host = "ws3.morpher.ru",
ServiceId = 1,
Id = 1
});
modelBuilder.Entity<Deployment>(entity =>
{
entity.Property(e => e.Host).IsRequired().HasMaxLength(256);
entity.Property(e => e.BasePath).HasMaxLength(512);
entity.HasOne(deployment => deployment.Service)
.WithMany(service => service.Deployments)
.HasForeignKey(d => d.ServiceId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Deployments_Services");
});
}
There are numerous StackOverflow questions mentioning the same error (1, 2, 3), but they are mostly to do with removing entities while not having a CASCADE delete policy or a nullable foreign key.
In my case, I am trying to add new rows and I don't see why it is considering the relationship 'severed'. Is setting ServiceId = 1 not enough?
I was able to reproduce the issue in latest at this time EF Core 3.1 version (3.1.28) by first removing the model data seeding code (HasData calls), then adding migration for just creating the tables/relationships, then adding the data seeding code and attempting to add new migration.
It does not happen in latest EF Core 6.0, so apparently you are hitting EF Core 3.1 defect/bug which has been fixed somewhere down on the road. So you either need to upgrade to a newer EF Core version (with all associated burdens like retesting everything, breaking changes etc.), or use the workaround below.
The workaround is to replace the DeleteBehavior.Restrict with either ClientNoAction or NoAction. Values of that enum and documentation of what they do is kind of messy, but all these 3 values seem to generate one and the same regular enforced FK constraint (with no cascade) in the database, and differ only by client side behavior, or in other words, what does EF Core change tracker do with related tracked entities when "deleting" a principal entity. And in this particular case, `Restrict" throws exception when there are tracked (loaded) related entity instances, while the other two won't.
I know you think you are just "adding data", but EF Core model data seeding is more than that - it tries to keep that data, so in some circumstances it needs to update or delete previously added data. Which in general works, except when there are bugs in the EF Core codebase, like in this case.

Updating related entity in EF Core

I am using Entity Framework Core with a code-first approach. I hit this error when saving the changes to an entity which has a change to one of its related objects:
"The property 'BirdId' on entity type 'Bird' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal."
I hit the error when I save the changes to an entity of type Observation with a updated to the related Bird object. I have included the models setup below:
public class Observation
{
[Key]
public int ObservationId { get; set; }
// other properties...
public int BirdId { get; set; }
public Bird Bird { get; set; }
}
The Bird class looks like this:
public class Bird
{
[Key]
public int BirdId { get; set; }
// other properties...
public ICollection<Observation> Observations { get; set; }
}
I have relied solely on the EF Core model conventions (I've not added code in the OnModelCreating method) which, from the EF migration, sets up the database like so:
migrationBuilder.CreateTable(
name: "Observation",
columns: table => new
{
ObservationId = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BirdId = table.Column<int>(nullable: false),
},
constraints: table =>
{
table.PrimaryKey("PK_Observation", x => x.ObservationId);
table.ForeignKey(
name: "FK_Observation_Bird_BirdId",
column: x => x.BirdId,
principalTable: "Bird",
principalColumn: "BirdId",
onDelete: ReferentialAction.Cascade);
});
Have I setup the model correctly?
My code for saving the updated Observation looks like this:
observation.BirdId = model.Bird.BirdId;
var bird = await _birdRepository.GetBirdAsync(model.Bird.BirdId);
observation.Bird = bird;
observation.LastUpdateDate = _systemClock.GetNow;
await _unitOfWork.CompleteAsync();
However, I think the problem lies in the way the relationship is setup in the models. Can anyone shed any light on the problem?
observation.Bird is linked to observation.BirdId by convention. You're not supposed to change both and it is generally recommended to only change the navigation property.
I'm going to assume that model is a view model and so you can't use its Bird directly, but basically you can leave out the first line of code and simply set observation.Bird.
EF gets confused in this case because you are trying to change the relation twice.

EF migration with new table collation error

I'm using Entity Framework code first, It has been working ok updating the database with db migrations up until recently...
I've added a anew property to the AspNetUser table
public partial class AspNetUser
{
....
public ICollection<Feed> Feeds { get; set; }
}
This is my new table
public class Feed
{
public int Id { get; set; }
public string UserId { get; set; }
public AspNetUser User { get; set; }
public MessageType Type { get; set; }
public string Data { get; set; }
public DateTime DateCreated { get; set; }
}
And this the is DBMigration script generated
public override void Up()
{
CreateTable(
"dbo.Feeds",
c => new
{
Id = c.Int(nullable: false, identity: true),
UserId = c.String(nullable: false, maxLength: 128),
Type = c.Int(nullable: false),
Data = c.String(),
DateCreated = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true)
.Index(t => t.UserId);
}
In the Context class:
modelBuilder.Entity<Feed>().HasRequired(x => x.User).WithMany(x => x.Feeds).HasForeignKey(x => x.UserId);
This created the table fine, on localhost, but when i deployed and run the migration on staging, the error i get is:
Column 'dbo.AspNetUsers.Id' is not of same collation as referencing column 'Feeds.UserId' in foreign key 'FK_dbo.Feeds_dbo.AspNetUsers_UserId'.
Could not create constraint or index. See previous errors.
What must I do... I've gone with the code first approach, thinking this would be easier, but this is really frustrating.
Note: I'm using sql Azure
The collation of both columns 'dbo.AspNetUsers.Id' and 'Feeds.UserId' should be the same, To make them the same you can modify the collation of one of those columns using below sample code:
context.Database.SqlCommand("ALTER TABLE MyTable ALTER COLUMN MyColumn VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CS_AS NULL");
Hope this helps.
Regards,
Alberto Morillo
This Is how I got round my problem, but is by no means a solution I would have wanted.
Export the Backpac of the db from azure (via the azure portal)
Import the backpac into SSMS (right click databases > import.. follow wizard)
Change the collation here, by right clicking the db > properties > options> collation dropdown.
export this backpac using SSMS
import the backpac (from step 4) onto azure server (I used SSMS to do this)
Point the connection strings to the new db.
The data comes through too, so no loss there.
I've tried a few db migrations and they seem to work too.
With EF Core 5 (and possibly classic EF6) you can declare the FK field yourself.
.Property("UserId").UseCollation("SQL_Latin1_General_CP1_CS_AS");
Keep your .WithMany() invocation as-is.

EF 6 Code-First TPH Inheritance Results in Migrations with Duplicate Columns (sometimes in triplicate)

I have an MVC5 application using EF 6.1
I am using table-per-hierarchy (TPH) inheritance for several entities and in every case I am incurring the same problem...that the migration generates duplicate column definitions within a single table even though the column is only declared in ONE of the derived classes.
CreateTable(
"dbo.ContactInfos",
c => new
{
ContactInfoId = c.Guid(nullable: false),
sContactInfoSubTypeId = c.Guid(nullable: false),
Value = c.String(nullable: false, maxLength: 150),
IsDefault = c.Boolean(nullable: false),
Deleted = c.Boolean(nullable: false),
BuildingId = c.Guid(), <-- Contained in ContactInfoBuilding
CompanyId = c.Guid(), <-- Contained in ContactInfoCompany
OccupancyId = c.Guid(), <-- Contained in ContactInfoOccupancy
PersonId = c.Guid(), <-- Contained in ContactInfoPerson
BuildingId1 = c.Guid(), <-- Why is this duplicated??
CompanyId1 = c.Guid(), <-- Why is this duplicated??
OccupancyId1 = c.Guid(), <-- Why is this duplicated??
Discriminator = c.String(nullable: false, maxLength: 128),
Occupant_OccupantId = c.Guid(), <-- Why is this triplicated??
})
My Base Class:
public abstract class ContactInfo
{
[Column("ContactInfoId")]
[Key]
public Guid ContactInfoId { get; set; }
[Column("sContactInfoSubTypeId")]
public Guid sContactInfoSubTypeId { get; set; }
[Column("Value")]
[MaxLength(150, ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "MaxLengthExceeded")]
public string Value{ get; set; }
[Column("IsDefault")]
public Boolean IsDefault { get; set; }
public virtual sContactInfoSubType sContactInfoSubType { get; set; }
}
My derived class:
public class ContactInfoOccupancy: ContactInfo
{
[Column("OccupancyId")]
public Guid OccupancyId { get; set; }
[ForeignKey("OccupancyId")]
public virtual Occupant Occupant { get; set; }
}
My DBContext Mapping Code:
modelBuilder.Entity<ContactInfo>().HasRequired(t => t.sContactInfoSubType).WithMany(t => t.ContactInfos).HasForeignKey(d => d.sContactInfoSubTypeId).WillCascadeOnDelete(false);
modelBuilder.Entity<ContactInfo>().Property(t => t.ContactInfoId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<ContactInfoOccupancy>().HasRequired(t => t.Occupant).WithMany(t => t.ContactInfoOccupancies).HasForeignKey(d => d.OccupancyId).WillCascadeOnDelete(false);
Finally, in my Occupant object, I have the ContactInfoOccupancies declared in the constructor and as a navigation property:
public partial class Occupant
{
public Occupant()
{
this.ContactInfoOccupancies = new List<ContactInfoOccupancy>();
}
...More stuff here that is unrelated...
public virtual ICollection<ContactInfoOccupancy> ContactInfoOccupancies { get; set; }
So, in summary: I declared my base class as abstract. I have the FK (OccupancyId for example) declared in only one derived class, and I have the mapping explicitly stated. I also have only the base-class declared as a DBSet in the DBContext. However, EF Code-First Migrations seems to be trying to add the fields twice or even three times (once as it should, a second time with the number one appended and a third time with an underscore between the table-name and the column-name). Any ideas how to stop this? Commenting out the erroneous lines from the migration doesn't work because the "snapshot" still thinks they exist and any insert/update attempts then fail because the "phantom columns" are still trying to be populated by calls to Context.SaveChanges
I also blew away my database and started a new migration from scratch, resulting in the above-listed migration...so this isn't an artifact resulting from an existing database having an inheritance schema pushed onto it.
Thanks in advance for any help...it will be greatly appreciated.
OK, the problem was that we were using ViewModels with MVC and the Scaffolder made the ViewModels inherit from the abstract, base-class Model of ContactInfo instead of the ContactInfoViewModel. We heavily modified the basic scaffolder to do this, so this was really our code that had a bug, not Microsoft's.
Since ViewModels are supposed to be ignorant of the database, we didn't have any mapping entries in the DBContext for the ViewModels, but the Migration-generator was seeing the relationships based on naming conventions (fields with the table-name followed by Id) within the ViewModels and artificially creating additional relationships within the DBContext due to the VMs inheriting from the base-class Model which was in the Context.
Changing the VMs to inherit from the ContactInfoViewModel removed these "implicit relationships" and all is good.

Mapping properties to (differently named) foreign key fields in Entity Framework CTP5

I'm trying to use the Entity Framework CTP5 Fluent API to map an exist database. I have the following classes:
public class Shop
{
public long Id
{
get;
set;
}
}
public class Sale
{
public long Id
{
get;
set;
}
public virtual Shop Shop
{
get;
set;
}
}
The corresponding tables are called "Stores" and "Sales". Sales has a StoreId foreign key that points to the Id field in the Stores table.
I'm struggling to map the Sale.Shop.Id to the StoreId in the table. I'm not at liberty to change it to ShopId, so need to map it.
In CTP4, I was using:
modelBuilder.Entity<Sale>().MapSingleType(x =>
new
{
Id = x.Id,
StoreId = x.Shop.Id
});
I tried the following:
modelBuilder.Entity<Sale>().Property(x => x.Shop.Id).HasColumnName("StoreId");
However, it seems this only works with a primitive type.
How do I specify this mapping?
Update: I've added a revised version for the Release Candidate of EF 4.1 below
After some hunting, I've found the answer that works for me:
EF4.1 RC version:
modelBuilder.Entity<Booking>().HasRequired(b => b.Booker)
.WithMany(m => m.BookedSlots).Map(p=>{
p.MapKey("BookerID");
});
in your case:
modelBuilder.Entity<Sale>().HasRequired(sale => sale.Shop)
.WithMany().Map(s=> {
s.MapKey("StoreId");
});
My version is slightly different because I have navigation properties on both sides of the relationship.
I think the best way to solve this would be to upgrade your independent Association to be a Foreign Key Association meaning that instead of hiding the foreign key ShopId, actually including it in Sale class. Then you can use Data Aannotations/Fluent API to change its column name to match to your existing schema:
public class Shop
{
public long Id { get;set; }
}
public class Sale
{
public long Id { get; set; }
[Column(Name="StoreID")]
public long ShopId { get; set; }
public virtual Shop Shop { get; set; }
}
Which results to the desired DB Schema:
I think what you're looking for is the RelatedTo attribute. More information in this ADO.NET team blog post.