简体   繁体   中英

NHibernate 3.2 mapping by code ignores my IUserType

I have a LocalizedString classed used to store localization of a single value. The concept is loosely based on a post by Fabio Maulo .

I'm using the new Mapping-By-Code concept in NHibernate 3.2, but it seem to ignore the IUserType implementation, because when it generate the SQL, it create a column with a different name and with the default string NVARCHAR(255) type.

I'm trying to map this simple class

public class Region : Entity
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Region"/> class.
    /// </summary>
    public Region()
    {
    }

    /// <summary>
    /// Gets or sets the localized name of the <see cref="Region"/>.
    /// </summary>
    public virtual LocalizedString Name { get; set; }
}

The resulting SQL is

create table Regions (RegionId INT not null, Item NVARCHAR(255) not null, primary key (RegionId))

The Item column here should be called Name and it should be of type XML. I presume the column name come from the name of the indexer of the LocalizedString .

This is my NHibernate configuration (its not complete, I'm in the process of building the convention)

private static Configuration CreateNHibernateConfiguration()
{
    var cfg = new Configuration();
    cfg.Proxy(p => p.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>())
        .DataBaseIntegration(db =>
        {
            db.ConnectionStringName = "***";
            db.Dialect<MsSql2008Dialect>();
            db.BatchSize = 500;
        });

    var mapper = new ConventionModelMapper();
    var baseEntityType = typeof(Entity);
    mapper.IsEntity((t, declared) => baseEntityType.IsAssignableFrom(t) && baseEntityType != t && !t.IsInterface);
    mapper.IsRootEntity((t, declared) => baseEntityType.Equals(t.BaseType));

    mapper.BeforeMapClass += (mi, t, map) =>
            {
                map.Table(Inflector.MakePlural(t.Name));
                map.Id(x =>
                    {
                        x.Column(t.Name + "Id");
                    });
            };

    mapper.BeforeMapManyToOne += (insp, prop, map) =>
            {
                map.Column(prop.LocalMember.GetPropertyOrFieldType().Name + "Id");
                map.Cascade(Cascade.Persist);
            };

    mapper.BeforeMapBag += (insp, prop, map) =>
            {
                map.Key(km => km.Column(prop.GetContainerEntity(insp).Name + "Id"));
                map.Cascade(Cascade.All);
            };

    mapper.BeforeMapProperty += (insp, prop, map) =>
            {
                map.NotNullable(true);
            };

    var exportedTypes = baseEntityType.Assembly.GetExportedTypes();
    mapper.AddMappings(exportedTypes.Where(t => t.Namespace.EndsWith("Mappings", StringComparison.Ordinal)));

    var mapping = mapper.CompileMappingFor(exportedTypes.Where(t => t.Namespace.EndsWith("Data", StringComparison.Ordinal)));

    cfg.AddDeserializedMapping(mapping, "MyModel");
    SchemaMetadataUpdater.QuoteTableAndColumns(cfg);

    return cfg;
}

This is the IUserType definition of my LocalizedString class:

/// <summary>
/// Defines a string that can have a different value in multiple cultures.
/// </summary>
public sealed partial class LocalizedString : IUserType
{
    object IUserType.Assemble(object cached, object owner)
    {
        var value = cached as string;
        if (value != null)
        {
            return LocalizedString.Parse(value);
        }

        return null;
    }

    object IUserType.DeepCopy(object value)
    {
        var toCopy = value as LocalizedString;
        if (toCopy == null)
        {
            return null;
        }

        var localizedString = new LocalizedString();
        foreach (var localizedValue in toCopy.localizedValues)
        {
            localizedString.localizedValues.Add(localizedValue.Key, localizedValue.Value);
        }

        return localizedString;
    }

    object IUserType.Disassemble(object value)
    {
        var localizedString = value as LocalizedString;
        if (localizedString != null)
        {
            return localizedString.ToXml();
        }

        return null;
    }

    bool IUserType.Equals(object x, object y)
    {
        if (x == null && y == null)
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        var localizedStringX = (LocalizedString)x;
        var localizedStringY = (LocalizedString)y;

        if (localizedStringX.localizedValues.Count() != localizedStringY.localizedValues.Count())
        {
            return false;
        }

        foreach (var value in localizedStringX.localizedValues)
        {
            if (!localizedStringY.localizedValues.ContainsKey(value.Key) || localizedStringY.localizedValues[value.Key] == value.Value)
            {
                return false;
            }
        }

        return true;
    }

    int IUserType.GetHashCode(object x)
    {
        if (x == null)
        {
            throw new ArgumentNullException("x");
        }

        return x.GetHashCode();
    }

    bool IUserType.IsMutable
    {
        get { return true; }
    }

    object IUserType.NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
    {
        if (rs == null)
        {
            throw new ArgumentNullException("rs");
        }

        if (names == null)
        {
            throw new ArgumentNullException("names");
        }

        if (names.Length != 1)
        {
            throw new InvalidOperationException("names array has more than one element. can't handle this!");
        }

        var val = rs[names[0]] as string;

        if (val != null)
        {
            return LocalizedString.Parse(val);
        }

        return null;
    }

    void IUserType.NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
    {
        if (cmd == null)
        {
            throw new ArgumentNullException("cmd");
        }

        var parameter = (DbParameter)cmd.Parameters[index];

        var localizedString = value as LocalizedString;
        if (localizedString == null)
        {
            parameter.Value = DBNull.Value;
        }
        else
        {
            parameter.Value = localizedString.ToXml();
        }
    }

    object IUserType.Replace(object original, object target, object owner)
    {
        throw new NotImplementedException();
    }

    Type IUserType.ReturnedType
    {
        get { return typeof(LocalizedString); }
    }

    NHibernate.SqlTypes.SqlType[] IUserType.SqlTypes
    {
        get { return new[] { new XmlSqlType() }; }
    }
}

You shouldn't use the IUserType in your domain model.

The IUserType interface should actually be called something like IUserTypeMapper , and you have to specify it explicitly in your mapping.

I suggest that you re-read that post.


Update: try this to map your type by convention:

mapper.BeforeMapProperty +=
    (insp, prop, map) =>
    {
        if (/*determine if this is member should be mapped as LocalizedString*/)
            map.Type<LocalizedString>();
    };

Of course the "determine if..." part would be something you determine, like the property name starting with "Localized", a custom attribute, or anything you want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM