I was just wandering if it would be possible to use the Fluent NHibernate to auto map a .Net TcpClient object?
I have a class that has a TcpClient property which I would like to map.
I tried creating a custom class inheriting from TcpClient called tTcpClient and adding an Id Property with a getter/setter; however, it was still looking for the Id field for the base class.
Anyone have any ideas if it is possible, or will I need to create my own xml mapping for the TcpClient?
I was sort of hoping to be able to save the object to easily recreate it on reloading the application and to bind the properties of the TcpClient object to the PropertiesGrid and allowing configuration through that rather easy.
Thanks.
NHibernate does not know how to deal with complex types like TcpClient out of the box. But it lets you provide your own loading and storing code. You can use IUserType:
public class TcpClientMapper : IUserType {
public SqlType[] SqlTypes {
get {
return new[] {
new SqlType(DbType.String),
new SqlType(DbType.Int32)
};
}
}
public Object NullSafeGet(IDataReader rs, String[] names, ...) {
String address = NHibernateUtil.String.NullSafeGet(rs, names[0]);
Int32 port = NHibernateUtil.Int32.NullSafeGet(rs, names[1]);
return new TcpClient(address, port);
}
public void NullSafeSet(IDbCommand cmd, Object value, Int32 index) {
TcpClient tcpClient = value as TcpClient;
if(tcpClient == null) {
NHibernateUtil.String.NullSafeSet(cmd, null, index);
NHibernateUtil.Int32.NullSafeSet(cmd, null, index + 1);
} else {
EndPoint red = tcpClient.Client.RemoteEndPoint;
IPEndPoint endpoint = ((IPEndPoint)red);
NHibernateUtil.String.Set(cmd, endpoint.Address.ToString(), index);
NHibernateUtil.Int32.Set(cmd, endpoint.Port, index + 1);
}
}
public Type ReturnedType {
get { return typeof(TcpClient); }
}
// TODO: implement other methods
}
And map it like this in hbm:
<property name="_tcpClient" type="MyNamespace.TcpClientMapper, MyAssembly">
<column name="Address" /> <!-- NullSafeGet/Set index == 0 -->
<column name="Port" /> <!-- NullSafeGet/Set index == 1 -->
</property>
Or use fluent UserTypeConvention:
public class TcpClientUserTypeConvention : UserTypeConvention<TcpClientMapper> {
}
Nathan,
Have you had a look at this project?
http://automapper.org/
Cheers
Related
I have a model like this:
public class Order
{
public virtual int OrderType { get; set; }
}
(lots of other properties omitted of course) which maps directly to an int type in the DB.
The thing is, the numeric order type is meaningless to my application. There are single-letter codes that the user sees which denote the order type. So, I could do something like this:
public class Order
{
public virtual int OrderTypeIgnored { get; set; }
public virtual char OrderType
{
get
{
return translateForward(OrderTypeIgnored);
}
set(char val)
{
OrderTypeIgnored = translateBackward(val);
}
}
}
(lots of air code/pseudocode there, I'm relatively new to C#) and just map the OrderTypeIgnored property. But is there a cleaner way to do this? Perhaps somehow overriding the getter and setter on the mapped property itself?
A few notes: The values are static enough that embedding the translation in the code is not a problem. No, there's no LOV table, and no, I don't have control over the database structure.
Sorry if there are answers for this, but searching for things like "mapping" and "translation" don't really get me the results I'm looking for, obviously.
You could create a public char property that uses a private int field and only map the field.
Model:
public class Order
{
private int _orderType;
public virtual char OrderType
{
get
{
return TranslateForward(_orderType);
}
set
{
_orderType = TranslateBackward(value);
}
}
}
Mapping:
<property name="_orderType" access="field" />
If you don't want to map the field directly (because you use a compile-safe mapping) you can map the public property using the access strategy "field", a naming strategy like "camelcase-underscore" and explicitly specify the "Int32" type.
you can always use enums for this kind of situation.
You can define it like this:
namespace MyApp.Domain
{
using System.ComponentModel;
public enum OrderType : short
{
[Description("Order Suspended")]
Suspended = 1,
[Description("Order Delivered")]
Delivered = 2,
[Description("Order New")]
Inserted = 3
}
}
and map it this way:
<property name="Type" type="MyApp.Domain.OrderType, MyApp.Domain" >
<column name="Type" not-null="true"/>
</property>
so you can write your QueryOver in a simple way like this:
var orders = this.Session.QueryOver<MyApp.Domain.Orders>()
.Where(x => x.Type == MyApp.Domain.OrderType.Inserted)
.List();
I am trying to load the domain class by deserializing an xml file. So I have used System.Collections.Generic.List in the domain class. But when I try to save the object using Session object then it is failing with exception "Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericBag1[MyFirstMapTest.Class5]' to type 'System.Collections.Generic.List1[MyFirstMapTest.Class5]'." This issue was posted in some of the previous discussion and the answer was to use use IList instead of List(Unable to cast object of type NHibernate.Collection.Generic.PersistentGenericBag to List)
But, If I use IList then I am unable to Deserialize the xml in to Domain class.
XmlTextReader xtr = new XmlTextReader(#"C:\Temp\SampleInput.xml");
XmlSerializer serializer = new XmlSerializer(objClass5.GetType());
objClass5 = (MyFirstMapTest.Class5)serializer.Deserialize(xtr);
session.Save(objClass5);
It is throwing the below error
"Cannot serialize member xxxxx of type System.Collections.Generic.IList`1[[xxxxxxxxxx, Examples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface."
I have tried to use PersistentGenericBag instead of List but PersistentGenericBag is not serializable. So Deserialization is not working.
How can I resolve this issue? Thank you for looking at this issue.
You may try to use backing field for NHibernte binding and property for Serialization, where property will be of type List, while backing field - IList.
Edit
fluent mapping may look like this:
public class HierarchyLevelMap : IAutoMappingOverride<HierarchyLevel>
{
public void Override(AutoMapping<HierarchyLevel> mapping)
{
mapping.HasMany(x => x.StructuralUnits)
.Access.ReadOnlyPropertyThroughCamelCaseField();
}
}
entity:
public class HierarchyLevel : IEntity
{
private readonly IList<StructuralUnit> structuralUnits = new List<StructuralUnit>();
public virtual List<StructuralUnit> StructuralUnits
{
get { return structuralUnits; }
set { structuralUnits = value; }
}
}
You can create two properties like this in your class:
public class Sample
{
private IList<Sample> _list;
[XmlIgnoreAttribute]
public virtual IList<Sample> List
{
get
{
return _list;
}
set
{
_list = value;
}
}
public virtual List<Sample> List
{
get
{
return (List<Sample>)_list;
}
set
{
_list = value;
}
}
}
And you only map your IList Property.
I'm using NHibernate on a legacy database with Oracle 8i client. I can perform Get and Delete on the database table, but can't save or update entries. The exception is as follow, the sqlString consists of question marks and I don't know why.
Nhibernate.Exceptions.GenericADOException:
{"could not update: [MIAP.Domain.Entities.MITFORG#3][SQL: UPDATE MITFORG SET TREELEVEL = ?, PARTENTID = ?, FORGNAME = ?, FORGINFO = ?, ACTIVE = ?, MUTATOR = ?, INPDATETIME = ?, UPDDATETIME = ? WHERE FORGID = ?]"}
Here are my entity class and mapping:
public class MITFORG {
private long fORGID;
private long? tREELEVEL;
private long? pARTENTID;
private string fORGNAME;
private string fORGINFO;
private string aCTIVE;
private long? mUTATOR;
private DateTime? iNPDATETIME;
private DateTime? uPDDATETIME;
public MITFORG() { }
public virtual long FORGID {
get {
return this.fORGID;
}
set {
this.fORGID = value;
}
}
public virtual long? TREELEVEL
{
get {
return this.tREELEVEL;
}
set {
this.tREELEVEL = value;
}
}
public virtual long? PARTENTID
{
get {
return this.pARTENTID;
}
set {
this.pARTENTID = value;
}
}
public virtual string FORGNAME {
get {
return this.fORGNAME;
}
set {
this.fORGNAME = value;
}
}
public virtual string FORGINFO {
get {
return this.fORGINFO;
}
set {
this.fORGINFO = value;
}
}
public virtual string ACTIVE {
get {
return this.aCTIVE;
}
set {
this.aCTIVE = value;
}
}
public virtual long? MUTATOR
{
get {
return this.mUTATOR;
}
set {
this.mUTATOR = value;
}
}
public virtual DateTime? INPDATETIME
{
get {
return this.iNPDATETIME;
}
set {
this.iNPDATETIME = value;
}
}
public virtual DateTime? UPDDATETIME
{
get {
return this.uPDDATETIME;
}
set {
this.uPDDATETIME = value;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping assembly="MIAP.Domain" namespace="MIAP.Domain.Entities" xmlns="urn:nhibernate-mapping-2.2">
<class name="MITFORG" table="MITFORG" lazy="true" >
<id name="FORGID">
<generator class="assigned" />
</id>
<property name="TREELEVEL"></property>
<property name="PARTENTID"></property>
<property name="FORGNAME"></property>
<property name="FORGINFO"></property>
<property name="ACTIVE"></property>
<property name="MUTATOR"></property>
<property name="INPDATETIME"></property>
<property name="UPDDATETIME"></property>
</class>
</hibernate-mapping>
I've checked property names and table column names. Since the FORGID is assigned by the application, I changed the generator class to "assigned". It doesn't work with "identity" either. Could someone please point me the direction to debug this?
Edited: Code to save entries
Dim mitforgRepository As New MITFORGRepository
Dim mitforg As MITFORG = mitforgRepository.GetById(3)
mitforg.FORGINFO = "T"
mitforg.ACTIVE = "Y"
mitforg.FORGINFO = "T"
mitforg.INPDATETIME = Now
mitforg.MUTATOR = 324
mitforg.PARTENTID = 335
mitforg.TREELEVEL = 1
mitforg.UPDDATETIME = Now
mitforgRepository .Save(mitforg)
And here is the Repository class:
using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using MIAP.Domain.Entities;
namespace MIAP.Domain.Repositories
{
public class MITFORGRepository : IRepository<MITFORG, Int64?>
{
private static ISession GetSession()
{
return SessionProvider.SessionFactory.OpenSession();
}
public MITFORG GetById(Int64? id)
{
using (ISession session = GetSession())
{
return session.Get<MITFORG>(id);
}
}
public void Save(MITFORG saveObj)
{
using (ISession session = GetSession())
{
using (ITransaction trans = session.BeginTransaction())
{
session.SaveOrUpdate(saveObj);
trans.Commit();
}
}
}
public void Delete(MITFORG delObj)
{
using (ISession session = GetSession())
{
using (ITransaction trans = session.BeginTransaction())
{
session.Delete(delObj);
trans.Commit();
}
}
}
}
}
The InnerException is System.Data.OracleClient.OracleException, ORA-12571
And here's the stack trace:
於 System.Data.OracleClient.OracleConnection.CheckError(OciErrorHandle errorHandle, Int32 rc)
於 System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, Boolean needRowid, OciRowidDescriptor& rowidDescriptor, ArrayList& resultParameterOrdinals)
於 System.Data.OracleClient.OracleCommand.ExecuteNonQueryInternal(Boolean needRowid, OciRowidDescriptor& rowidDescriptor)
於 System.Data.OracleClient.OracleCommand.ExecuteNonQuery()
於 NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) 於 c:\Users\oskar.berggren\Documents\Projects\nhibernate-core-3\src\NHibernate\AdoNet\AbstractBatcher.cs: 行 203
於 NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) 於 c:\Users\oskar.berggren\Documents\Projects\nhibernate-core-3\src\NHibernate\AdoNet\NonBatchingBatcher.cs: 行 40
於 NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) 於 c:\Users\oskar.berggren\Documents\Projects\nhibernate-core-3\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs: 行 2799
If you're using assigned id's you can't use SaveOrUpdate as NHibernate won't know whether it's a new instance (ie. to Save/do an insert) or an existing instance(ie. to Update).
You need to then be explicit about whether you're inserting a new entity (session.Save) or updating an existing entity (session.Update).
From the looks of it, you want to create a new object and save it into the database but what you are doing is loading the object using the Nhibernate Session and then updating the properties of it.. what this tells the Nhibernate session is that you have an object associated in the db with the given id and now you want to update certain properties when infact you want an insert statement to run.
Thus the right way is to create a new MitForg object and then call Session.SaveOrUpdate() on it and Nhibernate should take care of the insertion thereafter.
You could also try using Session.Merge().
Let me know if this works out for you..
Turns out it's a Unicode to ASCII conversion problem. I followed the solution in the links below. What's needed is adding a type attribute to all string type properties in the mapping file like this:
<property name="FORGNAME" type="AnsiString" ></property>
Recently my colleague also encountered some kind of Unicode/ASCII conversion problem but I never thought it would have been the answer. The exception message is just misleading...
Thank Martin for the inspiring suggestion!
NHibernate and The Case of the Crappy Oracle
NHibernate and ORA-12571 Errors
I am looking to persist user preferences into a collection of name value pairs, where the value may be an int, bool, or string.
There are a few ways to skin this cat, but the most convenient method I can think of is something like this:
public class User
{
public virtual IDictionary<string, object> Preferences { get; set; }
}
with its usage as:
user.Preferences["preference1"] = "some value";
user.Preferences["preference2"] = 10;
user.Preferences["preference3"] = true;
var pref = (int)user.Preferences["preference2"];
I'm not sure how to map this in Fluent NHibernate, though I do think it is possible.
Generally, you would map a simpler Dictionary<string, string> as:
HasMany(x => x.Preferences)
.Table("Preferences")
.AsMap("preferenceName")
.Element("preferenceValue");
But with a type of 'object', NHibernate doesn't know how to deal with it. I imagine a custom UserType could be created that breaks an 'object' down to a string representing its Type and a string representing the value. We would have a table that looks kind of like this:
Table Preferences
userId (int)
preferenceName (varchar)
preferenceValue (varchar)
preferenceValueType (varchar)
and the hibernate mapping would like this:
<map name="Preferences" table="Preferences">
<key column="userId"></key>
<index column="preferenceName" type="String" />
<element type="ObjectAsStringUserType, Assembly">
<column name="preferenceValue" />
<column name="preferenceValueType"/>
</element>
</map>
I'm not sure how you would map this in Fluent NHibernate.
Maybe there's a better way to do this, or maybe I should just suck it up and use IDictionary<string, string>. Any ideas?
i would say IDictionary<string,string> would be a lot easier. However here's the code
HasMany(u => u.Preferences)
.Table("Preferences")
.AsMap("preferenceName")
.Element("preferenceType", e => e.Column("preferenceValue").Type<ObjAsStringUserType>());
class ObjAsStringUserType : ImmutableUserType
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var type = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
var value = (string)NHibernateUtil.String.NullSafeGet(rs, names[1]);
switch (type)
{
case "boolean":
return bool.Parse(value);
...
default:
return null;
break;
}
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
var type = value.GetType().Name;
var valuestring = value.ToString(CultureInfo.InvariantCulture);
NHibernateUtil.String.NullSafeSet(cmd, type, index);
NHibernateUtil.String.NullSafeSet(cmd, valuestring, index + 1);
}
public override Type ReturnedType
{
get { return typeof(object); }
}
public override SqlType[] SqlTypes
{
get { return new []
{
SqlTypeFactory.GetString(length: 255), // preferenceType
SqlTypeFactory.GetString(length: 255), // preferenceValue
};
}
}
}
Given this XML configuration (which works)
<component type="X.Y.Z.ActivityService, X.Y.Z.Services" id="X.Y.Z.ActivityService" lifestyle="transient">
<parameters>
<Listeners>
<array>
<item>${DefaultActivityListener}</item>
</array>
</Listeners>
</parameters>
</component>
<component type="X.Y.Z.DefaultActivityListener, X.Y.Z.Services" id="DefaultActivityListener" lifestyle="transient" />
I have converted to use the fluent API as below (which doesn't work):
Container.Register(
Component.For<X.Y.Z.ActivityService>()
.ServiceOverrides(
ServiceOverride.ForKey("Listeners").Eq(typeof(X.Y.Z.DefaultActivityListener).Name))
.LifeStyle.Transient
);
Container.Register(
Component.For<X.Y.Z.DefaultActivityListener>()
.Named("DefaultActivityListener")
.LifeStyle.Transient
);
When I now attempt to resolve an instance of X.Y.Z.ActivityService Windsor throws a NotImplementedException in Castle.MicroKernel.SubSystems.Conversion.ArrayConverter.PerformConversion(String, Type).
The implementation of the PerformConversion method is:
public override object PerformConversion(String value, Type targetType)
{
throw new NotImplementedException();
}
I should add that if I remove the ServiceOverrides call, all behaves as expected. So there is specifically something wrong in the way I am wiring up the Listeners parameter. Listeners by the way is a property as opposed to a constructor parameter.
Seeing as the XML config works as expected how do I best use the fluent API (short of implementing the PerformConversion method) in order to achieve the same result?
I am using Release 2.0.
EDIT
I will extend the question to how would you achieve this configuration in code, with or without use of the fluent API.
UPDATE
It appears the problem occurs if you attempt to assign a single element to an array property. Unit tests provided below to illustrate issue.
namespace Components
{
public class A
{
public I[] I { get; set; }
}
public interface I
{
string Name { get; }
}
public class B : I
{
public string Name { get { return "B"; } }
}
public class C : I
{
public string Name { get { return "C"; } }
}
}
[TestMethod]
public void ArrayPropertyTestApi()
{
//PASSES
using (Castle.Windsor.WindsorContainer container = new Castle.Windsor.WindsorContainer())
{
container.Register(Component.For<Components.A>().ServiceOverrides(ServiceOverride.ForKey("I").Eq(typeof(Components.B).FullName, typeof(Components.C).FullName)));
container.Register(Component.For<Components.B>());
container.Register(Component.For<Components.C>());
Components.A svc = container.Resolve<Components.A>();
Assert.IsTrue(svc.I.Length == 2);
Assert.IsTrue(svc.I[0].Name == "B");
Assert.IsTrue(svc.I[1].Name == "C");
}
}
[TestMethod]
public void ArrayPropertyTestApi2()
{
//FAILS
using (Castle.Windsor.WindsorContainer container = new Castle.Windsor.WindsorContainer())
{
container.Register(Component.For<Components.A>().ServiceOverrides(ServiceOverride.ForKey("I").Eq(typeof(Components.B).FullName)));
container.Register(Component.For<Components.B>());
container.Register(Component.For<Components.C>());
Components.A svc = container.Resolve<Components.A>(); //Throws NotImplementedException
Assert.IsTrue(svc.I.Length == 1);
Assert.IsTrue(svc.I[0].Name == "B");
}
}
Question still stands.
Thanks.
[TestFixture]
public class WindsorTests {
[Test]
public void ArrayConfig() {
var container = new WindsorContainer();
container.Register(Component.For<Listener>().Named("listener"));
container.Register(Component.For<ActivityService>()
.ServiceOverrides(ServiceOverride.ForKey("listeners").Eq(new[] {"listener"})));
var service = container.Resolve<ActivityService>();
Assert.AreEqual(1, service.Listeners.Length);
}
}
public class Listener {}
public class ActivityService {
public Listener[] Listeners { get; set; }
}
The key part here is the new[] {"listener"}. The MicroKernel needs to know that the parameter listeners is an array, if you pass just "listener" it assumes that the parameter is scalar and throws because it can't convert a scalar to an array.