I define a simple Bug Class:
using System;
namespace BugNS.Entities
{
class Bug
{
public virtual int Id { get; private set; }
public virtual int BugNumber { get; set; }
}
}
and a simple mapper class:
using System;
using FluentNHibernate.Mapping;
using BugNS.Entities;
namespace BugNS.Mappings
{
class BugMap : ClassMap<Bug>
{
public BugMap()
{
Id(b => b.Id);
Map(b => b.BugNumber);
}
}
}
and then just try to use it like:
using System;
using System.IO;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using BugNS.Entities;
namespace BugNS
{
class Program
{
private const string DbFile = "bugs.db";
static void Main()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
Bug b = new Bug { BugNumber = 121212 };
session.SaveOrUpdate(b);
transaction.Commit();
}
}
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile))
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Program>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private static void BuildSchema(Configuration config)
{
// delete the existing db on each run
if (File.Exists(DbFile))
File.Delete(DbFile);
// this NHibernate tool takes a configuration (with mapping info in)
// and exports a database schema from it
new SchemaExport(config)
.Create(false, true);
}
}
}
and I got the following error:
An unhandled exception of type
'NHibernate.MappingException' occurred
in NHibernate.dll
Additional information: No persister
for: BugNS.Entities.Bug
I am sorry, but I just started learning Fluent and NHibernate from the docs. It would be great if someone knows the solution to this issue, as I already spend many hours in vain.
Access level of the class needs to be public for Fluent to make use of it.
Try the following:
public class Bug
{
public virtual int Id { get; private set; }
public virtual int BugNumber { get; set; }
}
public class BugMap : ClassMap<Bug>
{
public BugMap()
{
Id(b => b.Id);
Map(b => b.BugNumber);
}
}
This is a gotcha for me as well when I started using Fluent.
One more thing that generally goes wrong is missing out virtual keyword, which is needed for NHibernate to make changes to those properties. Its nice that you made that part correct :)
Hope that helps.
Thanks
You need to make the Bug and BugMap classes public so that NHibernate/Fluent NHibernate can see them.
Related
I'm using WCF OData service as my application Data Provider.OData service expose a entity that I don't want to get whole entity,I create LINQ query to get projection from this Entity.
But i have error in OData Service.This is my code:
from n in NewsInfos
select new NewsInfos
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};
This is entire code:
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class NewsDataService : DataService<NewsODataModel>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
}
Yes, WCF Data Services and OData support projection. Projection is codified in the URL with the $select system query option, e.g.: http://services.odata.org/Experimental/OData/OData.svc/Products?$select=Name&$format=json. The LINQ Provider in the client bits enable this similarly to what you've shown in your example. Here is one such example:
using System;
using System.Data.Services.Client;
using System.Linq;
namespace Scratch
{
public class Program
{
public static void Main()
{
var context = new DataServiceContext(new Uri("http://services.odata.org/OData/OData.svc/"));
var categories = context.CreateQuery<Category>("Categories").Select(c => new { c.Name });
Console.WriteLine("context.Categories.Where(...): {0}", categories);
foreach (var category in categories)
{
Console.WriteLine(category.Name);
}
}
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
}
}
One thing to consider with projection is that the magic in our client-side bits frequently requires you to use anonymous objects (hence the new { c.Name }).
Your error may be unrelated; if you're still getting the error after reading this can you update your service to return verbose errors as per http://blogs.msdn.com/b/phaniraj/archive/2008/06/18/debugging-ado-net-data-services.aspx? My guess is that you may be missing the [DataServiceKey] attribute on NewsInfos.
Just return an anonymous object from your select and it should work.
from n in NewsInfos
select new
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};
In my quest for a version-wide database filter for an application, I have written the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations;
using FluentNHibernate.Mapping;
using MvcExtensions.Model;
using NHibernate;
namespace MvcExtensions.Services.Impl.FluentNHibernate
{
public interface IVersionAware
{
string Version { get; set; }
}
public class VersionFilter : FilterDefinition
{
const string FILTERNAME = "MyVersionFilter";
const string COLUMNNAME = "Version";
public VersionFilter()
{
this.WithName(FILTERNAME)
.WithCondition("Version = :"+COLUMNNAME)
.AddParameter(COLUMNNAME, NHibernateUtil.String );
}
public static void EnableVersionFilter(ISession session,string version)
{
session.EnableFilter(FILTERNAME).SetParameter(COLUMNNAME, version);
}
public static void DisableVersionFilter(ISession session)
{
session.DisableFilter(FILTERNAME);
}
}
public class VersionAwareOverride : IAutoMappingOverride<IVersionAware>
{
#region IAutoMappingOverride<IVersionAware> Members
public void Override(AutoMapping<IVersionAware> mapping)
{
mapping.ApplyFilter<VersionFilter>();
}
#endregion
}
}
But, since overrides do not work on interfaces, I am looking for a way to implement this.
Currently I'm using this (rather cumbersome) way for each class that implements the interface :
public class SomeVersionedEntity : IModelId, IVersionAware
{
public virtual int Id { get; set; }
public virtual string Version { get; set; }
}
public class SomeVersionedEntityOverride : IAutoMappingOverride<SomeVersionedEntity>
{
#region IAutoMappingOverride<SomeVersionedEntity> Members
public void Override(AutoMapping<SomeVersionedEntity> mapping)
{
mapping.ApplyFilter<VersionFilter>();
}
#endregion
}
I have been looking at IClassmap interfaces etc, but they do not seem to provide a way to access the ApplyFilter method, so I have not got a clue here...
Since I am probably not the first one who has this problem, I am quite sure that it should be possible; I am just not quite sure how this works..
EDIT :
I have gotten a bit closer to a generic solution:
This is the way I tried to solve it :
Using a generic class to implement alterations to classes implementing an interface :
public abstract class AutomappingInterfaceAlteration<I> : IAutoMappingAlteration
{
public void Alter(AutoPersistenceModel model)
{
model.OverrideAll(map =>
{
var recordType = map.GetType().GetGenericArguments().Single();
if (typeof(I).IsAssignableFrom(recordType))
{
this.GetType().GetMethod("overrideStuff").MakeGenericMethod(recordType).Invoke(this, new object[] { model });
}
});
}
public void overrideStuff<T>(AutoPersistenceModel pm) where T : I
{
pm.Override<T>( a => Override(a));
}
public abstract void Override<T>(AutoMapping<T> am) where T:I;
}
And a specific implementation :
public class VersionAwareAlteration : AutomappingInterfaceAlteration<IVersionAware>
{
public override void Override<T>(AutoMapping<T> am)
{
am.Map(x => x.Version).Column("VersionTest");
am.ApplyFilter<VersionFilter>();
}
}
Unfortunately I get the following error now :
[InvalidOperationException: Collection was modified; enumeration operation may not execute.]
System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) +51
System.Collections.Generic.Enumerator.MoveNextRare() +7661017
System.Collections.Generic.Enumerator.MoveNext() +61
System.Linq.WhereListIterator`1.MoveNext() +156
FluentNHibernate.Utils.CollectionExtensions.Each(IEnumerable`1 enumerable, Action`1 each) +239
FluentNHibernate.Automapping.AutoMapper.ApplyOverrides(Type classType, IList`1 mappedProperties, ClassMappingBase mapping) +345
FluentNHibernate.Automapping.AutoMapper.MergeMap(Type classType, ClassMappingBase mapping, IList`1 mappedProperties) +43
FluentNHibernate.Automapping.AutoMapper.Map(Type classType, List`1 types) +566
FluentNHibernate.Automapping.AutoPersistenceModel.AddMapping(Type type) +85
FluentNHibernate.Automapping.AutoPersistenceModel.CompileMappings() +746
EDIT 2 : I managed to get a bit further; I now invoke "Override" using reflection for each class that implements the interface :
public abstract class PersistenceOverride<I>
{
public void DoOverrides(AutoPersistenceModel model,IEnumerable<Type> Mytypes)
{
foreach(var t in Mytypes.Where(x=>typeof(I).IsAssignableFrom(x)))
ManualOverride(t,model);
}
private void ManualOverride(Type recordType,AutoPersistenceModel model)
{
var t_amt = typeof(AutoMapping<>).MakeGenericType(recordType);
var t_act = typeof(Action<>).MakeGenericType(t_amt);
var m = typeof(PersistenceOverride<I>)
.GetMethod("MyOverride")
.MakeGenericMethod(recordType)
.Invoke(this, null);
model.GetType().GetMethod("Override").MakeGenericMethod(recordType).Invoke(model, new object[] { m });
}
public abstract Action<AutoMapping<T>> MyOverride<T>() where T:I;
}
public class VersionAwareOverride : PersistenceOverride<IVersionAware>
{
public override Action<AutoMapping<T>> MyOverride<T>()
{
return am =>
{
am.Map(x => x.Version).Column(VersionFilter.COLUMNNAME);
am.ApplyFilter<VersionFilter>();
};
}
}
However, for one reason or another my generated hbm files do not contain any "filter" fields....
Maybe somebody could help me a bit further now ??
Apparently, there was a bug in the current version of fluent-nhibernate. The filters did not get copied when you use Automapper.Override<T> .
I forked the source, fixed the bug, tested, and I have now sent a pull request to the fluentnhib guys over at github.
For now, you can download the fixed sourcecode at http://github.com/ToJans/fluent-nhibernate/commit/29058de9b2bc3af85bc433aa6f71549f7b5d8e04
There is now also a complete blog post on how to do this :
http://www.corebvba.be/blog/post/How-to-override-interface-mappings-and-creata-a-generic-entity-version-filter-in-fluent-nhibernate.aspx
I'm trying to use the automap functionality in fluent to generate a
DDL for the following model and program, but somehow I keep getting
the error "Association references unmapped class: IRole" when I call
the GenerateSchemaCreationScript method in NHibernate. When I replace
the type of the ILists with the implementation of the interfaces (User
and Role) everything works fine. What am I doing wrong here? How can I
make fluent use the implemented versions of IUser and IRole as defined
in Unity?
public interface IRole
{
string Title { get; set; }
IList<IUser> Users { get; set; }
}
public interface IUser
{
string Email { get; set; }
IList<IRole> Roles { get; set; }
}
public class Role : IRole
{
public virtual string Title { get; set; }
public virtual IList<IUser> Users { get; set; }
}
public class User : IUser
{
public virtual string Email { get; set; }
public virtual IList<IRole> Roles { get; set; }
}
I use the following program to generate the DDL using the
GenerateSchemaCreationScript in NHibernate:
class Program
{
static void Main(string[] args)
{
var ddl = new NHibernateSessionManager();
ddl.BuildConfiguration();
}
}
public class NHibernateSessionManager
{
private ISessionFactory _sessionFactory;
private static IUnityContainer _container;
private static void InitContainer()
{
_container = new UnityContainer();
_container.RegisterType(typeof(IUser), typeof(User));
_container.RegisterType(typeof(IRole), typeof(Role));
}
public ISessionFactory BuildConfiguration()
{
InitContainer();
return
Fluently.Configure().Database(MsSqlConfiguration.MsSql2008
.ConnectionString("ConnectionString"))
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<IUser>()))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private void BuildSchema(Configuration cfg)
{
var ddl = cfg.GenerateSchemaCreationScript(new
NHibernate.Dialect.MsSql2008Dialect());
System.IO.File.WriteAllLines("Filename", ddl);
}
}
I am in the same situation as you. Having used the ClassMap before I know you can do this with Fluent but I had never used the AutoMapping feature before. I have successfully been able to do a one to one mapping with the AutoMapper using an IReferenceConvention (see previous SO post).
I have now hit the same problem as you where I have a one to many mapping which I am now having a problem with. There is an IHasManyConvention interface which I have started to look at but have had no luck as of yet.
Just because some thing is hard to do it doesn't make it wrong, mapping to interfaces defiantly has value and can easily be done in the raw nHibernate mapping files or by using Fluents ClassMap mapping files. I think once people start do more with AutoMapping feature there will be more blog posts.
EDIT
I have found an interim solution using an IAutoMappingOverride. Below is a rough example of what you need.
public class RoleAutoMappingOverride : IAutoMappingOverride<Role>
{
public void Override(AutoMapping<Role> mapping)
{
mapping.HasMany<User>( x => x.Users ).KeyColumn( "User_id" );
}
}
EDIT
A college of mine has worked out a better solution that uses conventions instead of the override. This covers how to do a single class but if you look at the SO post I mentioned before you can see how this could be made generic.
public class Foo : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
if (instance.ChildType == typeof(Role))
{
instance.Relationship.CustomClass<User>();
}
}
}
EDIT
I have now turned this and my other post into a blog post:
http://bronumski.blogspot.com/2011/01/making-fluent-nhibernate-automapper.html
You can't provide an interface as the type T in AssemblyOf<T>, you need to provide a concrete type. Or you could use the method that accepts an assemply:
.Mappings(m => m.AutoMappings.Add(
AutoMap.Assembly(myAssembly)))
Edit: The problem is that your classes contain collections of interface types instead of class type. I don't know if it's possible to automap interfaces in this manner. Also, I think there's rarely any value in using interfaces to specify domain objects.
I'm trying to get the AutoPersistence model to map several composite elements. However, it seems that either I end up mapping it as an entity, dropping down to manual mapping or it just doesn't plain work. Here's some code that demonstrates my problem:
using System;
using System.Collections.Generic;
using FluentNHibernate.AutoMap;
using FluentNHibernate.Cfg;
using FluentNHibernate.Conventions.Helpers;
using NHibernate.Cfg;
namespace Scanner {
public class Root {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Component> Components { get; set; }
}
public class Component {
public string Name { get; set; }
}
class Example {
public void DoesntGetComponents()
{
Configuration configuration = new Configuration();
configuration.SetProperty("ConnectionString", "");
configuration.SetProperty("dialect", "NHibernate.Dialect.MsSql2005Dialect");
var config = Fluently.Configure(configuration)
.Mappings(m => m.AutoMappings.Add(AutoMapping))
.BuildConfiguration();
var sql2005 = new NHibernate.Dialect.MsSql2005Dialect();
foreach (var line in config.GenerateSchemaCreationScript(sql2005))
{
Console.WriteLine(line);
}
}
static AutoPersistenceModel AutoMapping() {
AutoPersistenceModel model = new AutoPersistenceModel();
return model
.AddEntityAssembly(typeof(Root).Assembly)
.WithSetup(e => e.IsComponentType = t => t == typeof(Component))
.Where(t => t == typeof(Root))
.MergeWithAutoMapsFromAssemblyOf<Root>()
.ConventionDiscovery.Add(ForeignKey.Format((p, t) => (p == null ? t.Name : p.Name) + "Id"))
.ConventionDiscovery.Add(Table.Is(t => t.EntityType.Name))
;
}
}
}
(Sorry it's so long, but it's the minimal code required to demonstrate the problem. This particular version of the code fails to register the component type at all.
So, what am I doing wrong?
It seems that the component in itself is not the problem, but the mapping of a collection of components. If you would map the component directly onto the Root class, this would not be any problem.
A possible workaround is making the Component class an entity (adding an ID) and overriding the mapping of Components to cascade + auto delete orphans:
AutoPersistenceModel
.ForTypesThatDeriveFrom<Root>(map => map.HasMany(root => root.Components).Cascade.AllDeleteOrphan())
Question says it all really, the default is for it to map as a string but I need it to map as an int.
I'm currently using PersistenceModel for setting my conventions if that makes any difference.
Update
Found that getting onto the latest version of the code from the trunk resolved my woes.
The way to define this convention changed sometimes ago, it's now :
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
So, as mentioned, getting the latest version of Fluent NHibernate off the trunk got me to where I needed to be. An example mapping for an enum with the latest code is:
Map(quote => quote.Status).CustomTypeIs(typeof(QuoteStatus));
The custom type forces it to be handled as an instance of the enum rather than using the GenericEnumMapper<TEnum>.
I'm actually considering submitting a patch to be able to change between a enum mapper that persists a string and one that persists an int as that seems like something you should be able to set as a convention.
This popped up on my recent activity and things have changed in the newer versions of Fluent NHibernate to make this easier.
To make all enums be mapped as integers you can now create a convention like so:
public class EnumConvention : IUserTypeConvention
{
public bool Accept(IProperty target)
{
return target.PropertyType.IsEnum;
}
public void Apply(IProperty target)
{
target.CustomTypeIs(target.PropertyType);
}
public bool Accept(Type type)
{
return type.IsEnum;
}
}
Then your mapping only has to be:
Map(quote => quote.Status);
You add the convention to your Fluent NHibernate mapping like so;
Fluently.Configure(nHibConfig)
.Mappings(mappingConfiguration =>
{
mappingConfiguration.FluentMappings
.ConventionDiscovery.AddFromAssemblyOf<EnumConvention>();
})
./* other configuration */
Don't forget about nullable enums (like ExampleEnum? ExampleProperty)! They need to be checked separately. This is how it's done with the new FNH style configuration:
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum ||
(x.Property.PropertyType.IsGenericType &&
x.Property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
x.Property.PropertyType.GetGenericArguments()[0].IsEnum)
);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
this is how I've mapped a enum property with an int value:
Map(x => x.Status).CustomType(typeof(Int32));
works for me!
For those using Fluent NHibernate with Automapping (and potentially an IoC container):
The IUserTypeConvention is as #Julien's answer above: https://stackoverflow.com/a/1706462/878612
public class EnumConvention : IUserTypeConvention
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Property.PropertyType.IsEnum);
}
public void Apply(IPropertyInstance target)
{
target.CustomType(target.Property.PropertyType);
}
}
The Fluent NHibernate Automapping configuration could be configured like this:
protected virtual ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SetupDatabase)
.Mappings(mappingConfiguration =>
{
mappingConfiguration.AutoMappings
.Add(CreateAutomappings);
}
).BuildSessionFactory();
}
protected virtual IPersistenceConfigurer SetupDatabase()
{
return MsSqlConfiguration.MsSql2008.UseOuterJoin()
.ConnectionString(x =>
x.FromConnectionStringWithKey("AppDatabase")) // In Web.config
.ShowSql();
}
protected static AutoPersistenceModel CreateAutomappings()
{
return AutoMap.AssemblyOf<ClassInAnAssemblyToBeMapped>(
new EntityAutomapConfiguration())
.Conventions.Setup(c =>
{
// Other IUserTypeConvention classes here
c.Add<EnumConvention>();
});
}
*Then the CreateSessionFactory can be utilized in an IoC such as Castle Windsor (using a PersistenceFacility and installer) easily. *
Kernel.Register(
Component.For<ISessionFactory>()
.UsingFactoryMethod(() => CreateSessionFactory()),
Component.For<ISession>()
.UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession())
.LifestylePerWebRequest()
);
You could create an NHibernate IUserType, and specify it using CustomTypeIs<T>() on the property map.
You should keep the values as int / tinyint in your DB Table. For mapping your enum you need to specify mapping correctly. Please see below mapping and enum sample,
Mapping Class
public class TransactionMap : ClassMap Transaction
{
public TransactionMap()
{
//Other mappings
.....
//Mapping for enum
Map(x => x.Status, "Status").CustomType();
Table("Transaction");
}
}
Enum
public enum TransactionStatus
{
Waiting = 1,
Processed = 2,
RolledBack = 3,
Blocked = 4,
Refunded = 5,
AlreadyProcessed = 6,
}