We are using EF6 database first with AspNet Identity. AspNetUsers is our table of customers. It extends AspNet IdentityUser. Each customer has many devices. The abbreviated table structures are
CREATE TABLE [dbo].[AspNetUsers] (
[Id] NVARCHAR (128) NOT NULL
);
CREATE TABLE [dbo].[Devices] (
[DeviceID] INT IDENTITY (1, 1) NOT NULL,
[UserId] NVARCHAR (128) NOT NULL
);
ALTER TABLE [dbo].[Devices]
ADD CONSTRAINT [FK_Devices_AspNetUsers] FOREIGN KEY ([UserId]) REFERENCES [dbo].[AspNetUsers] ([Id]);
CustomersContext OnModelCreatingspecifies the relationship between AspNetUsers and Devices.
modelBuilder.Entity<ApplicationUser>()
.HasMany(e => e.Devices)
.WithRequired(e => e.User)
.HasForeignKey(e => e.UserId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Device>()
.HasKey(e => e.DeviceId);
modelBuilder.Entity<Device>().Property(e => e.UserId)
.IsRequired()
.HasMaxLength(450);
The following code
var userDevices = user.Devices.ToList();
foreach (Device device in userDevices)
{
user.Devices.Remove(device);
}
await customersContext.SaveChangesAsync();
fails with the error
Cannot insert the value NULL into column 'UserId', table 'Customers.dbo.Devices'
That appears to be trying to remove the relationship without deleting the device. How do I delete a device using EF?
You want to delete the device. It is not possible to just remove it from the Devices list, as it cannot exist stand-alone by itself. It has a non-nullable foreign key (from column UserId) referencing AspNetUsers(Id) - deletion would break referential integrity.
To delete the device, add following statement inside foreach loop to successfully delete the devices associated with a user.
customersContext.Entry(device).State = EntityState.Deleted;
Another way would be to place following code inside foreach loop to delete the device directly from the device repository itself, instead of going through Devices navigation property available on ApplicationUser entity:
customersContext.Devices.Remove(device);
If you don't want to delete the records from the Devices table and just want to remove the relationship between AspNetUsers and all the Devices in one go, then you can assign null to the navigation property and save the changes. This will remove the relation between the entities without physically deleting the records from database.
user.Devices = null;
Related
I found related question but my issue seems to be different.
Running the following code:
var dbitem = context.MyDatabaseItems.Single(p => p.Id == someId);
context.Update(dbitem);
context.SaveChanges();
Results in "Cannot update identity column 'Id'". Table behind is a bit special. "Id" is NOT the primary key for different reasons. Primary key consists of combination of other fields. No matter what I do: detaching, reattaching etc etc the existing item I am unable to save the entity even if I do not change it (see the code).
However this Id is unique and auto generated.
The builder is the following:
builder.Property(p => p.Id)
.ValueGeneratedOnAdd();
builder.HasKey(p => new { p.BusinessDay, p.ClientId, p.Version });
BusinessDay is dateTime, CLientId and Version are integers.
What is going on here?
There are two metadata properties which control the update behavior called BeforeSaveBehavior and AfterSaveBehavior.
For auto generated keys the later is assumed to be Ignore, i.e. never update. For non key auto generated properties it must be configured explicitly (note that there is no fluent API for that so far, so you have to use the metadata API directly), e.g.
// First define the new key
builder.HasKey(p => new { p.BusinessDay, p.ClientId, p.Version });
// Then configure the auto generated column
// This (especially the `SetAfterUpdateBehavior` call) must be after
// unassociating the property as a PK, otherwise you'll get an exception
builder.Property(p => p.Id)
.ValueGeneratedOnAdd()
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); // <--
This does not change the database schema (model), hence no migration is needed. Just the EF Core update entity behavior.
I have got a database first approach with EF5 and here is a fragment of mappings:
internal class xxxMapping : EntityTypeConfiguration<Order>
{
public xxxMapping ()
{
ToTable("my_table");
//......
HasMany(it => it.Documents)
.WithMany()
.Map(
m =>
{
m.ToTable("dependent_table");
m.MapLeftKey("left_key_id");
m.MapRightKey("right_key_id");
});
}
What is the best way to declare, using fluent API, that when some row is deleted from my_table, then dependent rows from dependent_table will be deleted too (Cascade delete option in FK)
UPD It seems to be working without any additional code (Of course - if foreign key in table is configured properly). But i'm not sure it's a good practice to do so
In my application users can define Parameters, and then create SlideSets based on a grouping of parameters.
I am using code-first Entity Framework 5.0 and I have the following model:
class SlideSet {
public ICollection<Parameter> Parameter
}
class Parameter {}
A parameter might be used by many slidesets or none at all. However, in my domain a parameter has no need to reference a SlideSet, they are in separate bounded contexts (both SlideSet and Parameter are Aggregate Roots). As such, I don't want to put a reference from Parameter to SlideSet.
The table model (I don't care about table/column names) that I want is
Table SlideSet
Table Param
Table SlideSetParam
FK_SlideSet
FK_Param
I know I could model this by introducing a ParameterGroup entity or a Param.SlideSets collection, but it would exist solely for ORM mapping purposes (and cause serialization issues). Is there any other way to tell EF to generate this table model from my entities?
This should make you a Parameter w/o a navigation property:
modelBuilder.Entity<SlideSet>()
.HasMany(x => x.Parameters)
.WithRequired();
EDIT:
Based on the comment - that should be all together similar. This seems to work nicely what you're trying to do....
modelBuilder.Entity<SlideSet>()
.HasMany(x => x.Parameters)
.WithMany();
...and you can use it either way:
var slideset = new SlideSet { Parameters = new []
{
new Parameter{},
new Parameter{},
new Parameter{},
new Parameter{},
}
};
var slideset2 = new SlideSet { };
db.SlideSets.Add(slideset);
db.SaveChanges();
var slidesets = db.SlideSets.ToList();
var parameters = db.Parameters.ToList();
Console.WriteLine("");
db.SlideSets.Add(slideset2);
db.SaveChanges();
slidesets = db.SlideSets.ToList();
parameters = db.Parameters.ToList();
Console.WriteLine("");
...and the SQL:
CREATE TABLE [dbo].[Parameters] (
[ParameterID] [int] NOT NULL IDENTITY,
CONSTRAINT [PK_dbo.Parameters] PRIMARY KEY ([ParameterID])
)
CREATE TABLE [dbo].[SlideSets] (
[SlideSetID] [int] NOT NULL IDENTITY,
CONSTRAINT [PK_dbo.SlideSets] PRIMARY KEY ([SlideSetID])
)
CREATE TABLE [dbo].[SlideSetParameters] (
[SlideSet_SlideSetID] [int] NOT NULL,
[Parameter_ParameterID] [int] NOT NULL,
CONSTRAINT [PK_dbo.SlideSetParameters] PRIMARY KEY ([SlideSet_SlideSetID], [Parameter_ParameterID])
)
CREATE INDEX [IX_SlideSet_SlideSetID] ON [dbo].[SlideSetParameters]([SlideSet_SlideSetID])
CREATE INDEX [IX_Parameter_ParameterID] ON [dbo].[SlideSetParameters]([Parameter_ParameterID])
ALTER TABLE [dbo].[SlideSetParameters] ADD CONSTRAINT [FK_dbo.SlideSetParameters_dbo.SlideSets_SlideSet_SlideSetID] FOREIGN KEY ([SlideSet_SlideSetID]) REFERENCES [dbo].[SlideSets] ([SlideSetID]) ON DELETE CASCADE
ALTER TABLE [dbo].[SlideSetParameters] ADD CONSTRAINT [FK_dbo.SlideSetParameters_dbo.Parameters_Parameter_ParameterID] FOREIGN KEY ([Parameter_ParameterID]) REFERENCES [dbo].[Parameters] ([ParameterID]) ON DELETE CASCADE
...this makes the original tables practically 'agnostic' of the relationships (many-to-many) - while index table is automatically generated in the background.
You can also further customize that and make your own SlideSetParam (e.g. if you'd want to add additional fields there) with pretty much the same layout - just Parameters would have to point to that instead.
I have some issues getting cascading to work on my optional one-to-one and one-to-many relation. Enabling 1 of them works fine but enabling both results in a 'possible circular cascading exception'.
I have a 'Customer' which has multiple 'DeliverAddresses' and one 'VisitAddress'. So for address I have an optional DeliverAddressForCustomer and an optional VisitAddressForCustomer.
this results in the following tables:
CREATE TABLE [dbo].[Customer]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY
[Name] NVARCHAR(50) NOT NULL,
)
CREATE TABLE [dbo].[Address]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[Street] NVARCHAR(50) NOT NULL,
[DeliverAddressForCustomerId] INT NULL,
[VisitAddressForCustomer] INT NULL,
CONSTRAINT [FK_Address_Customer_DeliverAddressForCustomerId] FOREIGN KEY ([DeliverAddressForCustomerId]) REFERENCES [Customer]([Id]),
CONSTRAINT [FK_Address_Customer_VisitAddressForCustomerId] FOREIGN KEY ([VisitAddressForCustomerId]) REFERENCES [Customer]([Id])
)
This works with the following mapping:
this.ToTable("Address");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Postcode).HasColumnName("Postcode");
this.Property(t => t.DeliverAddressForCustomerId).HasColumnName("DeliverAddressForCustomerId");
// Relationships
this.HasOptional(t => t.DeliverAddressForCustomer)
.WithMany(t => t.DeliverAddresses)
.HasForeignKey(d => d.DeliverAddressForCustomerId)
.WillCascadeOnDelete(true);
this.HasOptional(a => a.VisitAddressForCustomer)
.WithOptionalDependent(k => k.VisitAddress)
.Map(x => x.MapKey("VisitAddressForCustomerId"))
.WillCascadeOnDelete(true);
Now if I have both 'WillCascadeOnDelete' set to true, it will work with an existing DB but not if I want to create the DB... I get the following exception message:
Introducing FOREIGN KEY constraint
'FK_dbo.Address_dbo.Address_Customer_VisitAddressForCustomerId' on table 'Address' may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
But this doesn't make sense to me since if I delete a deliveryAddress or a visitAddress, customer should stay untouched in this setting. So there should be no circular cascading.
The message says "may cause cycles or multiple cascade paths". It's the last part that matters. SQL server won't allow more than one FK constraint with cascaded deletes in one table.
By the way, note that cascaded delete does not prescribe what should happen if you delete an Address, but if you delete a Customer.
Also see this: SQL Server 2008 - Multiple Cascading FK's - Do i need a trigger?
In my WCF service I am using Entity Framework .NET 4.0,
in my database i have this table:
CREATE TABLE [dbo].[Tracking](
[TrackingID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
...
CONSTRAINT [PK_Tracking] PRIMARY KEY CLUSTERED
(
[TrackingID] ASC
)
) ON [DATA]
ALTER TABLE [dbo].[Tracking] ADD CONSTRAINT [DF_Tracking_TrackingID] DEFAULT (newid()) FOR [TrackingID]
GO
when i insert a record Entity framewrok has prepopulated the TrackingID as "00000000-0000-0000-0000-000000000000". I have setting field property to be Computed and Identity bu no such luck any ideas: here is my code snippet:
using (var context = new DB.PTLEntities())
{
var tracking = new DB.Tracking();
context.Trackings.AddObject(tracking);
context.SaveChanges();
trackingID = tracking.TrackingID;
}
Looks like EF doesnt think this column is an identity column so its putting in default(Guid). Take a look here for some details on making a guid an identity column (it actually goes through your exact example) http://leedumond.com/blog/using-a-guid-as-an-entitykey-in-entity-framework-4/
I got same issue. Following solution worked for me.
There is a need to change the StoreGeneratedPattern value for the property in the EF designer. Set it to Identity. This will cause EF to avoid setting the value on an insert, then capture it after the insert is complete.
If your EF version is 4, then you may have trouble with this using the designer only. You may have to edit the .edmx manually and set the StoreGeneratedPattern in the storage model itself.
Example:
< Property Name="EmpId" Type="uniqueidentifier" Nullable="false" StoreGeneratedPattern="Identity" />
In my case (EF >= 4.1) I needed to add a default to the table itself:
ALTER TABLE MyTable ADD DEFAULT (newid()) FOR [Id]
Also if you're using your own context, you should tell EF that the column is the identity column:
public class ShedContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DraftFromWebOneOff>()
.Property(d => d.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
}