I'm trying to save a mapped entity using NHibernate but my insert to the database fails because the underlying table has a column that does not allow nulls and IS NOT mapped in my domain object. The reason it isn't mapped is because the column in question supports a legacy application and has no relevance to my application - so I'd like to not pollute my entity with the legacy property.
I know I could use a private field inside my class - but this still feels nasty to me. I've read that I can use an NHibernate interceptor and override the OnSave() method to add in the new column right before my entity is saved. This is proving difficult since I can't work out how to add an instance of Nhibernate.type.IType to the types parameter of my interceptor's OnSave.
My Entity roughly looks like this:
public class Client
{
public virtual int Id { get; set; }
public virtual int ParentId { get; set; }
public virtual string Name { get; set; }
public virtual string Phone { get; set; }
public virtual string Email { get; set; }
public virtual string Url { get; set; }
}
And my interceptor
public class ClientInterceptor : EmptyInterceptor
{
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
if (entity is Client)
{
/*
manually add the COM_HOLD column to the Client entity
*/
List<string> pn_list = propertyNames.ToList();
pn_list.Add("COM_HOLD");
propertyNames = pn_list.ToArray();
List<Object> _state = state.ToList();
_state.Add(false);
state = _state.ToArray();
//somehow add an IType to types param ??
}
return base.OnSave(entity, id, state, propertyNames, types);
}
}
Does anyone have any ideas on how to do this properly?
I can't say for sure since I've never actually done this (like Stefan, I also prefer to just add a private property), but can you just add a NHibernate.Type.BooleanType to the types array?
List<IType> typeList = types.ToList();
typeList.Add(new BooleanType());
types = typesList.ToArray();
EDIT
Yes, it looks like you are right; the types have an internal constructor. I did some digging and found TypeFactory:
Applications should use static
methods and constants on
NHibernate.NHibernateUtil if the
default IType is good enough. For example, the TypeFactory should only
be used when the String needs to have a length of 300 instead of 255. At this point
NHibernate.String does not get you thecorrect IType. Instead use TypeFactory.GetString(300) and keep a
local variable that holds a reference to the IType.
So it looks like what you want is NHibernateUtil:
Provides access to the full range of
NHibernate built-in types. IType
instances may be used to bind values
to query parameters. Also a factory
for new Blobs and Clobs.
typeList.Add(NHibernateUtil.Boolean);
Personally I wouldn't do it so complicated. I would add the private property and assign it a default value - finished. You could also consider a default value in the database, then you don't need to do anything else.
private virtual bool COM_HOLD
{
get { return false; }
set { /* make NH happy */ }
}
Before writing a interceptor for that I would consider to write a database trigger. Because with the Interceptor you are "polluting" your data access layer. It could make it unstable and you could have strange problems.
Related
I am building a Web API using Dapper for .NET Core and trying to adhere to Clean Architecture principles. The API is consumed by an external Angular front-end.
I have repositories that use Dapper to retrieve data from the database, and this data then passes through a service to be mapped into a DTO for display to the user.
It is my understanding that an entity should be an exact representation of the database object, with no extra properties, and that I should use DTOs if I require some additional properties to show the user (or if I wish to obscure certain properties from the user too).
Suppose I have a DTO:
public class StudentDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Assignment> Assignments { get; set;}
}
and its corresponding Entity:
public class Student
{
public Guid Id { get; set; }
public string Name { get; set; }
}
With this model, should I want to get a student with all of their assignments, I'd need to have two repository calls, and do something like this in the service:
public StudentDTO GetById(Guid id)
{
var student = this.studentRepository.GetById(id);
var assignments = this.assignmentRepository.GetByStudentId(id);
return SomeMapperClass.Map(student, assignments);
}
But this seems inefficient and unnecessary. My question is, should I not just retrieve the Assignments when I get the student entity in the repository, using a JOIN? Or would this violate what an entity is supposed to be?
I apologise, I do realise this is a rather simple question, but I'd really like to know which method is the best approach, or if they both have their use cases
I think it would be more efficient, since map uses reflections, that is slower tens times
public StudentDTO GetById(Guid id)
{
var student = this.studentRepository.GetById(id);
student.Assignments = this.assignmentRepository.GetByStudentId(id);
return student;
}
but the common way is
return _context.Students.Include(i=>i.Assignments).FirstOrDefault(i=> i.Id==id);
This is why the generic repository is a bad idea in the most casses, since it is hard to guess what set of data you will need.
I'm trying to make a UserType that hashes a value. The issue I'm having is getting access to the Salt that sits in the same table.
void IUserType.NullSafeSet(IDbCommand cmd, object value, int index)
{
object paramVal = DBNull.Value;
if (!String.IsNullOrEmpty((string)value))
{
paramVal = ComputeHash((string)value, saltBytes?);
}
IDataParameter parameter = (IDataParameter)cmd.Parameters[index];
parameter.Value = paramVal;
}
I am uncertain on how to reliably access a database column of the same table to get the salt that was set.
I could do something like this to access the salt column:
byte[] saltValueBeingInsertedIntoDB = (IDataParameter)cmd.Parameters[1].Value;
It just seems so fragile to access it via index, as the order could change. I'd love it if I could access it off of the column name, but the column name (SoureColumn) is never populated.
How can I reliably access the Salt that exists in cmd.Parameters? Or is there a better way? (I have full control to change whatever is needed, except the NHibernate version).
Note: If I'm setting the salt somewhere else, it may make sense for me to also hash the value in that place, rather than using a UserType.
NHiberate 2.1.2.4000
Fluent NHibernate 1.1.0.685
To solve my issue, I chose not to use the UserType.
Instead, I create a static instance of my Ciphering service on the object(s) that need it, and then use a helper property to get/set the encrypted value. This works great for me.
public class Consumer
{
static Consumer()
{
CipherConsumerSsnService = new CipherConsumerSsnService();
}
public static ICipherConsumerSsnService CipherConsumerSsnService { get; set; }
public virtual long ID { get; private set; }
public virtual byte[] SSN { get; protected set; }
public virtual string GetDecryptedSsnOrSetSsnValueAndEncryptIt
{
get
{
return SSN != null ? CipherConsumerSsnService.Decrypt(SSN) : null;
}
set
{
SSN = value != null ? CipherConsumerSsnService.Encrypt(value) : null;
}
}
}
Note that this example doesn't use a salt, but you should!
I have been working with NHibernate, using Fluent NHibernate for mapping. I solved a lot of issues, and started to think myself as experienced in nhibernate.
However, this error is quite strange.
This is my model:
public class MessageNew
{
public virtual int Id { get; set; }
public virtual string Content { get; set; }
public virtual string Subject { get; set; }
public virtual User User { get; set; }
public virtual bool IsSent { get; set; }
public virtual string AmazonMessageId { get; set; }
}
And my mapping
public class MessageNewMap : ClassMap<MessageNew>
{
public MessageNewMap()
{
Id(x => x.Id);
Map(x => x.Content).CustomSqlType("text");
Map(x => x.Subject);
Map(x => x.AmazonMessageId);
Map(x => x.IsSent);
References(x => x.User);
}
}
Here where exception occurs:
foreach (var userToSend in usersToSend)
{
string body = MailHelper.BuildSomeBody()
if (userToSend != CurrentUser)
{
MessageNew message = new MessageNew
{
User = userToSend,
IsSent = false,
Content = body,
Subject = subject
};
session.Save(message); // Exception thrown
}
}
The exception details:
NHibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: 1779, of entity: Models.MessageNew
at NHibernate.Engine.StatefulPersistenceContext.CheckUniqueness(EntityKey key, Object obj)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
Id generator is database driven auto-increment id generator. (not hilo or any other). NHibernate version is 3.2.0 .
I have tried overloading Equals and GetHashCode, no luck.
The UnitOfWork pattern I am using requires not to commit transaction or flush session inside foreach loop. NHibernate says there is another object with same id, but all i am doing is inserting a new object, which does not have any identifier at all.
I am using the same structure all over my project, and it works well everywhere but this. I am suspicious that it might be because of "Content" property, which is text and set to a large string.
What am i missing here? Or NHibernate is missing something?
Sometimes it happend when we assign the object to the same new object. So first check your model and viewmodel that they are not same.
I had similar problem. I went through a lot of discussions, tutorials and forums, but after writing some unit tests, I realized:
1) session.Contains method works with instances
2)session.Save/SaveorUpdate works with ID
This error shows you have another instances of object with same ID in session.So, contains return false because you are working on different instances and Save/SaveorUpdate throws an exception because there is another object with same ID in session.
I've solved my problem like this(my problem was in Job Entity):
Job lJob = lSession.Load<Job>(this.ID);
if(lJob.ID==this.ID)
lSession.Evict(lJob);
lSession.SaveOrUpdate(this);
I hope it helps you
You can use Evict() to evict an object from a session and then you can do whatever you want.
This error occurs when you have the same object in another session.
messagenew should implement Equals and GetHashCode
public class MessageNew
{
public virtual int Id { get; set; }
public override bool Equals(object obj)
{
var other = obj as MessageNew;
return (other != null) && (IsTransient ? ReferenceEquals(this, other) : Id == other.Id;
}
private int? _cachedHashcode; // because Hashcode should not change
public override int GetHashCode()
{
if (_cachedHashcode == null)
_cachedHashcode = IsTransient ? base.GetHashCode() : Id.GetHashCode();
return _cachedHashcode.Value;
}
public bool IsTransient { get { return Id == 0; } }
}
I read some NH code. It basically inserts the new instance into the database to get its id. Then it checks if the id generated by the database is actually unique. If not, you get this exception.
Your database is not generating unique ids. You most probably forgot to set it to an IDENTITY column.
OR the identity starts counting on 0 instead of 1.
That exception usually indicates that you have 2 separate instances of an object with the same identifier value which you are trying to manage over the same session.
You already have another instance of the entity with that id.
Two possible issues:
1 - Your comparison of the entity does not work. You could override equals as suggested or you could change your test case that you use prior to the save:
if (userToSend.Id != CurrentUser.Id)
2 - You are not generating a unique Id for your entity, you need to either assign an Id yourself, have NHibernate generate one or have your sql server do it for you. In your mapping it is implied that an Identity should be used (Fluents default) but have you set up the column in your database to be and Identity column?
My take: you are not declaring an Id generator. Therefore, as soon as you get two MessageNew instances in the session, they'll both have 0 as the Id.
maybe a bit late but hope this helps.
I had a similar problem when i was trying to save multiple instances of an object over the same session with an auto generated column on them. My solution was giving a diferent value and assign it mannually for each entity, so nhibernates doesn't recognize it as the same primary key for that entity.
[..]
};
session.Clear();
session.Save(message);
Try this, helped me.
Add below two lines before Session.Save or Session.SaveOrUpdate
Session.Clear();
Session.Flush();
This will clear all cached entities with the Session.
I have been spending a couple of days now to get to know the Fluent NHibernate automapping working model. It is quite nice, but I keep detecting new details missing from my schemas. Now I want to add extra properties to my classes, but not have them mapped to the database. A typical case is when I need extra properties with internal logic.
So I read the examples and scanned StackOverflow and found out that this was not another convention to be added, but rather a matter of inheriting the DefaultAutomappingConfiguration and override the ShouldMap method.
Fine, no problem, one minute later I had something like this:
public class CustomAutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Member member)
{
var explicitSkip = member.PropertyType.GetCustomAttributes(typeof(SkipMap), false).Length > 0;
if ((member.IsProperty && !member.CanWrite) || explicitSkip)
{
return false;
}
return base.ShouldMap(member);
}
}
/// <summary>
/// Don't map this property to database.
/// </summary>
public class SkipMap : Attribute
{
}
public class DemoClass
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual MyBitwiseEnum Status { get; set; }
public virtual bool IsValid
{
get
{
return (int)Status > 3;
}
}
[SkipMap]
public virtual bool IsBad
{
get
{
return MyBitwiseEnum.HasFlag(MyBitwiseEnum.Bad);
}
set
{
MyEnum = value ? MyBitwiseEnum | MyBitwiseEnum.Bad : MyBitwiseEnum ^ MyBitwiseEnum.Bad;
}
}
}
I know that my demo class is kind of stupid, but it will illustrate my point.
The idea is that I want to manually decide what properties to map to database.
The readonly property works fine because the ShouldMap method will look for property.CanWrite. But the custom attribute that definitely is set will not be detected. Why is that!?
In the convention methods I have used the same approach frequently and there it works fine. Why is the property not able to detect defined attributes here, when it obviously can in the convention setting. Is there a workaround?
have you added your new automapconvention to Automap?
AutoMap.AssemblyOf<>(new CustomAutomappingConfiguration())
Update: you are getting the skip attribute from Boolean class instead of the property
member.PropertyType.GetCustomAttributes(typeof(SkipMap), false)
should be
member.MemberInfo.GetCustomAttributes(typeof(SkipMap), false)
Just to be sure the custom attribute is applicable to properties, try adding [AttributeUsage(AttributeTargets.Property)] to your SkipMap class.
Another possibility is an attribute name clash with another attribute that applies to different targets. Try renaming the class to something like MyVerySpecialSkipMap and retest to verify you don't have an attribute clash. At the very least, write some simple reflection code to test for the SkipMap attribute outside the context of your application to ensure it can be found.
I'm experiencing an odd problem with FluentNHibernate: when I save my entity, one of the (reference) properties is not updated. Other properties, both fields and references, are updated, and the failing property is correctly mapped (retrieving entities works like a charm).
A (slightly simplified) description of what I'm doing:
Into my MVC action method, an InputModel is bound and set. It has a property for the TypeID, where I wish to set the Type of my entity (let's call the entity type Thing).
A new Thing object is created, and the simple properties of the InputModel is copied over. For a couple of complex properties, among them the Type property which isn't working and another property which is, the following is done:
2.1. The correct ThingType is fetched from the repository, based on the provided type id.
2.2. The type is set (using thing.Type = theType) on the new Thing object.
The Thing that I want to update is fetched from the repository, based on the id on the input model (not the same id as the TypeID).
All properties, complex and other, are copied over from the new thing (created by me) to the original one (fetched from db).
The original Thing is saved, using session.Save();.
As stated above, it's only one property that isn't working - other properties, following (as far as I can tell) the exact same pattern, work. I've also debugged and verified that the original Thing has the correct, updated Type when it is passed to session.Save().
I have no idea where to start troubleshooting this...
Update: The classes are plain POCOs:
public class Thing
{
public int ID { get; set; }
public string SomeSimpleProp { get; set; }
public ThingType Type { get; set; }
public OtherEntity OtherReference { get; set; }
}
public class ThingType
{
public int ID { get; set; }
public string Name { get; set; }
}
My exact mappings (except for the names of types and properties) are these:
// In ThingMap : ClassMap<Thing> constructor:
Id(t => t.ID).Column("ThingID");
Map(t => t.SomeSimpleProp);
References(t => t.Type).Column("ThingTypeID");
References(t => t.OtherReference).Column("OtherReferenceID");
// In ThingTypeMap : ClassMap<ThingType> constructor:
Id(t => t.ID).Column("ThingTypeID");
Map(t => t.Name);
As I said, OtherReference is updated correctly while Type is not. They are mapped identically, so I don't see how this could be a mapping error.
You should specify <many-to-one .... cascade="save-update"/> in order to update references.