How to define DeleteBehavior in the class inheriting from another? - sql-server

I know I could use DeleteBehavior attribute in a property, the problem is that property is inherited.
I have the following classes:
[Table("Profiles")]
public class Profile
{
public Guid Id { get; set; }
// ...
}
[Table("Agents")]
public class AgentProfile : Profile
{
// ...
}
And this is the migration generated:
migrationBuilder.CreateTable(
name: "Agents",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
// ...
},
constraints: table =>
{
table.PrimaryKey("PK_Agents", x => x.Id);
// ...
table.ForeignKey(
name: "FK_Agents_Profiles_Id",
column: x => x.Id,
principalTable: "Profiles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
The problem is this line:
onDelete: ReferentialAction.Cascade);
How do I mark that specific FK as Restrict? I know how to do it for explicit relationships like:
EntityTypeBuilder<AgentProfile>
.HasMany(a => a.Cases)
.WithOne(c => c.Agent)
.OnDelete(DeleteBehavior.Restrict);
But I couldn't find out how to do that for my scenario (the inherited relationship). The migration is created (a weird behavior from EF Core IMO, I think the error should be thrown during the migration creation), but I can't update the database because I get the error:
Introducing FOREIGN KEY constraint 'FK_Agents_Profiles_Id' on table 'Agents' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors
I know I could simply change the generated migration and change it to Restrict, but I don't want to do that, otherwise it'll be an extra step, using the FluentApi would be ideal. I also understand MS SQL Server has its own restrictions, but I'm assuming it's possible to define this behavior without having to change the auto generated migration.
How can I do so using an attribute or the FluentApi?
Using EF Core 7.

Response by ajcvickers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<AgentProfile>()
.HasOne<Profile>()
.WithOne()
.HasForeignKey<AgentProfile>(e => e.Id)
.OnDelete(DeleteBehavior.Restrict);
}
See https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/breaking-changes#tpt-cascade-delete

Related

Dotnet EF Core Linq string contains in a list string split by comma

I have a model like this in the database:
Post (PostId int, UserIds varchar(MAX)), example Post (12, "1,2,3,7,9,20")
I want to query it by UserId, for now, I use this:
DBContext.Posts.Where(_ => _.UserIds.Contains(targetId)).ToList();
But problem is that if target is 1, it also return Post with UserIds = "15,16"
I try to use Regex like Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") but SQL can't translate it.
Is any way to solve this case?
So your database has a table filled with Posts. Every Post seems to be posted by zero or more (maybe one or more) Users. It seems to me, that you also have a table of Users. Every User has posted zero or more Posts.
It seems to me that there is a many-to-many relation between Users and Posts: Every User has posted zero or more Posts; every Post has been posted by zero (one?) or more Users.
Normally in a database you would implement a many-to-many relation with a special table: the junction table.
You don't use the junction table. Your database is not normalized.
Maybe your current problem can be solved without changing the database, but I see so many problems you will have to solve, maybe not now, but in the near future: what immense work would you need to do if you want to delete a user? How do you get all "Posts that user [10] has posted" And what if User [10] doesn't want to be mentioned anymore in the publication list of Post [23]? How to prevent that User [10] is mentioned twice in Post[23]:
UserIds = 10, 3, 5, 10, 7, 10
Normalize the database
Consider to update the database with a junction table and get rid of the string column Post.UserIds. This would solve all these problems at once.
class User
{
public int Id {get; set;}
public string Name {get; set;}
...
// every user has posted zero or more Posts:
public virtual ICollection<Post> Posts {get; set;}
}
class Post
{
public int Id {get; set;}
public string Title {get; set;}
public Datetime PublicationDate {get; set;}
...
// every Post has been posted by zero or more Users:
public virtual ICollection<User> Users {get; set;}
}
And the junction table:
public UsersPost
{
public int UserId {get; set;}
public int PostId {get; set;}
}
Note: [UserId, PostId] is unique. Use this a the primary key
In entity framework the columns of tables are represented by non-virtual properties. The virtual properties reflect the relations between the tables (one-to-many, many-to-many)
Note: a foreign key is a real column in a table, hence a foreign key is non-virtual.
To configure many-to-many, you can use Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// User - Post: many-to-many
modelBuilder.Entity<User>()
.HasMany<Post>(user => user.Posts)
.WithMany(post => post.Users)
.Map(userpost =>
{
userpost.MapLeftKey(nameof(UserPost.UserId));
userpost.MapRightKey(nameof(UserPost.PostId));
userpost.ToTable(nameof(UserPost));
});
// primary key of UserPost is a composite key:
modelBuilder.Entity<UserPost>()
.HasKey(userpost => new {userpost.UserId, userpost.PostId});
}
Back to your problem
Once you've implemented the junction table your data request will be easy:
int userId = ...
// get this User with all his Posts:
var userWithPosts= dbContext.Users
.Where(user => user.Id == userId)
.Select(user => new
{
// Select only the user properties that you plan to use
Name = user.Name,
...
Posts = user.Posts.Select(post => new
{
// Select only the Post properties that you plan to use
Id = post.Id
PublicationDate = post.PublicationDate,
...
})
.ToList(),
});
Or, if you don't want any user data, start with the Posts:
var postsOfUser = dbContext.Posts
.Where(post => post.Users.Any(user => user.Id == userId))
.Select(post => new {...});
Some people don't like to use the virtual ICollections, or they use a version of entity framework that doesn't support this. In that case, you'll have to do the Join yourself:
int userId = ...
var postsOfThisUser = dbContext.UserPosts
// keep only the UserPosts of this user:
.Where(userPost => post.UserId == userId)
// join the remaining UserPosts with Posts
.Join(dbContext.Posts,
userpost => userpost.PostId, // from every UserPost get the foreign key to Post
post => post.Id, // from every Post, get the primary key
// parameter resultSelector: from every UserPost with matching Post make one new
(userPost, post) => new
{
Title = post.Title,
PublicationDate = post.PublicationDate,
...
}
}
Solution without normalized database
If you really can't convince your project leader that a proper database will prevent a lot of problems in the future, consider to create a SQL text that get the proper posts for you.
Your DbContext represents the current implementation of your database. It described the tables and the relations between the tables. Adding a method to fetch the Posts of a user seems to me a legit method for the DbContext.
My SQL is a bit rusty, you'll know way better than I how to do this in SQL. I guess you'll get the gist:
public IEnumerable<Post> GetPostsOfUser(int userId)
{
const string sqlText = "Select Id, ... from Posts where ..."
object[] parameters = new object[] {userId};
return this.Database.SqlQuery(sqlText, parameters);
}
Here's a possible solution if you can't normalize it:
var sql = "select PostId,UserIds from Post";
sql += $" outer apply string_split(UserIds,',') where value={targetId}";
DBContext.Posts.FromSqlRaw(sql).ToList();

Exception The value of 'X' is unknown when attempting to save changes

There are these two entities:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public CompanyVehicle CompanyVehicle { get; set; }
}
and
public class CompanyVehicle
{
public int Id { get; set; }
public string Name { get; set; }
public Employee Employee { get; set; }
}
Using Entity Framework Core 5.0.8 on SQL Server 2019, the configuration for CompanyVehicle is:
entityBuilder.HasOne(t => t.Employee)
.WithOne(t => t.CompanyVehicle)
.HasForeignKey<Employee>(t => t.Id)
.IsRequired();
And we'll try to insert something:
public void Create(Employee employee)
{
employee.CompanyVehicle = new CompanyVehicle();
dbContext.Add<Employee>(employee);
dbContext.SaveChanges();
}
The above code used to work fine in EF6. Two new records in both Employee and CompanyVehicle tables were created with the same Id. After migrating to EF Core 5.0.8, dbContext.SaveChanges() throws an exception:
System.InvalidOperationException: 'The value of 'Employee.Id' is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.'
Note that these entities are just examples and the database design should not be altered in my case.
Update
After some more investigation, I've found out my problem is:
Having X (principal) and Y (dependent) as two tables where X.Id is PK for X and Y.Id is PK for Y and also FK to X, in EF Core a record of X cannot be inserted.
So I finally found the problem, configuring a Property to be both PK and FK is possible and very easy. We had our old codes after migrating to EFCore from EF6 in an assembly. The project is a framework so in OnModelCreating we use modelBuilder.ApplyConfigurationsFromAssembly in our base DbContext to register configurations in the guest projects. The project will automatically find all the configurations in all of assemblies referenced by the project or DLLs in the application path.
The key point is: In EF Core explicit fluent FK configuration is in the reverse order compared to EF6. So in EF6 for Employee we used to write:
this.HasRequired(t => t.CompanyVehicle)
.WithRequiredDependent(t => t.Employee)
.HasForeignKey(d => d.Id);
and in EF Core we should write:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<Employee>(t => t.Id).IsRequired();
The parameter d used in the first part is of type CompanyVehicle. So our migrator converted the old code to:
b.HasOne(t => t.CompanyVehicle)
.WithOne(t => t.Employee)
.HasForeignKey<CompanyVehicle>(t => t.Id).IsRequired();
Which is incorrect. The generic parameter should be the dependent table type. We later fixed the issue in a new namespace but the ApplyConfigurationsFromAssembly method kept applying the obsolete code after our configuration too.
I used the following block of code at the end of OnModelCreating to investigate the issue:
foreach (var entity in modelBuilder.Model.GetEntityTypes())
foreach(var key in entity.GetForeignKeys())
{
//Check what is in the key...
}
and noticed that there are duplicated keys configured for my entities.
Entity Framework Core configures one to one relationships by being able to detect the foreign key property, and thereby identify which is the principal and which is the dependent entity in the relationship.
First look at the existing database and check what is the dependant table, assuming it is the Employee, it should have a foriegn key to CompanyVehicle table. (It could be other way around in your case.)
1. Using EF Core convestions.
If Employee is the depentant table, add that exact foriegn key property name (let's assume it's Vehicle_Id) to your Employee entity. Follow 2nd method if you don't want to add a property to the class.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Vehicle_Id { get; set; } // <-- This right here.
public CompanyVehicle CompanyVehicle { get; set; }
}
Without this property, as I mentioned earlier, child/dependent side could not be determined for the one-to-one relationship. (check what is yours in the db and add that property, otherwise you will get two foreign keys in the Employee table)
And using fluent API, configure the relation like this. (Notice how a and b were used to separate two navigation properties, in your implementation you have used t, for both, and when you say .HasForeignKey<Employee>(t => t.Id), you're setting the foriegn key to primary key Id of Employee table, which could be the reason behind your error).
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Vehicle_Id);
}
2. Not using EF Core conventions.
If you do not like to add a property to the dependant table, use the exsisting foriegn key in the database (let's assume it's Vehicle_Id), fluent API config should look like this.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>("Vehicle_Id");
}
Edit:
The Has/With pattern is used to close the loop and fully define a relationship. In this case, since the relationship to be configured is a one-to-one, the HasOne method is chained with the WithOne method. Then the dependent entity (Employee) is identified by passing it in as a type parameter to the HasForeignKey method, which takes a lambda specifying which property in the dependent type is the foreign key.
So if you want the Employee Id to act as the foriegn key to the CompanyVehicle table, ammend your Fluent API as this, again notice a and b when specifying lambdas.
modelBuilder.Entity<CompanyVehicle>()
.HasOne(a => a.Employee)
.WithOne(b => b.CompanyVehicle)
.HasForeignKey<Employee>(b => b.Id);
I had the same issue that A. Morel had.
When manually inserting a custom join table for ManyToMany, and a foreign key was 0, I was getting this error.
Fixed by changing the seed value of the parent table to start at 2:
DBCC CHECKIDENT ('program_contact', RESEED, 1);
Because of this issue
DBCC CHECKIDENT Sets Identity to 0
For me this seems to be caused by creating "blank" entities that I don't add to the context. In EF6 these were ignored since they were not added, but in EF Core they seem to be added automatically.
I corrected the issue by reducing the scope of my "writable" context down to just the single line where a change was made, and used a separate "read only" context for everything else.
I could further correct this by not using entity types directly in my view so that I can make blank entries that are not entities.

How to switch between DatabaseGeneratedOption.Identity, Computed and None at runtime without having to generate empty DbMigrations

I am migrating a legacy database to a new database which we need to access and "manage" (as oxymoronic as it might sound) primarily through Entity Framework Code-First.
We are using MS SQL Server 2014.
The legacy database contained some tables with computed columns. Typical GUID and DateTime stuff.
Technically speaking, these columns did not have a computed column specification, but rather where given a default value with NEWID() and GETDATE()
We all know that it is very easy to configure the DbContext to deal with those properties as follows:
modelBuilder.Entity<Foo>()
.Property(t => t.Guid)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
modelBuilder.Entity<Bar>()
.Property(t => t.DTS)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
The above would instruct the Entity Framework to ignore submitting any supplied values for such properties during INSERTs and UPDATEs.
But now we need to allow for import of legacy records and maintain the OLD values, including the PRIMARY KEY, which is marked as IDENTITY
This means we would have to set the Id, Guid and DTS properties to DatabaseGeneratedOption.None while inserting those records.
For the case of Id, we would have to somehow execute SET IDENTITY_INSERT ... ON/OFF within the connection session.
And we want to do this
importing process via Code-First as well.
If I modify the model and "temporarily" and set those properties to DatabaseGeneratedOption.None after the database has been created, we would get the typical:
The model backing the context has changed since the database was created. Consider using Code First Migrations to update the database.
I understand that we could generate an empty coded-migration with -IgnoreChanges so as to "establish" this latest version of the context, but this wouldn't be an acceptable strategy as we would have to be run empty migrations back-and-forth solely for this purpose.
Half an answer:
We have considered giving these properties nullable types, i.e.
public class Foo
{
...
public Guid? Guid { get; set; }
}
public class Bar
{
...
public DateTime? DTS { get; set; }
}
While caring about the default values in an initial DbMigration:
CreateTable(
"dbo.Foos",
c => new
{
Id = c.Int(nullable: false, identity: true),
Guid = c.Guid(nullable: false, defaultValueSql: "NEWID()"),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Bars",
c => new
{
Id = c.Int(nullable: false, identity: true),
DTS = c.Guid(nullable: false, defaultValueSql: "GETDATE()"),
})
.PrimaryKey(t => t.Id);
The Question:
But the question remains: Is there a way to switch between DatabaseGeneratedOption.Identity, DatabaseGeneratedOption.Computed and DatabaseGeneratedOption.None at runtime?
At the very least, how could we turn DatabaseGeneratedOption.Identity on/off at runtime?
A certain amount of the configuration of the context is always going to be dependent on the runtime environment - for example, proxy generation and validation. As such, runtime configuration of the Entity Framework DbContext is something I leverage quite heavily.
Although I've never used this approach to switch the configuration of the context on a per use-case basis, I see no reason why this would not work.
In its simplest form, this can be achieved by having a set of EntityTypeConfiguration classes for each environment. Each configuration set is then wired to the DbContext on a per-environment basis. Again, in its simplest form this could be achieved by having a DbContext type per environment. In your case, this would be per use-case.
Less naively, I usually encapsulate the configuration of the context in an environment-specific unit of work. For example, the unit of work for an Asp.Net environment has an underlying DbContext configured to delegate validation to the web framework, as well as to turn off proxy generation to prevent serialisation issues. I imagine this approach would have similar usefulness to your problem.
For example (using brute force code):
// Foo Configuration which enforces computed columns
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
}
// Foo configuration that allows computed columns to be overridden
public class FooConfiguration2 : EntityTypeConfiguration<Foo>
{
public FooConfiguration2()
{
Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
// DbContext that enforces computed columns
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooConfiguration());
}
}
// DbContext that allows computed columns to be overridden
public class MyContext2 : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooConfiguration2());
}
}
This can obviously be tidied up - we usually use a combination of factory and strategy patterns to encapsulate the creation of a runtime specific context. In combination with a DI container this allows the correct set up configuration classes to be injected on a per-environment basis.
Example usage:
[Fact]
public void CanConfigureContextAtRuntime()
{
// Enforce computed columns
using (var context = new EfContext())
{
var foo1 = new Foo();
context.Foos.Add(foo1);
context.SaveChanges();
}
// Allow overridden computed columns
using (var context = new EfContext2())
{
var foo2 = new Foo { DateTime = DateTime.Now.AddYears(-3) };
context.Foos.Add(foo2);
context.SaveChanges();
}
// etc
}

How to delete child entities before parent with Entity Framework CF?

I am trying to use EF code-first to delete a db record (deleteMe) and it's children (deleteMe.Prices).
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
db.Entry(deleteMe).State = EntityState.Deleted;
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
}
}
db.SaveChanges();
However, Entity Framework seems to be unable to track the fact that the child records should be deleted before the parent. I get the error:
The DELETE statement conflicted with the REFERENCE constraint "ItemPrice_Item".
The conflict occurred in database "DEVDB", table "dbo.ItemPrices",
column 'Item_ItemId'.
The statement has been terminated.
How would I execute this delete in EF?
I ended up finding a quick line that'd do it for me:
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
///
deleteMe.Prices.ToList().ForEach(p => db.ItemPrices.Remove(p));
///
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
EF6
context.Children.RemoveRange(parent.Children)
Cascade delete in EF is dependent on cascade delete configured in relation in the database so if you don't have cascade delete configured in the database you must first load all item prices to your application and mark them as deleted.
Well the most easiest solution would be to iterate through prices first and call save changes, then set the entry to delete for deleteMe and call save changes again, but have you checked out this: Entity framework code first delete with cascade? It seems to be what you want.
Curious though also why you just aren't removing the entities from the context to delete but instead setting the entry state?
Another option is to set cascade delete http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx
Do something like this (not tested but hopefully you get the jist):
using (TransactionScope scope = new TransactionScope())
{
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
db.SaveChanges();
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
scope.Complete();
}
Additionally you could call:
db.Prices.Remove(item);
and
db.DeleteMes.Remove(deleteMe);
instead of setting the entry state. Not sure if there is a difference behind the scenes between the two though.
Cascade delete in Entity framework is tricky thing, as you need to be sure about deletion entity object graph.It is better to always write a integration test for these cascade deletes.
If you try to delete parent entity in EF, it will try to execute delete statements for any child entities in current dbcontext. As a result, it will not initialize any child entities which have not been loaded. This will lead to RDBMS runtime error which violate the foreign key constraint. To be in safe side ensure all dependent entities loaded to current dbcontext before deleting.
The following works quite efficiently.
For each relational table in your database add the following (At your context file).
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<TableA>()
.HasMany(x => x.TableB)
.WithRequired(x => x.TableA)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableC>()
.HasMany(x => x.TableD)
.WithRequired(x => x.TableC)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableE>()
.HasMany(x => x.TableF)
.WithRequired(x => x.TableE)
.WillCascadeOnDelete(); }
Then in your code, don't forget to load these tables, before you delete
context.TableA.Load();
context.TableB.Load();
context.TableC.Load();
context.TableD.Load();
context.TableE.Load();
context.TableF.Load();
var tableAEntity= TableA.Where(x => x.Condition == [yourcondition].FirstOrDefault();
context.TableA.Remove(tableAEntity);
context.SaveChanges();
This will delete the entity (record) from the main entry table and all the connected table records (related through FK) quite fast and efficiently (Even if the relationship cascades deeply at multiple levels).
If your object is self-referencing, you can delete both many-to-many and one-to-many children using the method below. Just remember to call db.SaveChanges() afterwards :)
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Object obj = this.db.Objects.Find(id);
this.DeleteObjectAndChildren(obj);
this.db.Objects.Remove(obj);
this.db.SaveChanges();
return this.Json(new { success = true });
}
/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
/// - MH # 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
// Deletes One-to-Many Children
if (parent.Things != null && parent.Things.Count > 0)
{
this.db.Things.RemoveRange(parent.Things);
}
// Deletes Self Referenced Children
if (parent.Children != null && parent.Children.Count > 0)
{
foreach (var child in parent.Children)
{
this.DeleteObjectAndChildren(child);
}
this.db.Objects.RemoveRange(parent.Children);
}
}
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}
_context.Remove(parent);
_context.RemoveRange(_context.Childrens
.Where(p => parent.Childrens
.Select(c => c.Id).Contains(p.Id)));

Using NHibernate mapping by code: Cannot insert explicit value for identity column in table 'DietUser' when IDENTITY_INSERT is set to OFF

Took me a while to find an answer for this so thought I'd share the love.
When using NHibernate's new mapping by code with SQL Server I'm unable to save an entity. When saving an entity a System.Data.SqlClient.SqlException is thrown with the following message (minus the table name):
"Cannot insert explicit value for identity column in table 'DietUser' when IDENTITY_INSERT is set to OFF."
My table uses an identity ID and the entity & mappings look like this:
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual short DailyPoints { get; set; }
}
public class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.Id);
Property(x => x.Name);
Property(x => x.Username);
Property(x => x.Password);
Property(x => x.DailyPoints);
}
}
I know how I'd map this using XML mapping but I want to use the built-in mapping by code if at all possible.
After some digging around in the forums I got lucky and found how to map this. The example Fabio provides is great if you're going to use a GUID for an ID or something like that but if you want to use an identity ID (saw a bunch of others as well), you need to specify what generator you're going to use. Here's my updated mapping that works:
public class UserMapping : ClassMapping<User>
{
public UserMapping()
{
Id(x => x.Id, map => map.Generator(Generators.Identity));
Property(x => x.Name);
Property(x => x.Username);
Property(x => x.Password);
Property(x => x.DailyPoints);
}
}
Speaking of identity ids, if you set generator=identity, NHibernate would not be able to perform batch insert, because it needs an additional request to obtain this bloody id generated by the database.
Are you trying to assign the value to Id property before persisting the entity? Can you add the peice of code which is trying to persist changes to the database?
While I started learning NHibernate I had built a small demo foloowing the getting started example. Here it is. Hope it helps.

Resources