I'm having issues with wrong NHibernate mappings when there is a nullable database field and developer forget to declare it nullable in the corresponding .Net Entity. Example:
Table:
CREATE TABLE myTable
(
ID int NOT NULL,
Total int NOT NULL,
Discount int NULL --Nullable field
)
INSERT INTO myTable VALUES (1, 10, NULL)
C# Entity:
public class MyTable{
public int ID { get; set; }
public int Total { get; set; }
public int Discount { get; set; } //not declared as nullable by mistake
}
NHibernate Mapping:
public class MyTableMap : ClassMap<MyTable>
{
public MyTableMap()
{
Table("dbo.myTable");
Id(x => x.ID).GeneratedBy.Assigned().Column("ID");
Map(x => x.Total).Column("Total");
Map(x => x.Discount).Column("Discount"); //setting mapping to .Nullable() doesn't change the behaviour
}
}
When i try to load the entity:
session.Get<MyTable>(1);
I would expect to get an exception, because the Discount field is null, but instead, NHibernate silently load the entity with default value 0 and then updates the database table at the first session.Flush(), even if i don't change any other value of the entity. That's even worse with datetime fields, because default value of .Net DateTime is '01/01/0001' and i get the exception:
The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
Did anyone else faced the same issue? Is there any configuration in SessionFactory to force NHibernate to throw an exception when a NULL column is mapped to non nullable .Net property? It's difficult to get fix it by checking every single mapping of every single property and column, especially when you work on someone else's code.
Following the suggestion in comments, i wrote a few conventions for primitive types that throws an exception on NullSafeGet method when database value is NULL and property is not declared as nullable.
For example, following is the custom type to apply to Int properties:
public class IntNotNullableType : Int32Type
{
public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session)
{
//Check if value returned by database is null, then throw an exception.
//Unfortunately column name is something like col_2_0_, but you should be able to see the full query in exception log, so you can find the wrong mapping
if (rs.IsDBNull(name))
throw new NoNullAllowedException("Column " + name + " returned NULL value for not nullable property");
return base.NullSafeGet(rs, name, session);
}
}
The convention:
public class IntNotNullableTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType == typeof(int) && Nullable.GetUnderlyingType(x.Property.PropertyType) == null); //apply to all int properties NOT declared as nullable (int? or Nullable<int>)
}
public void Apply(IPropertyInstance instance)
{
instance.CustomType(typeof(IntNotNullableType));
}
}
And finally add the convention in SessionFactory:
public static class SessionFactoryBuilder
{
public static ISessionFactory Build(string connectionString)
{
return Fluently
.Configure()
.Database(() =>
{
return MsSqlConfiguration
.MsSql2012
.ConnectionString(connectionString);
})
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
.Conventions.Add<IntNotNullableTypeConvention>() //add the convention
)
.BuildSessionFactory();
}
}
You can do the same for all other primitive types, such as DateTime, bool, double etc. Just create a new Type and Convention, inheriting from the correct type.
Example for datetime:
public class DateTimeNotNullableType : DateTimeType
{
public override object NullSafeGet(DbDataReader rs, string name, ISessionImplementor session)
{
if (rs.IsDBNull(name))
throw new NoNullAllowedException("Column " + name + " returned NULL value for not nullable property");
return base.NullSafeGet(rs, name, session);
}
}
public class DateTimeNotNullableTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType == typeof(DateTime) && Nullable.GetUnderlyingType(x.Property.PropertyType) == null);
}
public void Apply(IPropertyInstance instance)
{
instance.CustomType(typeof(DateTimeNotNullableType));
}
}
public static ISessionFactory Build(string connectionString)
{
return Fluently
.Configure()
.Database(() =>
{
return MsSqlConfiguration
.MsSql2012
.ConnectionString(connectionString);
})
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
.Conventions.Add<IntNotNullableTypeConvention>() //add the convention
.Conventions.Add<DateTimeNotNullableTypeConvention>()
)
.BuildSessionFactory();
}
Related
I like to add some data to a column, from a method instead of a property. Is this somehow possible in EF Core?
For example, the config code could look like this:
internal class MyEntityTypeConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ToTable("Table1");
// Add column "Value1" and set it with the return value of myEntity.GetValue()
builder.Property<string>("Value1").WithValue(myEntity => myEntity.GetValue()); // TODO create WithValue
builder.HasKey(o => o.Id);
}
}
in this case, the WithValue method won't exist.
Example:
For example, I will save 2 entities.
GetValue() for entity 1 returns "I am Entity 1"
GetValue() for entity 2 returns "I am Entity 2"
Then I like store "I am Entity 1" and "I am Entity 2" in the column Value1
Solution
Jairo's solution with the ValueGenerator worked perfect for me! I made the WithValue like this:
internal class ValueRetriever<TEntityEntry, TResult> : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator<TResult>
{
private readonly Func<TEntityEntry, TResult> _retrieve;
public ValueRetriever(Func<TEntityEntry, TResult> retrieve)
{
_retrieve = retrieve;
}
public override bool GeneratesTemporaryValues => false;
public override TResult Next(EntityEntry entry) => _retrieve((TEntityEntry)entry.Entity);
}
WithValue extension:
public static void WithValue<TEntityEntry, TResult>(this PropertyBuilder<TResult> propertyBuilder, Func<TEntityEntry, TResult> retrieve)
{
propertyBuilder.HasValueGenerator((property, type) => new ValueRetriever<TEntityEntry, TResult>(retrieve));
}
Usage:
builder
.Property<string>("Value1")
.WithValue<MyEntity, string>(myEntity => myEntity.GetValue());
I think shadow properties can help you.
EF Core shadow properties let you define & persist non-domain data, data that are not defined in your classes. You define shadow properties in your DbContext, and you use the DbContext to set their values.
To define them:
modelBuilder.Entity<MyEntity>().Property<String>("Value1");
To set their values:
dbContext.Entry(myEntity).Property("Value1").CurrentValue = myEntity.GetValue();
Also, you can use the HasValueGenerator extension method to set a value generator that can get the value from your entity:
modelBuilder.Entity<MyEntity>().Property<string>("Value1").HasValueGenerator<ValueGenerator>();
The Value Generator:
class ValueGenerator : Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator
{
public override bool GeneratesTemporaryValues => false;
protected override object NextValue(EntityEntry entry) => ((MyEntity) entry.Entity).GetValue();
}
The entity:
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string GetValue() => $"My Name: {Name}";
}
How do I find the biggest Id of a DbSet.Set<T>()?
Note: not DbSet<TEntity>.
I don't know the type at runtime.
Context: I have 20 tables/entities, which I'm using a generic method to do processing.
The process involves looking up the biggest Id of that table/entity and comparing it with the record at hand.
If the record's id is bigger than the database's, than it would be inserted into the database.
So far I've tried using reflection:
DbSet<T> table = DbContext.Set<T>();
var lastRecord = table.LastOrDefault(); // throws not supported error
var idProperty = lastRecord.GetType().GetProperties()
.FirstOrDefault(p => p.Name.Equals("Id");
int maxId = (int)idProperty.GetValue(lastRecord);
I've also tried using an interface cast:
interface ICommonEntity
{ // this interface allows the generic method
string StringId { get;} // to know how to handle entity Id's of
int? IntId { get; } // different types (string vs int).
}
var whatever = table.OrderByDescending(e => (e as ICommonEntity).IntId).FirstOrDefault();
int maxId = (whatever as ICommonEntity).IntId ?? 0;
But the above yields the following error:
The 'TypeAs' expression with an input of type xx is not supported. and a check of type yy. Only entity types and complex types are supported in LINQ to Entities queries
Additional data: All my entities have the column/property Id of type int.
Web searches that I've done mainly point to solutions that the type is known e.g. TEntity, db.Users.xxx() etc..
Update
In response to Ian's answer, I can't use Id directly. Why?
One of my entity has a field named Id, but is of type string.
class EntityStringId : ICommonEntity
{
public string Id { get; set; }
public string StringId => Id;
public int? IntId => null;
}
class EntityIntId : ICommonEntity
{
public int Id { get; set; }
public string StringId => null;
public int? IntId => Id;
}
And if I try to use IntId for ordering,
private void SomeMethod<T>(string file)
//where T : class // original
//where T : ICommonEntity // cannot. DbContext.Set<T>(); requires class
where T : class, ICommonEntity // throws exception
{
var table_T = DbContext.Set<T>();
var maxId = table_T.Max(e => e.IntId); // throws exception ↓
}
The specified type member 'IntId' is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties are supported.
For a better picture, my method's logic:
private void ProcessCsvToDb<T>(
DbSet<T> table,
T csvRecord) where T : class
{
var iRecord = csvRecord as ICommonEntity;
T dbRecord = null;
if (!string.IsNullOrEmpty(iRecord.StringId))
{
dbRecord = table.Find(iRecord.StringId);
}
else if (iRecord.IntId != null)
{
dbRecord = table.Find(iRecord.IntId);
}
}
In order to do this without a base class/interface, you will need to manually compose the expression:
public static IOrderedQueryable<int> OrderById(Type entityType)
{
var dbSet = context.Set(entityType);
var item = Expression.Parameter(entityType, "item");
var property = Expression.Property(item, "Id");
var lambda = Expression.Lambda<Func<T, int>>(property, item);
// the above generates:
// item => item.Id
return dbSet.OrderByDescending(lambda);
}
You can build expression to sort by Id, but DynamicQueryable class does it for you:
DbSet<T> table = assignFromSomeWhere();
var maxId = table.OrderBy("Id desc").FirstOrDefault();
DynamicQueryable also gives you different extension methods (dynamic Where, Select). Obviously it is bigger satisfaction to build expressions on your own, but sometimes it is very complicated and this library helps a lot.
If you have an interface, as discussed in comments, is there any reason you can't do this to avoid the cast:
public static int? GetMaxId<T>(DBSet<T> dbSet)
where T : ICommonEntity
{
return dbSet.OrderByDescending(e => e.Id).FirstOrDefault();
}
I have the following enumeration type:
public enum EnumType
{
E1,
E2
}
used in the following class:
public class X
{
public virtual int? Id { get; set; }
public virtual EnumType EnumProperty { get; set; }
public virtual string S { get; set; }
}
I want to persist instances of this type in a database using NHibernate. And to avoid writing boilerplate code, I'm trying to use auto mapping feature as follows:
private ISessionFactory CreateSessionFactory()
{
var mappings = AutoMap
.AssemblyOf<Domain.X>(new MyAutoMappingConfiguration());
this.NHibernateConfiguration = Fluently
.Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2012.ConnectionString(
b => b.FromConnectionStringWithKey("x")))
.Mappings(m => m.AutoMappings.Add(mappings))
.BuildConfiguration();
return this.NHibernateConfiguration.BuildSessionFactory();
}
where MyAutoMappingConfiguration looks like this:
public class MyAutoMappingConfiguration: FluentNHibernate.Automapping.DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.Namespace == "Domain";
}
public override bool IsComponent(Type type)
{
return type.Name == "EnumType";
}
}
When I use the schema generated from this configuration to create the database:
new SchemaExport(this.sessionProvider.NHibernateConfiguration)
.Execute(true, true, false);
the following script is being generated and executed:
if exists (select * from dbo.sysobjects where id = object_id(N'[X]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [X]
create table [X] (
Id INT not null,
S NVARCHAR(255) null,
primary key (Id)
)
Why is the property EnumProperty ignored?
When I add an explicit mapping for X, or (what seems equivalent) an override for auto-mapping like this:
var mappings = AutoMap
.AssemblyOf<Domain.X>(new MyAutoMappingConfiguration())
.Override<Domain.X>(m =>
{
m.Table("X");
m.Id(x => x.Id);
m.Map(x => x.EnumProperty); // this works
m.Map(x => x.S);
});
the script is generated correctly:
if exists (select * from dbo.sysobjects where id = object_id(N'[X]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [X]
create table [X] (
Id INT not null,
EnumProperty NVARCHAR(255) null,
S NVARCHAR(255) null,
primary key (Id)
)
This shows that there seems to be nothing wrong with NHibernate's ability to map the presented enum correctly. Why can't auto-mapping cope with this?
When I add the following method to MyAutoMappingConfiguration:
public override bool ShouldMap(Member member)
{
var result = base.ShouldMap(member);
return result;
}
and place the breakpoint, the result is true for the EnumProperty member, which somehow gets ignored later.
In my experience enums are mapped out of the box, nothing extra to do at all (no custom types or components).
So, there are two problems:
IsComponent shouldn't indicate that the enum in question is a component:
public override bool IsComponent(Type type)
{
return base.IsComponent(type);
}
(or just remove the implementation at all)
ShouldMap shouldn't indicate that the enum needs to be mapped explicitly. It will be mapped anyway, because it's a property. So, for example:
public override bool ShouldMap(Member member)
{
return base.ShouldMap(member) && member.CanWrite &&
!member.MemberInfo.IsDefined(typeof(NotMappedAttribute), false);
}
In your case, if the enum is in the same namespace you should do:
public override bool ShouldMap(Type type)
{
return type.Namespace == "Domain"
&& !type.IsEnum;
}
(which is a bit counter-intuitive)
I'm relatively new to EntityFramework (started using it today!)
I have the following code:
public class DemoContext : DbContext
{
public DemoContext() : base("demoContext")
{
}
public DbSet<BaseUser> Users {get; set;}
public DbSet<BaseSession> Sessions {get;set;}
}
public class DemoInitializer : DropCreateDatabaseAlways<DemoContext>
{
protected override void Seed(DemoContext context)
{
var staffUsers = new List<StaffUser>
{
new StaffUser {Id=1,Username="test",Password="test",DisplayName="test user",AccessFlags=(AccessFlags)2048 },
};
staffUsers.ForEach(su => context.Users.Add(su));
context.SaveChanges();
}
}
But whenever the context.SaveChanges(); line is called, it throws the following exception:
An exception of type
'System.Data.Entity.Infrastructure.DbUpdateException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: Error retrieving values from ObjectStateEntry.
See inner exception for details.
And the inner exception is:
The given key was not present in the dictionary.
Whilst this error doesn't really mean much to me at this point, and Googling around hasn't produced any answers, I have seemingly managed to nail it down to being related to the public property DbSet<BaseSession> Sessions {get;set} on the DemoContext class
(By this I mean, if I comment it out, the error doesn't occur and away we go!)
Why is this, and what's the correct fix?
Do I need to do something with all of the DbSet properties present in a DbContext if I use Initializer.Seed()?
I'm assuming you don't actually have to populate them all with data, as that doesn't make sense?
.
Screenshot of the full exception, incase it's useful!
.
EDIT
I've dialed it down to being a problem with this specific implementation of the BaseSession class:
public class UniqueSession : BaseSession
{
public Date DateOfSession { get; set; }
public string Comments { get; set; }
}
Which, as you can see, uses a custom Date class, which I pilfered from another question on here:
public class Date : IEquatable<Date>, IEquatable<DateTime>
{
public Date(DateTime date)
{
value = new DateTime(date.Date.Ticks, DateTimeKind.Unspecified);
//value = date.Date;
}
public bool Equals(Date other)
{
return other != null && value.Equals(other.value);
}
public bool Equals(DateTime other)
{
return value.Equals(other);
}
public override string ToString()
{
return value.ToString();
}
public static implicit operator DateTime(Date date)
{
return date.value;
}
public static explicit operator Date(DateTime dateTime)
{
return new Date(dateTime);
}
private DateTime value;
}
If I switch UniqueSession.DateOfSession to be a DateTime, this error doesn't occur?
.
EDIT 2
If I change the Date class so that the private DateTime value; is actually a public property (public DateTime value { get; set; }), the error goes away!
But why?
A complex type is (beyond other) a shortcut to "create columns in the database".
One column for one public property of the class. With no property in the class there is no column to map/create.
This may confuse EF.
The class Date for EF is like any other not built in (and handled by EF) type.
So:
- the DateOfSession is a reference to Date
- Date (Dates) is a table on SQL Server (and a DbSet on the context)
In this case the EF probably is looking for the DbSet<Date>
Our office team is working and ASP.NET project that uses .NET Framework 4 and NHibernate 3.3.1
I have a POCO domain class called AllResourcesInfo ( which ultimately maps to a Resource table in the database that is inner joined to some other tables, but just to keep things simple, let's just say that the AllResourcesInfo maps to the Resource table ).
I also have a POCO domain class called Department ( which ultimately maps to a Department table in the database )
The AllResourcesInfo could possibly have a many-to-one relationship with a Department.
If an instance of AllResourcesInfo belongs to a department then the AllResourcesInfo.DepartmentDatabaseID has a valid DepartmentDatabaseID that is in the Department entity.
However, if the instance of AllResourcesInfo does Not belong to a department then the AllResourcesInfo.DepartmentDatabaseID has a NULL value in the datbase. database.
When I use NHibernate to retrieve( basically NHibernate does a read on the Database ) values from AllResourcesInfo table, NHibernate will populate the C# POCO property of AllResourcesInfo.DepartmentDatabaseID with the 00000000-0000-0000-0000-000000000000 value ( ie the System.Guid.Empty value ) if the AllResourcesInfo does Not belong to a department
However, when I use NHibernate Session to save a brand new C# POCO instance of AllResourcesInfo which does Not belong to any department , I populate the POCO property of AllResourcesInfo.DepartmentDatabaseID with the 00000000-0000-0000-0000-000000000000 value ( ie the System.Guid.Empty value ) But The Database will Obviously Throw an Error Because a Guid value of 00000000-0000-0000-0000-000000000000 obviously fails to exist in Department table of the Database.
To summarize, NHibernate Session Save fails to save(or leave ) a uniqueidentifier field in the Database with a value of NULL when we want it to leave a uniqueidentifier field value as null.
Could someone please tell me how we can ensure that NHibernate "translates" a C# POCO Guid property with a value of System.Guid.Empty to be saved( or left) as NULL for the Database uniqueidentifier field value?
You need to implement NHibernate's IUserType.
It should like this:
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
[Serializable] //to allow NH configuration serialization
public class GuidTypeConverter : IUserType
{
SqlType[] sqlTypes;
public GuidTypeConverter()
{
sqlTypes = new[] { SqlTypeFactory.GetSqlType(DbType.Guid, 0, 0) };
}
public SqlType[] SqlTypes
{
get { return sqlTypes; }
}
public Type ReturnedType
{
get { return typeof(Guid); }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
if (rs[names[0]] == DBNull.Value)
{
return Guid.Empty;
}
return (Guid) rs[names[0]];
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var param = (IDataParameter) cmd.Parameters[index];
param.DbType = sqlTypes[0].DbType;
var guid = (Guid) value;
if (guid != Guid.Empty)
{
param.Value = guid;
}
else
{
param.Value = DBNull.Value;
}
}
public bool IsMutable
{
//guid is struct so it's not mutable
get { return false; }
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
return x != null && x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
}
(Note that I haven't tested it, there are casts here - some tweaking is probably needed)
In mapping you specify:
<property name="GuidProperty" column="guidColumn" type="GuidTypeConverterNamespace.GuidTypeConverter, GuidTypeConverterAssemblyName" />
Guid's cannot be null. You can change your type to Nullable<Guid>, or Guid? for short, to allow nulls.
What you want to do is create a mapping between Guid (not nullable Guids) and GUIDs in the database. This is the purpose of the IUserType interface in NHibernate. You would need a custom GuidUserType class that does the translation and then refer to it in the mapping definition (hbxml, fluent, whatever). See http://blog.miraclespain.com/archive/2008/Mar-18.html for a full explanation.
In the mapping for the primary key of your Department object, there should be a property called unsaved-value. Set that to the value of Guid.Empty and all will be fine.
Below is an example of how you'd do it using raw hbm mapping:
<id name="DepartmentId" type="Guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid" />
</id>