I am using NHibernate 3.3. I have a case where I want to insert a calculated column which is not referenced.
My Domain Entity can be reduced to the form
public class Location
{
public virtual IPoint GeoLocation {get;set;}
}
public class MappingOverride : IAutoMappingOverride<Location>
{
public void Override(AutoMapping<Location> mapping)
{
mapping
.Map(e => e.GeoLocation)
.Column("GeoLocation")
.CustomSqlType("geography")
.CustomType<MsSql2008GeographyType>;
}
}
The table column is of type 'Geography`
However it errors out as
at System.Reflection.RuntimeAssembly.GetExportedTypes(RuntimeAssembly assembly, ObjectHandleOnStack retTypes)
at System.Reflection.RuntimeAssembly.GetExportedTypes()
at GeoAPI.GeometryServiceProvider.GetLoadableTypes(Assembly assembly)
at GeoAPI.GeometryServiceProvider.ReflectInstance()
at GeoAPI.GeometryServiceProvider.get_Instance()
at NetTopologySuite.Geometries.Geometry.set_SRID(Int32 value)
It says that it needs Antrl.Runtime, but a very old version. All the Antrl.Runtime nuget packages out there have a different assembly identifier.
Could not load file or assembly 'antlr.runtime, Version=2.7.6.2, Culture=neutral, PublicKeyToken=1790ba318ebc5d56' or one of its dependencies. The system cannot find the file specified.
I worked on a separate project where I used map by code convention and it works without any reference to Antrl.Runtime.
Need help to point myself in the right direction...
As a separate sample project I tried something which worked, WHICH DID NOT WORK in the actual ASP.NET MVC project.
Definition of Class
public class Location : IEntity
{
public virtual int Id { get; protected set; }
public virtual string LocationName { get; set; }
public virtual IPoint GeoLocation { get; set; }
}
Mapping
public class LocationMapping : IAutoMappingOverride<Location>
{
public void Override(AutoMapping<Location> mapping)
{
mapping.Map(x => x.LocationName).Column("Name");
mapping.Map(x => x.GeoLocation).Column("GeoLocation")
.CustomType<MsSql2008GeographyType>();
}
}
Test
public class WhenSavingGeoLocation : BasePersistanceTest
{
[Test]
public void Save()
{
this.Session
.SaveOrUpdate(new Location {
LocationName = "Category 01",
GeoLocation = new Point(-72.12, 32.2323234) { SRID = 4326 }
});
}
}
NHibernate.Spatial uses NetTopologicalSuite which itself is a derivative of GeoAPI
The GeoAPI library defines an interface GeoAPI.Geometries and while loading / searching the correct implementation in the current project was failing with the Antlr error in the MVC project.
It was however able to fetch the NetTopologySuite.NtsGeometryServices implementation in the separate sample project. The actual code in GeoAPI which searches for the implementation goes like
private static IGeometryServices ReflectInstance()
{
#if !PCL
var a = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in a)
{
// Take a look at issue 114: http://code.google.com/p/nettopologysuite/issues/detail?id=114
if (assembly is System.Reflection.Emit.AssemblyBuilder) continue;
if (assembly.GetType().FullName == "System.Reflection.Emit.InternalAssemblyBuilder") continue;
if (assembly.GlobalAssemblyCache && assembly.CodeBase == Assembly.GetExecutingAssembly().CodeBase) continue;
foreach (var t in GetLoadableTypes(assembly))
{
if (t.IsInterface) continue;
if (t.IsAbstract) continue;
if (t.IsNotPublic) continue;
if (!typeof(IGeometryServices).IsAssignableFrom(t)) continue;
var constuctors = t.GetConstructors();
foreach (var constructorInfo in constuctors)
{
if (constructorInfo.IsPublic && constructorInfo.GetParameters().Length == 0)
return (IGeometryServices)Activator.CreateInstance(t);
}
}
}
#endif
throw new InvalidOperationException("Cannot use GeometryServiceProvider without an assigned IGeometryServices class");
}
}
This gets called when setting the SRID on the geometry. I am guessing that my MVC project has a reference to an assembly which makes this look for Antlr.
There were some suggestions of adding a redirect for to newer Antlr assembly. I tried that as well but the error still kept repeating.
Related
I'm trying to implement authorization in my ASP.NET Core 2.0 Web app.
This app has like 20 models, each with a controller implementing at least a CRUD. I found these two pages and I liked the idea of using a handler to authorize requisitions. I would like initially to implement authorization by user, i.e., a user has only permission to see/edit his own entities. All my database entities have an OwnerId field.
These examples I found seem to only work for one specific controller.
So, my question is: is it possible to create one authorization handler for all controllers?
Have you found a solution or workaround yet that works with the authorization handler or authorization attributes? I have the exact same setup as you do.
I was trying to create a generic attribute to serve all may Entity CRUD owner checks, but generic attributes are not allowed by design.
The only two (unsatisfying) solutions that I came up with are:
Within the controller action, get the ownerId from the User, forward it all the way to your CRUD and include there a check for the ownerId. However, the code must be duplicated for every action in every controller.
[HttpGet("{id}"]
public async Task<IActionResult> GetById(int id)
{
var stringGuid = User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (String.IsNullOrWhiteSpace(stringGuid)) return Unauthorized();
var ownerGuid = new Guid(stringGuid);
var entity = _yourCrudInstance.GetById(id, ownerGuid);
return Ok(entity);
}
Add a method to your CRUD repository like bool IsOwner(Guid ownerId) and use this method when creating the custom authorization handler (by creating a custom requirement together with a custom handler). This eliminates code duplication in the controller, because you can create a new policy with this custom authorization handler and consequently you can simply decorate every action with a [Authorize(Policy = "yourOwnershipPolicy")]. But still, there must be a service created for each and every controller. Moreover, the IsOwner(...) method adds an additional database call compared to solution 1 - one db call for checking the ownership (during authorization check) and one db call for actually getting the entity (by working through the controller action).
[Authorize(Policy = "yourOwnershipPolicy")]
public async Task<IActionResult> GetById(int id)
{
var entity = _yourCrudInstance.GetById(id);
return Ok(entity);
}
I am going with the first solution until I found a way to create a generic authorization handling for my generic CRUD repository, because one may forget creating the required authorization policy for a new entity, but one cannot forget to supply the parameter ownerId to .GetById(id, ownerGuid), provided there is no overload method, or the code doesn't compile.
Update:
I found a third solution in which was able to create a kind of generic authorization attribute. The trick was to use the type of concrete repository as input parameter in the authorization attribute. Yet, there is still a limitation: The authorization attribute must be copied for every type of Id, for example int Id, Guid id, etc. But still, this reduces repeated code to the types of ids. In most cases, people only have one type of id, probably int or Guid.
Here some code that demonstrates my architecture. It is heavily summarized and redacted, but should compile successfully. My original code is working and in production:
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
[Route("api/yourcontroller")]
public class YourApiController : Controller
{
private readonly YourEntityXYZRepository _repo;
public YourApiController(YourDbContext yourDbContext)
{
_repo = new YourEntityXYZRepository(yourDbContext);
}
[HttpGet("{id}")]
[AuthorizeOwnerIntId(typeof(YourEntityXYZRepository), Policy = "YourCustomPolicy")]
public async Task<IActionResult> GetById(int id)
{
var entity = _repo.GetById(id);
return Ok(entity);
}
}
// The "generic" authorization attribute for type int id
// Similar authorization attributes for every type of id must be created additionally, for example Guid
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizeOwnerIntIdAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private object _entityRepositoryObject;
private IAsyncOwnerIntId _entityRepository;
private readonly Type _TCrudRepository;
public AuthorizeOwnerIntIdAttribute(Type TCrudRepository)
{
_TCrudRepository = TCrudRepository;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var yourDbContext = context.HttpContext.RequestServices.GetService<YourDbContext>();
_entityRepositoryObject = Activator.CreateInstance(_TCrudRepository, yourDbContext);
_entityRepository = _entityRepositoryObject as IAsyncOwnerIntId;
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
// it isn't needed to set unauthorized result
// as the base class already requires the user to be authenticated
// this also makes redirect to a login page work properly
// context.Result = new UnauthorizedResult();
return;
}
// get entityId from uri
var idString = context.RouteData.Values["id"].ToString();
if (!int.TryParse(idString, out var entityId))
{
context.Result = new UnauthorizedResult();
return;
}
// get subjectId from user claims
var ownerIdString = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (!Guid.TryParse(ownerIdString, out var ownerGuid))
{
context.Result = new UnauthorizedResult();
return;
}
if (!_entityRepository.IsEntityOwner(entityId, ownerGuid))
{
context.Result = new UnauthorizedResult();
}
}
}
// Your concrete repository
public class YourEntityXYZRepository : AsyncCrud<YourEntityXYZ, int>,
IAsyncOwnerIntId // Note that type concrete IAsyncOwnerIntId is only implemented in concrete repository
{
public YourEntityXYZRepository(YourDbContext yourDbContext) : base(yourDbContext)
{
}
}
// Your generic Crud repository
public abstract class AsyncCrud<TEntity, TId> : IAsyncCrud<TEntity, TId>
where TEntity : class, IEntityUniqueIdentifier<TId>, IEntityOwner
where TId : struct
{
protected YourDbContext YourDbContext;
public AsyncCrud(YourDbContext yourDbContext)
{
YourDbContext = yourDbContext;
}
// Note that the following single concrete implementation satisfies both interface members
// bool IsEntityOwner(TId id, Guid ownerGuid); from IAsyncCrud<TEntity, TId> and
// bool IsEntityOwner(int id, Guid ownerGuid); from IAsyncOwnerIntId
public bool IsEntityOwner(TId id, Guid ownerGuid)
{
var entity = YourDbContext.Set<TEntity>().Find(id);
if (entity != null && entity.OwnerGuid == ownerGuid)
{
return true;
}
return false;
}
// Further implementations (redacted)
public Task<bool> SaveContext() { throw new NotImplementedException(); }
public Task<TEntity> Update(TEntity entity){ throw new NotImplementedException(); }
public Task<TEntity> Create(TEntity entity, Guid ownerGuid) { throw new NotImplementedException(); }
public Task<bool> Delete(TId id) { throw new NotImplementedException(); }
public Task<bool> DoesEntityExist(TId id) { throw new NotImplementedException(); }
public virtual Task<TEntity> GetById(TId id) { throw new NotImplementedException(); }
}
// The interface for the Crud operations
public interface IAsyncCrud<TEntity, TId>
where TEntity : class, IEntityUniqueIdentifier<TId>
where TId : struct
{
bool IsEntityOwner(TId id, Guid ownerGuid);
Task<bool> DoesEntityExist(TId id);
Task<TEntity> GetById(TId id);
Task<TEntity> Create(TEntity entity, Guid ownerGuid);
Task<TEntity> Update(TEntity entity);
Task<bool> Delete(TId id);
Task<bool> SaveContext();
}
// The interface for the concrete type method for int id
// Similar interfaces for every type of id must be created additionally, for example Guid
public interface IAsyncOwnerIntId
{
bool IsEntityOwner(int id, Guid ownerGuid);
}
// Typical db context
public class YourDbContext : DbContext
{
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
{
}
public DbSet<YourEntityXYZ> YourEntityXYZ { get; set; }
}
public class YourEntityXYZ : IEntityUniqueIdentifier<int>, IEntityOwner
{
public int Id { get; set; }
public Guid? OwnerGuid { get; set; }
// ... Additonal custom properties
}
public interface IEntityUniqueIdentifier<TId>
where TId : struct
{
TId Id { get; set; }
}
public interface IEntityOwner
{
Guid? OwnerGuid { get; set; }
}
Here's the (edited) domain class:
public class Patient : IntegerKeyEntity
{
...
public virtual string LastName
{
get
{
return Encoding.Unicode.GetString(encryptionService.Decrypt(LastNameEncrypted));
}
set
{
LastNameEncrypted = encryptionService.Encrypt(value);
}
}
/// <summary>
/// Contains the encrypted last name. Access via LastName for unencrypted version.
/// </summary>
public virtual byte[] LastNameEncrypted { get; set; }
...
}
Here's the Fluent mapping:
public class PatientMap : ClassMap<Patient>
{
public PatientMap()
{
...
Map(x => x.LastNameEncrypted, "LastName")
.Not.Nullable();
...
}
}
I don't map LastName in Patient. The table has a LastName column (a varbinary) and no LastNameEncrypted column.
Here's the query:
public virtual IQueryable<TResult> SatisfyingElementsFrom(IQueryable<T> candidates, int start, int limit, string sort, string dir)
{
if (this.MatchingCriteria != null)
{
return candidates.Where(this.MatchingCriteria).OrderBy(sort + " " + dir).Skip(start).Take(limit).ToList().ConvertAll(this.ResultMap).AsQueryable();
}
return candidates.ToList().ConvertAll(this.ResultMap).AsQueryable();
}
The return (inside the if block) is where the error is triggered. The error says "Could not resolve property LastName of Patient".
I am using NHibernate (v2.1.2.4000), Fluent NHibernate (v1.1.0.685) and NHibernate.Linq (v1.1.0.1001). I cannot update these DLLs.
Is it because I don't have a mapping for Patient.LastName? I don't have or need one. If that is the issue, how do I map/tell Fluent NHibernate to ignore that property?
PS: I am not using AutoMapping, only explicit mappings. They are loaded as follows. In my application, only cfg (an NHibernate.Cfg.Configuration object) and mappingAssemblies (which points to the one DLL with the mappings) have a value.
private static ISessionFactory CreateSessionFactoryFor(string[] mappingAssemblies, AutoPersistenceModel autoPersistenceModel, Configuration cfg, IPersistenceConfigurer persistenceConfigurer)
{
FluentConfiguration fluentConfiguration = Fluently.Configure(cfg);
if (persistenceConfigurer != null)
{
fluentConfiguration.Database(persistenceConfigurer);
}
fluentConfiguration.Mappings(m =>
{
foreach (var mappingAssembly in mappingAssemblies)
{
var assembly = Assembly.LoadFrom(MakeLoadReadyAssemblyName(mappingAssembly));
m.HbmMappings.AddFromAssembly(assembly);
m.FluentMappings.AddFromAssembly(assembly).Conventions.AddAssembly(assembly);
}
if (autoPersistenceModel != null)
{
m.AutoMappings.Add(autoPersistenceModel);
}
});
return fluentConfiguration.BuildSessionFactory();
}
This error happens when you do the query. Looking to your code I see only one thing that might cause the problem - that is MatchingCriteria:
return candidates.Where(this.MatchingCriteria)...
What type is stored in this.MatchingCriteria?
Try to replace it with inline conditions: in
..Where(<put_inline_criteria_here>)..
I've scoured Google and SO but haven't come across anyone having the same problem. Here is my model:
public class Hierarchy
{
public virtual Event Prerequisite { get; set; }
public virtual Event Dependent { get; set; }
public override bool Equals(object obj)
{
var other = obj as Hierarchy;
if (other == null)
{
return false;
}
else
{
return this.Prerequisite == other.Prerequisite && this.Dependent == other.Dependent;
}
}
public override int GetHashCode()
{
return (Prerequisite.Id.ToString() + "|" + Dependent.Id.ToString()).GetHashCode();
}
}
Here is my mapping:
public class HierarchyMap : ClassMap<Hierarchy>
{
public HierarchyMap()
{
CompositeId()
.KeyReference(h => h.Prerequisite, "PrerequisiteId")
.KeyReference(h => h.Dependent, "DependentId");
}
}
And here is the ever present result:
{"The entity 'Hierarchy' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)."}
Is there some special configuration I need to do to enable composite id's? I have the latest FNh (as of 6/29/2012).
Edit
I consider the question open even though I've decided to map an Id and reference the 2 Event's instead of using a CompositeId. Feel free to propose an answer.
I figured out this was due to auto mapping trying to auto map the ID
Even though i had an actual map for my class - it still tried to auto map the ID. Once i excluded the class from auto mapping, it worked just fine.
My aim is to implement a quite generic search mechanism. Here's the general idea:
you can search based on any property of the entity you're searching for (for example- by Employee's salary, or by Department name etc.).
Each property you can search by is represented by a class, which inherits from EntityProperty:
public abstract class EntityProperty<T>
where T:Entity
{
public enum Operator
{
In,
NotIn,
}
/// <summary>
/// Name of the property
/// </summary>
public abstract string Name { get; }
//Add a search term to the given query, using the given values
public abstract IQueryable<T> AddSearchTerm(IQueryable<T> query, IEnumerable<object> values);
public abstract IQueryable<T> AddSortingTerm(IQueryable<T> query);
protected Operator _operator = Operator.In;
protected bool _sortAscending = false;
public EntityProperty(Operator op)
{
_operator = op;
}
//use this c'tor if you're using the property for sorting only
public EntityProperty(bool sortAscending)
{
_sortAscending = sortAscending;
}
}
all of the properties you're searching / sorting by are stored in a simple collection class:
public class SearchParametersCollection<T>
where T: Entity
{
public IDictionary<EntityProperty<T>,IEnumerable<object>> SearchProperties { get; private set; }
public IList<EntityProperty<T>> SortProperties { get; private set; }
public SearchParametersCollection()
{
SearchProperties = new Dictionary<EntityProperty<T>, IEnumerable<object>>();
SortProperties = new List<EntityProperty<T>>();
}
public void AddSearchProperty(EntityProperty<T> property, IEnumerable<object> values)
{
SearchProperties.Add(property, values);
}
public void AddSortProperty(EntityProperty<T> property)
{
if (SortProperties.Contains(property))
{
throw new ArgumentException(string.Format("property {0} already exists in sorting order", property.Name));
}
SortProperties.Add(property);
}
}
now, all the repository class has to do is:
protected IEnumerable<T> Search<T>(SearchParametersCollection<T> parameters)
where T : Entity
{
IQueryable<T> query = this.Session.Linq<T>();
foreach (var searchParam in parameters.SearchProperties)
{
query = searchParam.Key.AddSearchTerm(query, searchParam.Value);
}
//add order
foreach (var sortParam in parameters.SortProperties)
{
query = sortParam.AddSortingTerm(query);
}
return query.AsEnumerable();
}
for example, here's a class which implements searching a user by their full name:
public class UserFullName : EntityProperty<User>
{
public override string Name
{
get { return "Full Name"; }
}
public override IQueryable<User> AddSearchTerm(IQueryable<User> query, IEnumerable<object> values)
{
switch (_operator)
{
case Operator.In:
//btw- this doesn't work with nHibernate... :(
return query.Where(u => (values.Cast<string>().Count(v => u.FullName.Contains(v)) > 0));
case Operator.NotIn:
return query.Where(u => (values.Cast<string>().Count(v => u.FullName.Contains(v)) == 0));
default:
throw new InvalidOperationException("Unrecognized operator " + _operator.ToString());
}
}
public override IQueryable<User> AddSortingTerm(IQueryable<User> query)
{
return (_sortAscending) ? query.OrderBy(u => u.FullName) : query.OrderByDescending(u => u.FullName);
}
public UserFullName(bool sortAscending)
: base(sortAscending)
{
}
public UserFullName(Operator op)
: base(op)
{
}
}
my questions are:
1. firstly- am I even on the right track? I don't know of any well-known method for achieving what I want, but I may be wrong...
2. it seems to me that the Properties classes should be in the domain layer and not in the DAL, since I'd like the controller layers to be able to use them. However, that prevents me from using any nHibernate-specific implementation of the search (i.e any other interface but Linq). Can anybody think of a solution that would enable me to utilize the full power of nH while keeping these classes visible to upper layers? I've thought about moving them to the 'Common' project, but 'Common' has no knowledge of the Model entities, and I'd like to keep it that way.
3. as you can see by my comment for the AddSearchTerm method- I haven't really been able to implement 'in' operator using nH (I'm using nH 2.1.2 with Linq provider). any sugggestions in that respect would be appriciated. (see also my question from yesterday).
thanks!
If you need good API to query NHIbernate objects then you should use ICriteria (for NH 2.x) or QueryOver (for NH 3.x).
You over complicating DAL with these searches. Ayende has a nice post about why you should not do it
I ended up using query objects, which greatly simplified things.
How do I "turn on" cascading saves using AutoMap Persistence Model with Fluent NHibernate?
As in:
I Save the Person and the Arm should also be saved. Currently I get
"object references an unsaved transient instance - save the transient instance before flushing"
public class Person : DomainEntity
{
public virtual Arm LeftArm { get; set; }
}
public class Arm : DomainEntity
{
public virtual int Size { get; set; }
}
I found an article on this topic, but it seems to be outdated.
This works with the new configuration bits. For more information, see http://fluentnhibernate.wikia.com/wiki/Converting_to_new_style_conventions
//hanging off of AutoPersistenceModel
.ConventionDiscovery.AddFromAssemblyOf<CascadeAll>()
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public bool Accept( IOneToOnePart target )
{
return true;
}
public void Apply( IOneToOnePart target )
{
target.Cascade.All();
}
public bool Accept( IOneToManyPart target )
{
return true;
}
public void Apply( IOneToManyPart target )
{
target.Cascade.All();
}
public bool Accept( IManyToOnePart target )
{
return true;
}
public void Apply( IManyToOnePart target )
{
target.Cascade.All();
}
}
Updated for use with the the current version:
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public void Apply(IOneToOneInstance instance)
{
instance.Cascade.All();
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.All();
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.All();
}
}
The easiest way I've found to do this for a whole project is to use DefaultCascade:
.Conventions.Add( DefaultCascade.All() );
Go to "The Simplest Conventions" section on the wiki, for this, and a list of others.
Here's the list from the Wiki:
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
A word of warning - some of the method names in the Wiki may be wrong. I edited the Wiki with what I could verify (i.e. DefaultCascade and DefaultLazy), but can't vouch for the rest. But you should be able to figure out the proper names with Intellisense if the need arises.
The Convention Method Signatures have changed. For the new answer that does exactly what this question asks see THIS QUESTION.
You can also make cascading the default convention for all types. For example (using the article you linked to as a starting point):
autoMappings.WithConvention(c =>
{
// our conventions
c.OneToOneConvention = o => o.Cascade.All();
c.OneToManyConvention = o => o.Cascade.All();
c.ManyToOneConvention = o => o.Cascade.All();
});