Is it possible to have an integer property of a class auto increment managed by the database but not be a primary key (or Id as NHibernate refers to them)? I'm having trouble finding examples of how to do this. Any help would be appreciated. Thanks.
Two options.
if the database is 100% responsible for this you just need to tell NHibernate that the property is generated and not to include it in any updates/isnerts. The downside is that NH will need to do an additional select to keep the value fresh in memory.
< property name="Foo" generated="always" update="false" insert="false" />
if the database is not responsible and you just want to have this done automatically you can use an interceptor to set the value to 1 on an insert and to increment it by 1 on an update.
http://www.nhforge.org/doc/nh/en/index.html#objectstate-interceptors (11.1 - Interceptors)
You would override OnSave() to find the property and set the initial value and then override OnFlushDirty() to find the property property and increment.
Edit:
I'm an idiot, didn't notice you said Fluent NHibernate.
Edit #2:
I think you might also be interested in using this column as a versioning?
< version name="Foo" generated="always" />
This works for me:
public class Potato
{
public virtual Guid Id { get; protected set; }
public virtual int LegacyId { get; protected set; }
}
public class PotatoMap : ClassMap<Potato>
{
public PotatoMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.LegacyId).CustomSqlType("INT IDENTITY(1,1)").Not.Nullable().ReadOnly().Generated.Insert();
}
}
Basically, the integer is set to be generated by the database and NHibernate is instructed to retrieve it on inserts.
It is important to note that the mapping is only half of the answer, and it will not work if the column is not created as an IDENTITY. CustomSqlType is added to the mapping with the purpose of creating the proper sql when generating the tables with SchemaExport. This is the generated sql:
create table [Potato] (
Id UNIQUEIDENTIFIER not null,
LegacyId INT IDENTITY(1,1) not null,
primary key (Id)
)
On the other side, ReadOnly and Generated.Insert() will tell NHibernate that the value is autogenerated by the database only on inserts, and therefore it has to query the database for the value at every insert.
Note that I only tested this with Sql Server. The custom type will probably change or may not be available in other databases.
Related
I've joined a team that uses non standard names for tables and columns, and have trouble building database-first projects with Entity Framework.
Here's my problem:
tFWAClientProcessing (Table)
FWAClientHandling (Primary Key, INT)
iClientID (Foreign Key, INT)
.
tClients (Table)
AClientID (Primary Key, INT)
sClientName (VARCHAR(255))
I need Entity Framework to detect the relationship between these two tables without making changes to those tables in production.
I'd long given up on EDMX and convention-based mapping for relationships and just set up EF via EntityConfiguration classes. Attributes in the entity definitions are another option which should work for simple cases like identifying column names. You can also wire up mapping in the OnModelCreating override directly.
For instance: To have entities called Client and FWAClientProcessing for that table structure:
public class Client
{
public int ClientId { get; set; }
public string ClientName { get; set; }
}
public class FWAClientProcessing
{
public int FWAClientProcessingId { get; set; }
public virtual Client Client { get; set; }
}
public class ClientConfiguration : EntityTypeConfiguration<Client>
{
public ClientConfiguration()
{
ToTable("tClients"); // assumes default schema, i.e. "dbo" in SQL Server. Can add schema name as 2nd parameter otherwise.
HasKey(x => x.ClientId)
.Property(x => x.ClientId)
.HasColumnName("iClientID");
Property(x => x.ClientName)
.HasColumnName("sClientName");
}
}
public class FWAClientProcessingConfiguration : EntityTypeConfiguration<FWAClientPrcessing>
{
public FWAClientProcessingConfiguration()
{
ToTable("tFWAClientProcessing");
HasKey(x => x.FWAClientProcessingId)
.Property(x => x.FWAClientProcessingId)
.HasColumnName("FWAClientHandling");
HasRequired(x => x.Client)
.WithMany()
.Map(x => x.MapKey("iClientID"));
}
}
Assuming that the EntityTypeConfiguration classes are in the same assembly as the entities, and the DBContext, registering them in the context becomes:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.AddFromAssembly(TypeOf(YourDbContex).Assembly);
base.OnModelCreating(modelBuilder);
}
These examples are for EF6, EF Core uses the concept of Shadow Properties for mapping FK relationships without exposing FK properties, and can accommodate the different column naming. EntityTypeConfiguration is available as an Interface with a Configure method accepting the builder.
I favor using the explicit entity type configuration by default as it keeps the configuration nicely isolated and out of the way and can handle all mapping scenarios that might come up that annotations cannot do. It's a bit of a one-off cost to set up, but at least then you have full visibility and control over how the schema is mapped and not simply hoping EF works things out. :)
Use the modern replacement for EDMX-based Database-First and reverse-engineer a code-first model from the existing database. Customizing an EDMX-based model with its mappings is a rabbit-hole of obsolete technology.
This is available for EF Core and EF6.
The reverse-engineered model is then a starting point for you to make model customizations, like mapping the tables and columns to sensible names, and configuring any Navigation Properties that for whatever reason didn't get picked up by the tooling.
You are right, it is easier if people follow the entity framework conventions. However, if you have to deviate from them, OnModelCreating is your friend.
In OnModelCreating, from every Table, column, relation between tables, that are not standard, you can inform entity framework about these deviations.
You can give different table names
You can use other column names
You can say that certain properties should be saved in certain database formats, for instance ProductPrice is a decimal with 2 digits after the decimal point, instead of the default number of digits.
etc.
There seems to be a one-to-many relation between Clients and ClientsProcessing: every Client with primary key Id, has zero or more ClientsProcessings, every ClientProcessing belongs to exactly one Client, namely the Client that the foreign key ClientId refers to.
You want to use unconventional table names, unconventional names for you primary and foreign keys, and you need to inform about what keys are used to define the one-to-many relation.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure DbSet<Client>:
ver clients = modelBuilder.Entity<Client>();
clients.ToTable("tClients")
.HasKey(client => client.Id)
// property Id should be in "AClientID"
clients.Property(client => client.Id).HasColumnName("AClientID");
clients.Property(client => client.Name).HasColumnName("sClientName");
Apart from different names of the columns, you can also declare whether the properties are required or optional, what format they should have (is a decimal with two digits after the decimal point, or does it have four digits?), etc.
Do something similar for modelBuilder.Entity<ClientProcessing>();
For the one-to-many relation: every Client has zero or more ClientProcessings; every ClientProcessing belongs to exactly one (required!) Client, namely the foreign key that ClientId refers to:
clients.HasMany(client => client.ClientProcessings)
.WithRequired(clientProcessing => clientProcessing.Client)
.HasForeignKey(clientProcessing => clientProcessing.ClientId);
Or if you want, you can start at ClienProcessing: every ClientProcessing has exactly one Client (required!), using foreign key ClientId. Every Client has many ClientProcessings.
modelBuilder.Entity<ClientProcessing>()
.HasRequired(clientProcessing => clientProcessing.Client)
.WithMany()
.HasForeignKey(clientProcessing => clientProcessing.ClientId);
Note: by default this will cascade on delete: whenever you delete a client, you will also delete all its processings: you did define there are no processings without a client.
In some relations, you don't want this, especially many-to-many relations or one-to-zero-or-one relation: a Student may have zero or one School-supplied-Laptop. If you delete the Laptop, you don't want to delete the Student as well. In that case you'll have to add .WillCascadeOnDelete(false)
I know that you set a nullable key in your entity if you want that FK to be nullable:
class ChildEntity
{
// Other properties not shown for brevity
public int? ParentId { get; set; }
public virtual ParentEntity Parent; { get; set; }
}
This will result in a nullable FK. It was suggested here that we should also set the optional relationship in Fluent:
modelBuilder.Entity<ChildEntity>()
.HasOptional(c => c.Parent)
.WithMany()
.HasForeignKey(c => c.ParentId);
but this still doesn't set delete set null. The FK ParentId still has delete set to No Action.
Later in the article, it was suggested that we should run the SQL command in the Seed method of the Configuration class? I'm not sure if it's a problem, but I run Update-Database quite often, and I'd be changing this setting back and forth.
So, is it safe, then, to "go behind EF's back" and change the delete rule to SET NULL in SQL Management Studio (or other app)? Since we're using SqlCommand in the seed method in plain SQL language, I want to say yes, we can go ahead and manually change the delete rule, but I'm not sure. I can't afford to experiment at this point, so I would appreciate an answer for this.
That example puts the sql in the Seed method and that means that it runs every time you call Update-Database. You avoid that by making the modification to the database using the Sql method in a migration. That way it only runs once.
public void Up()
{
Sql(#"ALTER TABLE Products DROP CONSTRAINT Product_Category");
Sql(#"ALTER TABLE Products ADD CONSTRAINT Product_Category
FOREIGN KEY (CategoryId) REFERENCES Categories (CategoryId)
ON UPDATE CASCADE ON DELETE SET NULL");"
}
I have a couple of classes that look like this:
public class Client
{
public int Id {get;set;}
public string Name {get;set;}
}
public class User
{
public int Id {get;set;}
public string Email {get;set;}
public Client Client {get;set;}
}
I'm using ConventionModelMapper and SchemaUpdate from NHibernate 3.2 to generate the schema in my SQL Server database and I want the Client property of the User class to be mapped to a ClientId column with foreign key. My convention code looks like this:
mapper.AfterMapManyToOne += (inspector, member, map) =>
{
map.Column(member.LocalMember.Name + "Id");
// ...
};
This works, in that I get a column ClientId that is mapped as a foreign key, but I also end up with a Client column that is also mapped as a foreign key. It seems that NHibernate is treating the Client property as both a standard Property (and thus generating the Client column for it), and also a ManyToOne property (resulting in the additional ClientId column). How can I prevent the Client column from being generated
I have just copied your EXACT code and, after making the properties virtual, the behavior is the expected (there's a single column, ClientId)
I have a situation where i have defined an entity in my domain model in which I would like to expose a single id column.
public class OfferedProduct
{
public virtual string Id {get; set;}
//other properties
}
The legacy database table this will map to is
CREATE TABLE ProductGrouping
MemberNumber INT NOT NULL,
GroupId CHAR NOT NULL,
...
I dont want to compromise the domain model by introducing two properties and mapping them using the "CompositeId" construct.
CompositeId().KeyProperty(x => x.MemberNumber).KeyProperty(x => x.GroupId)
What I want ideally is to concatenate the two values in the form {MemberNumber}{GroupId} and expose this as the Id value. I would then use a Custom Type to handle how these values are concatenated when retrieved from the DB and broken apart when saving/selecting.
I have noticed that the "CompositeId" method does not allow a customType as with the standard "Id" call; but the "Id" method does not provide the ability to set multiple columns. I have seen examples where people have used "Map" to combine two columns using a custom type, but not for id values.
I have noticed the "CompositeId" has an overload that can take a custom identity class but I am unsure how to use it in this scenario.
CompositeId<OfferedProductIdentifier>(x => x.?)
Any help would be greatly appreciated.
in case someone comes here
CompositeId()
.KeyProperty(t => t.Id, c =>
c.Type(typeof(MyUserType)).ColumnName("MemberNumber").ColumnName("GroupId"));
I've got a MS-SQL database with a table created with this code
CREATE TABLE [dbo].[portfoliomanager](
[idPortfolioManager] [int] NOT NULL PRIMARY KEY IDENTITY,
[name] [varchar](45) NULL
)
so that idPortfolioManager is my primary key and also auto-incrementing. Now on my Windows WPF application I'm using NHibernate to help with adding/updating/removing/etc. data from the database. Here is the class that should be connecting to the portfoliomanager table
namespace PortfolioManager
{
[Class(Table="portfoliomanager",NameType=typeof(PortfolioManagerClass))]
public class PortfolioManagerClass {
[Id(Name = "idPortfolioManager")]
[Generator(1, Class = "identity")]
public virtual int idPortfolioManager { get; set; }
[NHibernate.Mapping.Attributes.Property(Name = "name")]
public virtual string name { get; set; }
public PortfolioManagerClass() {
}
}
}
and some short code to try and insert something
PortfolioManagerClass portfolio = new PortfolioManagerClass();
Portfolio.name = "Brad's Portfolios";
The problem is, when I try running this, I get this error:
{System.Data.SqlClient.SqlException: Cannot insert the value NULL into
column
'idPortfolioManager', table 'PortfolioManagementSystem.dbo.portfoliomanager'; column
does not allow nulls. INSERT fails. The statement has been terminated...
with an outer exception of
{"could not insert: [PortfolioManager.PortfolioManagerClass][SQL:
INSERT INTO
portfoliomanager (name) VALUES (?); select SCOPE_IDENTITY()]"}
I'm hoping this is the last error I'll have to solve with NHibernate just to get it to do something, it's been a long process. Just as a note, I've also tried setting Class="native" and unsaved-value="0" with the same error. Thanks!
Edit:
Ok removing the 1, from Generator actually allows the program to run (not sure why that was even in the samples I was looking at) but it actually doesn't get added to the database. I logged in to the server and ran the sql server profiler tool and I never see the connection coming through or the SQL its trying to run, but NHibernate isn't throwing an error anymore. Starting to think it would be easier to just write SQL statements myself :(
Just FYI, you should take a look at Fluent NHibernate. It will substantially reduce the amount of headaches you'll have when implementing most NHibernate mappings.