I am trying to add an HqlGenerator to allow NHibernate to query a calculated property on the following domain entity:
public class User
{
public string FirstName { get; set; }
public string LastName {get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
I have created an HQL generator like so:
public class UserFullName : BaseHqlGeneratorForProperty
{
public UserFullName()
{
var properties = new List<MemberInfo> { typeof(User).GetProperty("FullName") };
SupportedProperties = properties;
}
public override HqlTreeNode BuildHql(MemberInfo member, Expression expression, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var user = visitor.Visit(expression).AsExpression();
return treeBuilder.Concat(
treeBuilder.Dot(user, treeBuilder.Ident("FirstName")),
treeBuilder.Constant(" "),
treeBuilder.Dot(user, treeBuilder.Ident("LastName"))
);
}
}
The generator is wired up correctly into the configuration as the debugger will break within the BuildHql method when I run the query:
session.Query<User>().FirstOrDefault(x => x.FullName == "Aaron Janes");
however, later on in the NHibernate internals an exception is thrown:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException : Exception of type 'Antlr.Runtime.NoViableAltException' was thrown. [.FirstOrDefault[User](NHibernate.Linq.NhQueryable`1[User], Quote((x, ) => (String.op_Equality(x.FullName, Aaron Janes))), )]
Can anyone spot what I am doing wrong?
In your BuildHQL() you include the subexpression 'user' multiple times. This is not possible. You need to use
var user1 = visitor.Visit(expression).AsExpression();
var user2 = visitor.Visit(expression).AsExpression();
and use each of those once in the Concat expression.
Related
I have a Model with a property of a value object type as following:
public class Course : AggregateRoot, ISpModel
{
...
public UnsignedNumber MaximumCapacity { get; private set; }
...
}
with UnsignedNumber being a value object containing a short value:
public class UnsignedNumber : BaseValueObject<UnsignedNumber>
{
public short Value { get; }
...
}
What I need to do is to sum all the MaximumCapacities of courses which correspond with certain conditions, but when I try to add a SumAsync(x => x.MaximumCapacity) at the end of the query, I get a syntax error
the syntax error
and when I try to do the same with it's value, I get a linq error in runtime.
"The LINQ expression '(int)(EntityShaperExpression: \r\n EntityType: Course\r\n ValueBufferExpression: \r\n (ProjectionBindingExpression: Outer)\r\n IsNullable: False\r\n).MaximumCapacity.Value' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information."
Edit:
Here's the Linq Expression that encounters the problem:
var query = _dbContext.Courses.AsQueryable();
query = query.Include(x => BunchOfIncludes(x));
var res = await query.Where(x => BunchOfClauses(x)).SumAsync(x => x.MaximumCapacity.Value);
Edit2: the classes mentioned above:
public abstract class AggregateRoot : Entity
{
private readonly List<IDomainEvent> _events;
protected AggregateRoot() => _events = new List<IDomainEvent>();
public AggregateRoot(IEnumerable<IDomainEvent> events)
{
if (events == null) return;
foreach (var #event in events)
((dynamic)this).On((dynamic)#event);
}
protected void AddEvent(IDomainEvent #event) => _events.Add(#event);
public IEnumerable<IDomainEvent> GetEvents() => _events.AsEnumerable();
public void ClearEvents() => _events.Clear();
}
public interface ISpModel
{
}
public abstract class BaseValueObject<TValueObject> : IEquatable<TValueObject>
where TValueObject : BaseValueObject<TValueObject>
{
...
public static bool operator ==(BaseValueObject<TValueObject> right, BaseValueObject<TValueObject> left)
{
if (right is null && left is null)
return true;
if (right is null || left is null)
return false;
return right.Equals(left);
}
...
}
When I save a document that has a generic type DataView<Customer>, I'm manually setting the collection name to "customers". However, I'm having some trouble making an index using AbstractIndexCreationTask with a non-default collection name. Here's my index:
public class customers_Search
: AbstractIndexCreationTask<DataView<Customer>, customers_Search.Result>
{
public class Result
{
public string Query { get; set; }
}
public customers_Search()
{
Map = customers =>
from customer in customers
where customer.Data != null
select new
{
Query = AsDocument(customer.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
When this gets deployed, it looks like this:
from customer in docs.DataViewOfCustomer
where customer.Data != null
select new {
Query = customer.Data.Select(x => x.Value)
}
This doesn't work obviously, and if I change DataViewOfCustomer to "customers" it works just fine.
I'd rather not have to use non-type-checked (string) indexes to deploy. Is there a way to set the collection name that from the AbstractIndexCreationTask class?
Update
Since my data class is generic, I made a generic index which fixes up the names.
public class DataViewQuery<TEntity>
: AbstractIndexCreationTask<DataView<TEntity>, DataViewQueryResult>
{
private readonly string _entityName;
private readonly string _indexName;
// this is to fix the collection name for the index name
public override string IndexName { get { return _indexName; } }
// this is to fix the collection name for the index query
public override void Execute(IDatabaseCommands databaseCommands, DocumentConvention documentConvention)
{
var conventions = documentConvention.Clone();
conventions.FindTypeTagName =
type =>
typeof(DataView<TEntity>) == type
? _entityName
: documentConvention.FindTypeTagName(type);
base.Execute(databaseCommands, conventions);
}
public DataViewQuery(string entityName)
{
_entityName = entityName;
_indexName = String.Format("{0}/{1}", entityName, "Query");
Map = items =>
from item in items
where item.Data != null
select new
{
Query = AsDocument(item.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
public class DataViewQueryResult
{
public string Query { get; set; }
}
Then I can create a specific index which has all the configuration in it.
// sets the collection type (DataView<Customer>) for the index
public class CustomerQuery : DataViewQuery<Customer>
{
// sets the collection name for the index
public CustomerQuery() : base(EntityName.Customers) { }
}
You need to configure this in the conventions.
The property to configure is FindTypeTagName
Working on creating my first Orchard Module and I am running into issues getting the form data saved back to the database. I have everything registered correctly as far as I can tell from looking at a lot of samples so I must be missing something minor.
I am able to get the Apartment form to show under the new menu, validation is working but when I fill the form completly and hit save I get:
Your Apartment has been created.
Checking the database the record is not in the table and checking the logs shows:
2013-12-19 09:15:23,416 [19]
NHibernate.Transaction.ITransactionFactory - DTC transaction prepre
phase failed NHibernate.Exceptions.GenericADOException: could not
execute batch command.[SQL: SQL not available] --->
System.Data.SqlClient.SqlException: Cannot insert the value NULL into
column 'FloorPlanName', table
'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord';
column does not allow nulls. INSERT fails.
Running SQL Profiler shows an insert with all columns being set to NULL.
Migrations.cs
SchemaBuilder.CreateTable(typeof(ApartmentPartRecord).Name, table => table
.ContentPartRecord()
.Column<string>("FloorPlanName", c => c.WithLength(25).NotNull())
.Column<string>("FullAddress", c => c.WithLength(256).NotNull()))
.Column<string>("ShortDescription", c => c.WithLength(150).NotNull())
.Column("NumberOfBedrooms", DbType.Int32, c => c.NotNull())
.Column("NumberOfBathrooms", DbType.Int32, c => c.NotNull())
.Column("SquareFootage", DbType.Int32, c => c.NotNull())
.Column("WhenAvailable", DbType.DateTime)
.Column("RentAmount", DbType.Decimal)
);
ContentDefinitionManager.AlterPartDefinition(typeof (ApartmentPart).Name, part => part.Attachable());
ApartmentPart
public class ApartmentPartRecord : ContentPartRecord {
public virtual string FloorPlanName { get; set; }
public virtual string ShortDescription { get; set; }
public virtual string FullAddress { get; set; }
public virtual int? NumberOfBedrooms { get; set; }
public virtual int? NumberOfBathrooms { get; set; }
public virtual int? SquareFootage { get; set; }
public virtual DateTime? WhenAvailable { get; set; }
public virtual decimal? RentAmount { get; set; }
}
public class ApartmentPart : ContentPart<ApartmentPartRecord> {
[Required, StringLength(256)]
[Display(Name = "Address / Unit Number")]
public string FullAddress {
get { return Record.FullAddress; }
set { Record.FullAddress = value; }
}
[Required, StringLength(25)]
[Display(Name = "Floor Plan")]
public string FloorPlanName {
get { return Record.FloorPlanName; }
set { Record.FloorPlanName = value; }
}
[Required, StringLength(150)]
[Display(Name = "Sales Description")]
public string ShortDescription {
get { return Record.ShortDescription; }
set { Record.ShortDescription = value; }
}
[Required]
[Display(Name = "Bedroom Count")]
public int? NumberOfBedrooms {
get { return Record.NumberOfBedrooms; }
set { Record.NumberOfBedrooms = value; }
}
[Required]
[Display(Name = "Bathroom Count")]
public int? NumberOfBathrooms {
get { return Record.NumberOfBathrooms; }
set { Record.NumberOfBathrooms = value; }
}
[Required]
[Display(Name = "Square Footage")]
public int? SquareFootage {
get { return Record.SquareFootage; }
set { Record.SquareFootage = value; }
}
[Display(Name = "First Availability")]
public DateTime? WhenAvailable {
get { return Record.WhenAvailable; }
set { Record.WhenAvailable = value; }
}
[Display(Name = "Rent Amount")]
public decimal? RentAmount {
get { return Record.RentAmount; }
set { Record.RentAmount = value; }
}
}
Driver
public class ApartmentPartDriver : ContentPartDriver<ApartmentPart>
{
protected override string Prefix
{
get { return "Apartment"; }
}
//GET
protected override DriverResult Editor(ApartmentPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Apartment_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: "Parts/Apartment",
Model: part,
Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ApartmentPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Handler
public class ApartmentPartHandler : ContentHandler {
public ApartmentPartHandler(IRepository<ApartmentPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
Your error message explains this pretty clearly:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'FloorPlanName', table 'Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord'; column does not allow nulls. INSERT fails.
Your problem occurs because:
You are using nullable types such as string and int? types in your Record class, which means you want to allow nulls.
Yet, you are specifying in your DB migration that you want to disallow nulls.
And when C# instantiates your Record class, it initializes the fields using the default value, which is null for nullable types.
You can do one of the following:
Make your DB columns nullable (remove NotNull)
Make your Record class use non-nullable types (for example, int instead of int?). Note that this is not an option for reference types such as string.
Give non-null default values to the fields of your Record class by giving the class a constructor. This is arguably bad practice since you will be calling virtual properties in a base class, but seems to be ok in NHibernate.
Give non-null default values to the fields of your Record class by giving your part an OnInitializing handler, which would be placed in your Handler class.
UPDATE
You commented that you are expecting the fields to be filled in by the TryUpdateModel in the Editor function of your driver class. This does eventually happen, but the actual sequence of events that occurs is this (you can see this in the CreatePOST method of Orchard.Core.Contents.Controllers.AdminController):
ContentManager.New() with the content type ID to create content item in memory. This step calls OnInitializing for the appropriate content parts for the content type, which are defined in handlers.
ContentManager.Create() with the content item in Draft Mode. This step actually tries to persist the item to the DB once.
ContentManager.UpdateEditor(). This is the call that actually calls Editor of the appropriate driver for the content type.
Check the ModelState and roll back the transaction if anything has failed.
Step 2 will fail if you have NULL values in columns marked NotNull, because the fields have default values at that point. For these columns, you have to fill them in before step 2 by using OnInitializing or by using a constructor on your Record part.
In other words, TryUpdateModel in your driver is actually applying changes directly to the entity that has already been Created and is now attached to the NHibernate session.
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 am using ValueInjecter to map properties from a Domain model to a DTO served up via a Service Layer. The service in question also accepts updates... so an updated DTO is passed in and this is then injected to the domain object and saved.
// Domain
public class Member
{
public Country Country { get; set; }
}
public class Country
{
public string Code { get; set; }
public string Name { get; set; }
}
//Dto
public class MemberDto
{
public string CountryCode { get; set; }
}
//Transformation Method attempt 1
public Member InjectFromDto (MemberDto dto, Member source)
{
source = source.InjectFrom<UnflatLoopValueInjection>(dto);
return source;
}
Now all this above code does is updates the Property Member.Country.Code which is obviously not what I need it to do.
So from the docs, I figured I needed to create an override and got this:
public class CountryLookup: UnflatLoopValueInjection<string, Country>
{
protected override Country SetValue(string sourcePropertyValue)
{
return countryService.LookupCode(sourcePropertyValue);
}
}
//revised transformation call
//Transformation Method attempt 2
public Member InjectFromDto (MemberDto dto, Member source)
{
source = source.InjectFrom<UnflatLoopValueInjection>(dto)
.InjectFrom<CountryLookup>(dto);
return source;
}
My problem is during debugging, CountryLookup never gets called.
Possible reasons I can think of:
Nhibernate Proxy classes causing value injecter to not match the Country type? Tho this doesnt make sense because it works during the flattening.
Perhaps the unflattening isn't firing for some reason. I.e Dto is CountryCode and Domain is Country.Code
I need to use the CountryCode property on the Dto to call a countryService.LookupCode to return the correct object to use during the update injection.
unflattening would be to do this:
entity.Country.Code <- dto.CountryCode
what you need is:
entity.Country <- dto.CountryCode
so the solution for you would be to inherit an ExactValueInjection where you would go from CountryCode to Country.
what I recommend you to do is do the same that I did in the live demo of another project of mine http://awesome.codeplex.com
where I have something like this:
public class Entity
{
public int Id{get;set;}
}
public class Member : Entity
{
public Country Country{get;set;}
}
public class MemberDto : DtoWithId
{
public int? Country {get;set;}
}
and use these injections to go from entity to dto and back
public class NullIntToEntity : LoopValueInjection
{
protected override bool TypesMatch(Type sourceType, Type targetType)
{
return sourceType == typeof(int?) && targetType.IsSubclassOf(typeof(Entity));
}
protected override object SetValue(object sourcePropertyValue)
{
if (sourcePropertyValue == null) return null;
var id = ((int?) sourcePropertyValue).Value;
dynamic repo = IoC.Resolve(typeof(IRepo<>).MakeGenericType(TargetPropType));
return repo.Get(id);
}
}
//(you also need to have a generic repository, notice IRepo<>)
public class EntityToNullInt : LoopValueInjection
{
protected override bool TypesMatch(Type sourceType, Type targetType)
{
return sourceType.IsSubclassOf(typeof (Entity)) && targetType == typeof (int?);
}
protected override object SetValue(object o)
{
if (o == null) return null;
return (o as Entity).Id;
}
}
these injections will handle not just going from int? to Country and back but also any other type which inherits Entity
Using the suggestion/reference from Omu this was the specific code to the problem.
public class CountryLookup : ExactValueInjection
{
private ICountryService countryservice;
public CountryLookup(ICountryService countryService)
{
this.countryService = countryService;
}
protected override bool TypesMatch(Type s, Type t)
{
return (s == typeof(string)) && (t == typeof (Country));
}
protected override Object SetValue(object v)
{
if (v == null)
return null;
var country = countryService.LookupCode((string) v);
return country;
}
public override string SourceName()
{
return "CountryCode";
}
public override string TargetName()
{
return "Country";
}
}
public Member InjectFromDto (MemberDto dto, Member source)
{
source = source.InjectFrom<UnflatLoopValueInjection>(dto)
.InjectFrom<CountryLookup>(dto);
return source;
}
Is a framework calling the setter method? In most DI frameworks, the standard is lowercase 's' in the setMethod(). Just a first-thought recommendation.