Here is the basic situation. In our application, we have Roles and Permissions. There is a many-to-many relationship between roles and permission. The catch is, that a user can be assigned roles directly, or they can have a role that they have acquired by paying for a service. To make things more interesting, if the user cancels the paid service, or it expires, then they should no longer have that role. My issue is that I would like to have a (read-only) collection of the Effective Permissions on any given user, and I'd like to be able to query against it. Some mapping details:
public ApplicationPermissionMap()
{
Table("CLSYS_ApplicationPermissions");
Id(ap => ap.ID)
.Column("ApplicationPermissionID")
.GeneratedBy.Assigned();
//...
}
public ApplicationRoleMap()
{
Table("CLSYS_ApplicationRole");
Id(role => role.ID)
.Column("ApplicationRoleID");
HasManyToMany(r => r.Permissions)
.Table("CLSYS_ApplicationRolePermissions")
.ParentKeyColumn("ApplicationRoleID")
.ChildKeyColumn("PermissionID")
.Cascade.None();
//...
}
public PaidServiceMap()
{
Table("ECOM_PaidService");
Id(ps => ps.ID)
.Column("PaidServiceID");
Component(ps => ps.Status, statusMapping =>
{
statusMapping.Map(status => status.ID)
.Column("StatusID")
.Not.Nullable();
});
HasManyToMany(ps => ps.ApplicationRoles)
.Table("ECOM_PaidServiceRoles")
.ParentKeyColumn("PaidServiceID")
.ChildKeyColumn("RoleID")
.Cascade.None();
//....
}
public UserMap()
{
Table("CLSYS_User");
Id(user => user.ID)
.Column("UserID");
HasManyToMany(user => user.Roles)
.Table("CLSYS_User_ApplicationRoles")
.ParentKeyColumn("UserID")
.ChildKeyColumn("RoleID")
.Cascade.None();
//...
}
What I need is something like this (an addition to UserMap above)
public UserMap()
{
//...
HasMany(user => user.EffectivePermissions)
//Union ( user -> ApplicationRoles -> permissions)
//Union ( user -> PaidServices [with active status] -> permissions)
//Distinct
}
And I would like to be able to query on these permissions like so
_session.Linq<User>().Where(u => u.EffectivePermssions.Contains(somePermission) && user.SomeOtherCriteria == something);
I understand that I wouldn't be able to modify this collection directly, and that is perfectly acceptable. Can anyone help me out with this mapping?
As a short term solution, I have created a view that handles the union logic, and have the following mapping:
HasManyToMany(user => user.EffectivePermissions)
.Table("VW_CLSYS_User_EffectiveApplicationPermissions")
.ParentKeyColumn("UserID")
.ChildKeyColumn("ApplicationPermissionID")
.Inverse();
This is not ideal, as I'd prefer to have the logic in the mapping for the user. Any other suggestions are welcome.
Related
I have a design with fluent n hibernate, I use nhibernate linq to query the database. The app use a domain driver architecture. The database is MSSQL 2012. I don't have problem to insert, update or delete with any table except the case of one table that use ID without seed identity. then I need to use in the map the following:
public class SySchoolLogoMap : ClassMap<SySchoolLogo>
{
public SySchoolLogoMap()
{
ReadOnly();
Table("SySchoolLogo");
Id(x => x.ID).Column("ImgId").GeneratedBy.Assigned();
Map(x => x.ContentType).Column("ContentType").Nullable();
Map(x => x.ImagenBytes).Column("Image").Not.Nullable().Length(50000);
Map(x => x.ImgLenth).Column("ImgLen").Nullable();
Map(x => x.ImageFile).Column("imgFile").Nullable();
Map(x => x.OfficialUse).Column("OfficialUse").Nullable();
Map(x => x.ImageCode).Column("ImageCode").Nullable();
Map(x => x.Description).Column("Description").Nullable();
}
}
The domain is as following:
public class SySchoolLogo : DomainEntityWithTypedID<int>
{
... abbreviate
}
The base DomainEntityWithTypedID
has only a ID integer that is used as Table primary key.
The update operation is using nhibernate link, I I am sure that the service is called and executed.
The update service is the following:
[HttpPost]
public HttpResponseMessage UpdateLogo([FromBody]SchoolLogoOutputModel logodata)
{
try
{
var logo = repository.Get<SySchoolLogo>(logodata.ID);
if (logo == null)
{
throw new HttpBadRequestResponseException("The Image does not exists or was erased in server.");
}
logo.UpdateLogo(logodata.Description, logodata.ImageCode, logodata.OfficialUse);
repository.SaveAndFlush(logo);
return new HttpResponseMessage(HttpStatusCode.OK);
} //catch ignored to abbreviate.
I had debugged the procedure and I am sure that the operation of Save was executed, also I tried with Update, Merge and all flush variant. The operation return as OK, but the database is not updated.
I am missing something, but I can not find what is, any help?
Well, you are using 5.1.3. class mapping:
<class
...
mutable="true|false" (4)
...
(4) mutable (optional, defaults to true): Specifies that instances of the class are (not) mutable.
which is the above mapping in fluent:
public SySchoolLogoMap()
{
ReadOnly(); // mutable="false"
And that would be the reason why NHiberante correctly does NOT execute any UPDATE...
I am trying to setup some mappings and am getting this exception:
Cannot extend unmapped class: CommonEntity
[MappingException: Cannot extend unmapped class: CommonEntity]
NHibernate.Cfg.XmlHbmBinding.ClassBinder.GetSuperclass(String
extendsName) +217
NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddEntitiesMappings(HbmMapping
mappingSchema, IDictionary`2 inheritedMetas) +352
NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(HbmMapping
mappingSchema) +85
NHibernate.Cfg.Configuration.AddDeserializedMapping(HbmMapping
mappingDocument, String documentFileName) +156
I have 3 classes. Entity, CommonEntity and User. Theres no entity or commonentity table, only a User table. User inherits from CommonEntity and CommonEntity inherits from Entity. Entity and CommonEntity are abstract.
I have defined this mapping:
public class Mapping : ConventionModelMapper
{
public Mapping()
{
IsRootEntity((type, declared) =>
{
return typeof(Entity<Guid>) == type.BaseType;
});
IsEntity((x,y) => typeof(Entity<Guid>).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface);
Class<Entity<Guid>>(x =>
{
x.Id(c => c.Id, m=>m.Generator(Generators.GuidComb));
x.Version(c=>c.Version, (vm) => { });
});
}
}
Which is used like this:
var types = typeof(Mapping).Assembly.GetExportedTypes().Where(t => typeof(Entity<Guid>).IsAssignableFrom(t));
var mapping = new Mapping().CompileMappingFor(types);
configuration.AddMapping(mapping);
Both User and CommonEntity are in the "types" array. I have tried adding a mapping for CommonEntity too but it made no difference.
Class<CommonEntity>(x =>
{
x.Property(c => c.DateCreated, m => m.Type<UtcDateTimeType>());
x.Property(c => c.DateModified, m => m.Type<UtcDateTimeType>());
});
Also tried calling Subclass instead of Class. If i inherit User directly from Entity everything works fine. Any help?
The problem appears to have been that CommonEntity was meeting the requirement for IsRootEntity. I modified it like so and things seem to be working now.
IsRootEntity((type, declared) =>
{
return !type.IsAbstract &&
new[] {typeof (Entity<Guid>), typeof (CommonEntity)}.Contains(type.BaseType);
});
I have the following index I'm creating in order to get all the permissions for a specific user. In the transform, roles.SelectMany(x => x.Permissions) could contain duplicates, so I want to put .Distinct() on it. However, when I do, it seems to get translated to Enumerable.Distinct(roles.SelectMany(x => x.Permissions) inside of Raven, which returns no results. If I change the index directly in Raven to use .Distinct() instead of Enumerable.Distinct(...), it works perfectly.
How can this be written so that it gets translated properly in Raven?
public class PermissionsByUser : AbstractIndexCreationTask<User, UserWithPermissions>
{
public override string IndexName
{
get
{
return "Users/PermissionsByUser";
}
}
public PermissionsByUser()
{
Map = users => from user in users
from role in user.Roles
select new {role.Id};
TransformResults = (database, users) => from user in users
let roles = database.Load<Role>(user.Roles.Select(x => x.Id))
select new
{
Id = user.Id,
Username = user.Username,
Password = user.Password,
Roles = user.Roles,
Permissions = roles.SelectMany(x => x.Permissions)//.Distinct()
};
}
}
This was, I think, actually just a problem of stale results. Answered at https://groups.google.com/forum/?fromgroups#!topic/ravendb/0hO8TOQicwc
I have two classes, user and role, defined as:
public class User : Entity
{
// other properties ...
public virtual string Username
public virtual ICollection<Role> Roles { get; set; }
}
public class Role : Entity
{
public virtual string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
In my mapping code, I have the following:
mapper.Class<User>(map =>
{
map.Bag(x=>x.Roles,
cm=>
{
cm.Table("UserRole");
cm.Cascade(Cascade.All);
cm.Key(k => k.Column("[User]"));
},
em=>
{
em.ManyToMany(mm =>
{
mm.Column("[Role]");
});
});
});
mapper.Class<Role>(map =>
{
map.Bag(x=>x.Users,
cm=>
{
cm.Inverse(true);
cm.Table("UserRole");
cm.Key(k=>k.Column("[Role]"));
},
em =>
{
em.ManyToMany(mm =>
{
mm.Column("[User]");
});
});
});
The mappings generate the expected schema, but the join table is never populated. Adding a new user with a new Role in its collection persists the role and then the user to the appropriate tables, but the join table is left empty. Why?
Edit: I still have not made any progress on this. I'm absolutely sure the mapping is correct, and the correct schema is generated, but the join table simply isn't populated. For test purposes, I'm generating entities using NBuilder like so:
var roles = new Role[]
{
new Role("Admin"),
new Role("Manager"),
new Role("User")
};
var users = Builder<User>.CreateListOfSize(10)
.TheFirst(1)
.Do(x =>
{
x.Roles.Add(roles[0]);
x.Roles.Add(roles[1]);
roles[0].Users.Add(x);
roles[1].Users.Add(x);
})
.All()
.With(x => x.Id = 0)
.And(x => x.Version = 0)
.And(x => x.Username = "test user")
.And(x => x.Password = "test password")
.Do(x =>
{
x.Roles.Add(roles[2]);
roles[2].Users.Add(x);
}
.Build();
foreach (var u in users) session.Save(u);
The User and Role entities are persisted correctly, but the join table remains empty. This means I cannot effective query the roles for a given user later, which nullifies the point.
Make sure you have both classes referencing each other.
I think that code, similar to one below, should work for you:
role.Users.Add(user);
user.Roles.Add(role);
session.Save(user); // NH only saves user and role, so it can get auto-generated identity fields
session.Flush(); // NH now can save into cross-ref table, because it knows required information (Flush is also called inside of Transaction.Commit())
I found a good answer to a question about many-to-many with lot of explanations and quotes from NH documentation. I think it worth to read it.
[EDIT]
In answer to this somewhat similar question there is discussion in which need for explicit transaction to save into cross-table is mentioned.
I also edited code above with adding session.Flush() to reflect my findings.
I ended up downloading the NHibernate source and referencing that directly so I could step through it. It turns out that it had something to do with the fact that my code for generating the test data was not wrapped in an explicit session transaction. Once I added that, it was fine. I'd love to see some kind of explanation on this, as I wasn't able to follow the code very clearly, but I'm at least satisfied that the problem is solved.
I've used Fluent NH in some projects but I'm having some problems with using the PersistenceSpecification class for testing a collection mapping. Here's the code for my classes (I'm just putting here the collection definition):
public class Ocorrencia : EntityWithAction, IHasAssignedId<Int32> {
private IList<Intervencao> _intervencoes = new List<Intervencao>();
public IEnumerable<Intervencao> Intervencoes {
get{
return new ReadOnlyCollection<Intervencao>( _intervencoes );
}
set {
_intervencoes = new List<Intervencao>( value );
Contract.Assume(_intervencoes != null);
}
}
public void ModificaEstado(Intervencao intervencao ){
//some checks removed
intervencao.Ocorrencia = this;
_intervencoes.Add(intervencao);
}
//more code removed
}
public class Intervencao : EntityWithAction, IHasAssignedDomainAction {
//other code remove
internal Ocorrencia Ocorrencia { get; set; }
}
And here's the mappings (only the important things):
public class IntervencaoMapping: ClassMap<Intervencao> {
public IntervencaoMapping()
{
WithTable("Intervencoes");
Not.LazyLoad();
Id(intervencao => intervencao.Id)
.ColumnName("IdIntervencoes")
.WithUnsavedValue(0)
.SetGeneratorClass("identity");
Map(intervencao => intervencao.Guid, "Guid")
.Not.Nullable();
Version(ent => ent.Version)
.ColumnName("Version");
References(ent => ent.Action, "IdAccao")
.Cascade
.SaveUpdate();
Map(intervencao => intervencao.TipoEstado, "TipoEstado")
.CustomTypeIs(typeof (TipoEstado))
.CustomSqlTypeIs("integer");
Map(intervencao => intervencao.Observacoes, "Observacoes");
References(intervencao => intervencao.Ocorrencia, "IdOcorrencias")
.Not.LazyLoad();
}
}
public class OcorrenciaMapping: ClassMap<Sra.Ocorrencias.Ocorrencia> {
public OcorrenciaMapping()
{
WithTable("Ocorrencias");
Not.LazyLoad();
Id(ocorrencia => ocorrencia.Id)
.ColumnName("IdOcorrencias")
.WithUnsavedValue(0)
.SetGeneratorClass("identity");
Map(ocorrencia => ocorrencia.Guid, "Guid")
.Not.Nullable();
Version(ocorrencia => ocorrencia.Version)
.ColumnName("Version");
Map(ocorrencia => ocorrencia.Descricao)
.ColumnName("Descricao");
Map(ocorrencia => ocorrencia.Nif, "Nif")
.Not.Nullable();
Map(ocorrencia => ocorrencia.TipoOcorrencia, "TipoOcorrencia")
.CustomTypeIs(typeof(TipoOcorrencia))
.CustomSqlTypeIs("integer");
Map(ocorrencia => ocorrencia.BalcaoEntrada, "Balcao")
.CustomTypeIs(typeof(TipoBalcao))
.CustomSqlTypeIs("integer")
.Not.Nullable();
References(ocorrencia => ocorrencia.Organismo, "IdOrganismos")
.Cascade.None()
.Not.Nullable();
HasMany(ocorrencia => ocorrencia.Intervencoes)
.Access.AsCamelCaseField(Prefix.Underscore)
.AsBag()
.Cascade
.All()
.KeyColumnNames.Add("IdOcorrencias")
.Not.LazyLoad();
}
}
As you can see, Interncao objects are added through the ModificaEstado method which ensures that Ocorrencia reference on Intervencao "points" to a reference of Ocorrencia. Now, how do I test this relationship with the PersistenceSpecification object? I've ended up with the following code:
[Test]
public void Test() {
using (var session = _factory.GetSession()) {
using (var tran = session.BeginTransaction()) {
var accao = CreateAction();
session.Save(accao);
var organismo = CreateOrganismo();
session.Save(organismo);
var intervencao = CreateIntervencao();
((IHasAssignedDomainAction)intervencao).SetActionTo(accao);
var intervencoes = new List<Intervencao> {intervencao};
new PersistenceSpecification<Ocorrencia>(session)
.CheckProperty(e => e.Nif, _nif)
.CheckProperty( e =>e.Organismo, organismo)
.CheckProperty( e => e.Descricao, _descricao)
.CheckProperty( e => e.TipoOcorrencia, TipoOcorrencia.Processo)
.CheckList( e => e.Intervencoes, intervencoes)
.VerifyTheMappings());
tran.Rollback();
}
}
}
Since IdOcorrencia is defined as an external key in table Intervencoes, the previous code fails because it tries to insert the intervencoes list with IdOcorrencia set to null. If I remove the external key, then the test works fine, but I believe that I shouldn't be doing that.
I'm probably doing something wrong but I'm not sure on what that is. So, can anyone be kind enough and give me a hint on how to solve this?
thanks guys.
Luis
The problem was that I was using an old version of the fluent nhibernate. recente versions have overrides which let you solve this kind of problem:
http://www.mail-archive.com/fluent-nhibernate#googlegroups.com/msg06121.html