NHibernate query throws exception, when looking by related table - linq

I have pretty simple linq expression:
session.Query<Order>().Where(x => x.States.OrderByDescending(z => z.Date).FirstOrDefault().Name == "2").ToList();
Result: InvalidCastException Unable to cast object of type 'Antlr.Runtime.Tree.CommonTree' to type 'NHibernate.Hql.Ast.ANTLR.Tree.IASTNode'.
Same query with LinqPad works as expected: selects orders, which last state is OnTheWay. How can I circumvent this and get desired result?
Code to try yourself:
class Program
{
static void Main(string[] args)
{
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("defaultConnectionStringForNhibernate")))
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetEntryAssembly()))
.BuildSessionFactory();
var session = sessionFactory.OpenSession();
var res2 =
session.Query<Order>().Where(x => x.States.OrderByDescending(z => z.Date).FirstOrDefault().Name == "2").ToList();
}
}
public class Order
{
public virtual int Id { get; set; }
public virtual IList<OrderState> States { get; set; }
public virtual string Name { get; set; }
}
public class OrderState
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime Date { get; set; }
public virtual Order Order { get; set; }
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Id(x => x.Id)
.GeneratedBy.Identity();
HasMany(x => x.States)
.Inverse()
.AsBag()
.KeyColumn("OrderId");
Map(x => x.Name)
.Not.Nullable();
Table("Orders");
}
}
public class OrderStateMap : ClassMap<OrderState>
{
public OrderStateMap()
{
Id(x => x.Id)
.GeneratedBy.Identity();
References(x => x.Order)
.Column("OrderId");
Map(x => x.Name)
.Not.Nullable();
Map(x => x.Date)
.Not.Nullable();
Table("OrderStates");
}
}

After some time research I found solution:
var res = (from i in session.Query<Order>()
where ((from s in i.States
orderby s.Date descending
select s.Name).First()) == "2"
select i).ToList();

Related

ASP API CORE : Get relation Many to One

I have two entities : Article and PrixVariation.
One Article has Many Prix variation.
One Prix variation has One Article.
public partial class Article
{
public Article()
{
PrixVariations = new HashSet<PrixVariation>();
}
public int Id { get; set; }
public DateTime DateTime { get; set; }
...
public virtual ICollection<PrixVariation> PrixVariations { get; set; }
}
public partial class PrixVariation
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public int Article { get; set; }
public double Prix { get; set; }
public virtual Article ArticleNavigation { get; set; } = null!;
}
My Context is as follow :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>(entity =>
{
entity.ToTable("articles");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
});
modelBuilder.Entity<PrixVariation>(entity =>
{
entity.ToTable("prix_variation");
entity.Property(e => e.Id)
.ValueGeneratedNever()
.HasColumnName("id");
entity.Property(e => e.Article).HasColumnName("article");
entity.Property(e => e.DateTime)
.HasColumnType("datetime")
.HasColumnName("dateTime")
.HasDefaultValueSql("(getdate())");
entity.Property(e => e.Prix).HasColumnName("prix");
entity.HasOne(d => d.ArticleNavigation)
.WithMany(p => p.PrixVariations)
.HasForeignKey(d => d.Article)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_prix_variation_articles");
});
And my controller is as follow :
public class ArticlesController : ControllerBase
{
private readonly STGEORGESContext _context;
public ArticlesController(STGEORGESContext context)
{
_context = context;
}
// GET: api/Articles
[HttpGet]
public async Task<ActionResult<IEnumerable<Article>>> GetArticles()
{
return await _context.Articles.ToListAsync();
}
....
There is something not working here because when I launch the debogger, the collection of PrixVaration is always empty :
{"id":1,"dateTime":"2021-11-28T08:37:17","prixVariations":[]}
And of course in the database there is one PrixVaration linked to that Article..
Can anyone can help ?? Thaks a lot !!!
Pierre
It is called lazy loading. Ef doesn't load any object collections if you don't ask about it explicitly. So try this
return await _context.Articles.Include(i=> i.PrixVariations).ToListAsync();

Ignore Soft-Deleted records within graph

Using EF Core
Assuming Structure:
class Parent
{
public int Id { get; set; }
public List<Child> Children { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
class Child
{
public int Id { get; set; }
List<GrandChild> Grandchildren { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
public int ParentId { get; set; }
}
class Grandchild
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
public int ChildId { get; set; }
}
Records could have been soft-deleted at any level, although no descendant of a soft-deleted entity can itself not be soft-deleted (obviously).
List<Parent> nonDeletedParents = Context.Parents
.Include(p => p.Children).ThenInclude(c => c.Grandchildren)
.Where(p => !p.IsDeleted)
.ToList();
will get me non-deleted parents, but how do I exclude deleted children and grandchildren in a single query to the database?
One approach I have explored is to use an anonymous return type and then rebuild:
var result = await Context.Parents
.Where(p => !p.IsDeleted)
.Select(p =>
{
Parent = p,
AllChildren = p.Children
.Where(c => !c.IsDeleted)
.ToList(),
AllGrandchildren = p.Children
.Where(c => !c.IsDeleted)
.SelectMany(c => c.Grandchildren)
.Where(g => !g.IsDeleted)
.ToList()
}).ToList();
List<Parent> actualResults = new List<Parent>();
results.ForEach(r =>
{
Parent actualResult = r.Parent;
actualResult.Children = r.AllChildren;
actualResult.Children.ForEach(c =>
{
c.Grandchildren = r.AllGrandchildren.Where(g => g.ChildId == c.Id).ToList();
});
actualResults.Add(actualResult);
}
return actualResults;
But, seems pretty inelegant. Is there a preferable approach?

AutoMapper setting all properties to NULL on destination

In an ASP.NET Core 1.1 Web API, I am trying to map an entity model to a DTO using AutoMapper.
The entity model:
namespace InspectionsData.Models
{
[Table("property")]
public class Property
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("property_id")]
public int? Id { get; set; }
[Column("show_inventory")]
public bool ShowInventory { get; set; }
[Column("latitude")]
public double? Latitude { get; set; }
[Column("longitude")]
public double? Longitude { get; set; }
[Column("property_type_id")]
public int? PropertyTypeId { get; set; }
[ForeignKey("PropertyTypeId")]
[Display(Name = "Property Type")]
public PropertyType PropertyType { get; set; }
[Column("inspection_frequency_id")]
public int? InspectionFrequencyId { get; set; }
[ForeignKey("InspectionFrequencyId")]
[Display(Name = "Inspection Frequency")]
public InspectionFrequency InspectionFrequency { get; set; }
[Column("group_id")]
public int? GroupId { get; set; }
[ForeignKey("GroupId")]
[Display(Name = "Group")]
public Group Group { get; set; }
[Column("added_by_id")]
public int? AddedById { get; set; }
[ForeignKey("AddedById")]
[Display(Name = "Added By")]
public virtual User AddedBy { get; set; }
[Column("added_date")]
[Display(Name = "Added Date")]
public DateTime AddedDate { get; set; }
[Column("deleted_by_id")]
public int? DeletedById { get; set; }
[ForeignKey("DeletedById")]
[Display(Name = "Deleted By")]
public virtual User DeletedBy { get; set; }
[Column("deleted_date")]
[Display(Name = "Deleted Date")]
public DateTime? DeletedDate { get; set; }
}
and the DTO:
namespace InspectionsData.DTOs
{
public class PropertyDto
{
public int? Id { get; set; }
public bool ShowInventory { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public PropertyType PropertyType { get; set; }
public InspectionFrequency InspectionFrequency { get; set; }
public DateTime NextInspectionDate { get; set; }
}
}
The mapping is done in a configuration file:
public class AutoMapperProfileConfiguration : Profile
{
public AutoMapperProfileConfiguration()
{
// Add as many of these lines as you need to map your objects
var map = CreateMap<Property, PropertyDto>();
map.ForAllMembers(opt => opt.Ignore());
map.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id));
map.ForMember(dest => dest.ShowInventory, opt => opt.MapFrom(src => src.ShowInventory));
map.ForMember(dest => dest.Latitude, opt => opt.MapFrom(src => src.Latitude));
map.ForMember(dest => dest.Longitude, opt => opt.MapFrom(src => src.Longitude));
map.ForMember(dest => dest.PropertyType, opt => opt.MapFrom(src => src.PropertyType));
map.ForMember(dest => dest.InspectionFrequency, opt => opt.MapFrom(src => src.InspectionFrequency));
}
}
And the setting up of AutoMapper in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperProfileConfiguration());
});
var mapper = config.CreateMapper();
services.AddSingleton(mapper);
}
In my controller action, I execute the mapping:
[HttpGet]
public async Task<IActionResult> GetProperty()
{
var properties = _context.Property
.Include(t => t.PropertyType)
.Include(f => f.InspectionFrequency)
.Where(a => a.DeletedDate == null && a.GroupId == 1);
var propertiesDto = _mapper.Map<IEnumerable<PropertyDto>>(properties);
return Ok(propertiesDto);
}
It doesn't give an error, but all the properties in all the objects in the propertiesDto list are default values (NULL for objects and nullable types, FALSE for booleans, 0 for integers, etc.) Any ideas where I'm going wrong?
It's because the following line
map.ForAllMembers(opt => opt.Ignore());
is letting AM ignore all member mappings, including the ones you have configured explicitly.
Simply use ForAllOtherMembers instead:
map.ForAllOtherMembers(opt => opt.Ignore());

Wrong column type - Found varchar2, Expected NVARCHAR2(255)

i have a mapping configuration for a composite-key using fluent nhibernate and i need some help, because it is not working.
I have one class called Agencia that have a composite key for two Int16 collumns that are mapped as number on a oracle 11g database.
[Serializable]
public class AgenciaPK
{
[DisplayName("Código")]
public virtual System.Int16 CdAgencia { get; set; }
public virtual System.Int16 CdOrgaoArrecad { get; set; }
public override int GetHashCode()
{
int hashCode = 0;
hashCode = hashCode ^ CdAgencia.GetHashCode() ^ CdOrgaoArrecad.GetHashCode();
return hashCode;
}
public override bool Equals(object obj)
{
var toCompare = obj as Agencia;
if (toCompare == null)
{
return false;
}
return (this.GetHashCode() != toCompare.GetHashCode());
}
}
public class Agencia : AgenciaPK
{
public virtual OrgaoArrecad OrgaoArrecad { get; set; }
[DisplayName("Nome")]
public virtual string NmAgencia { get; set; }
[DisplayName("Digito")]
public virtual string NrDigitoAgencia { get; set; }
[DisplayName("Número")]
public virtual string NrAgencia { get; set; }
[DisplayName("Descrição")]
[StringLength(20)]
public virtual string DsCompl { get; set; }
[DisplayName("Logradouro")]
public virtual string NmLograd { get; set; }
[DisplayName("Bairro")]
public virtual string NmBairro { get; set; }
public virtual System.Nullable<short> IdSituacAgencia { get; set; }
[DisplayName("Cep")]
public virtual System.Nullable<short> NrCep { get; set; }
}
I have another class called Convenio that have a composite key composed by one string collumn (CdConvenio) and another is int CdOrgaoArrecad.
[Serializable]
public class ConvenioPK
{
[DisplayName("Código")]
[StringLength(20)]
public virtual string CdConvenio { get; set; }
[DisplayName("Cód. Órgão arrecadador")]
public virtual System.Int16 CdOrgaoArrecad { get; set; }
public override int GetHashCode()
{
int hashCode = 13;
if (CdConvenio != null)
hashCode = hashCode ^ CdConvenio.GetHashCode() ^ CdOrgaoArrecad.GetHashCode();
return hashCode;
}
public override bool Equals(object obj)
{
var toCompare = obj as Convenio;
if (toCompare == null)
{
return false;
}
return (this.GetHashCode() != toCompare.GetHashCode());
}
}
public class Convenio : ConvenioPK
{
[DisplayName("Órgão arrecadador")]
public virtual OrgaoArrecad OrgaoArrecad { get; set; }
[DisplayName("Data início")]
[DataType(DataType.Date)]
public virtual System.DateTime DtInicioConvenio { get; set; }
[DisplayName("Data término")]
[DataType(DataType.Date)]
public virtual System.DateTime DtFinalConvenio { get; set; }
[DisplayName("Tipo")]
public virtual short TpConvenio { get; set; }
[DisplayName("Tarifa")]
public virtual short TpTarifaConvenio { get; set; }
[DisplayName("Valor tarifa")]
public virtual Double VlTarifaConvenio { get; set; }
[DisplayName("Quantidade dias")]
public virtual short QtDiasFloat { get; set; }
}
And when i access this class Agencia using this mapping class AgenciaMap, it works fine.
public class AgenciaMap : FluentNHibernate.Mapping.ClassMap<Agencia>
{
public AgenciaMap() {
Table("SCI_AGENCIA");
CompositeId()
.KeyProperty(x => x.CdAgencia, "CD_AGENCIA")
.KeyProperty(x => x.CdOrgaoArrecad, "CD_ORGAO_ARRECAD");
Map(x => x.CdOrgaoArrecad).Column("CD_ORGAO_ARRECAD").Not.Nullable();
Map(x => x.NmAgencia).Column("NM_AGENCIA").CustomType("AnsiString").Length(30).Not.Nullable();
References<OrgaoArrecad>(x => x.OrgaoArrecad, "CD_ORGAO_ARRECAD").ForeignKey().Not.Update()
.Not.Insert()
.Cascade.None().Not.Nullable().Not.LazyLoad().Fetch.Join();
}
}
But, when i try to access the class Convenio, using the class ConvenioMap:
public class ConvenioMap : FluentNHibernate.Mapping.ClassMap<Convenio>
{
public ConvenioMap() {
Table("CONVENIO");
CompositeId()
.KeyProperty(x => x.CdConvenio, "CD_CONVENIO")
.KeyProperty(x => x.CdOrgaoArrecad, "CD_ORGAO_ARRECAD");
Map(x => x.CdConvenio).Column("CD_CONVENIO").CustomSqlType("varchar2(20)").CustomType("AnsiString").Length(20).Not.Nullable();
Map(x => x.CdOrgaoArrecad).Column("CD_ORGAO_ARRECAD").Not.Nullable();
Map(x => x.DtInicioConvenio).Column("DT_INICIO_CONVENIO").CustomSqlType("DATE").Not.Nullable();
Map(x => x.DtFinalConvenio).Column("DT_FINAL_CONVENIO").CustomSqlType("DATE").Not.Nullable();
Map(x => x.TpConvenio).Column("TP_CONVENIO").CustomSqlType("NUMBER(2,0)").Not.Nullable();
Map(x => x.TpTarifaConvenio).Column("TP_TARIFA_CONVENIO").CustomSqlType("NUMBER(2,0)").Not.Nullable();
Map(x => x.VlTarifaConvenio).Column("VL_TARIFA_CONVENIO").CustomSqlType("NUMBER(15,2)").Not.Nullable();
Map(x => x.QtDiasFloat).Column("QT_DIAS_FLOAT").CustomSqlType("NUMBER(3,0)").Not.Nullable();
References(x => x.OrgaoArrecad)
.Column("CD_ORGAO_ARRECAD")
.ForeignKey().Not.Update()
.Not.Insert()
.Cascade.None()
.Not.Nullable()
.Not.LazyLoad()
.Fetch.Join();
}
}
When i map a collumn that is the type string "CD_CONVENIO", i'm getting this error:
Wrong column type in SCI.CONVENIO for column CD_CONVENIO. Found: varchar2, Expected NVARCHAR2(255)
The specific part of my code that i'm in doubt is this:
CompositeId()
.KeyProperty(x => x.CdConvenio, "CD_CONVENIO")
.KeyProperty(x => x.CdOrgaoArrecad, "CD_ORGAO_ARRECAD");
So, here is my question: What am i doing wrong?

NHibernate LINQ query throws error "Could not resolve property"

I'm testing out using LINQ with NHibernate but have run into some problems with resolving string.length. I have the following
public class DC_Control
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual bool IsEnabled { get; set; }
public virtual string Url { get; set; }
public virtual string Category { get; set; }
public virtual string Description { get; set; }
public virtual bool RequireScriptManager { get; set; }
public virtual string TriggerQueryString { get; set; }
public virtual DateTime? DateAdded { get; set; }
public virtual DateTime? DateUpdated { get; set; }
}
public class DC_ControlMap : ClassMap<DC_Control>
{
public DC_ControlMap()
{
Id(x => x.ID);
Map(x => x.Name).Length(128);
Map(x => x.IsEnabled);
Map(x => x.Url);
Map(x => x.Category);
Map(x => x.Description);
Map(x => x.RequireScriptManager);
Map(x => x.TriggerQueryString);
Map(x => x.DateAdded);
Map(x => x.DateUpdated);
}
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2008)
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(c => c.SetProperty("connection.connection_string", "CONNSTRING"))
.ExposeConfiguration(c => c.SetProperty("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory,NHibernate.ByteCode.Castle"))
.BuildSessionFactory();
}
public static void test()
{
using (ISession session = sessionFactory.OpenSession())
{
var sqlQuery = session.CreateSQLQuery("select * from DC_Control where LEN(url) > 80").AddEntity(typeof(DC_Control)).List<DC_Control>();
var linqQuery= session.Linq<DC_Control>().Where(c => c.Url.Length > 80).ToList();
}
}
In my test method I first try and perform the query using SQL, this works just fine. Then I want to do the same thing in LINQ, and it throws the following error:
NHibernate.QueryException: could not resolve property: Url.Length of: DC_Control
I've searched alot for this "could not resolve property" error, but I can't quite figure out, what this means. Is this because the LINQ implementation is not complete? If so it's a bit disappointing coming from Linq2Sql where this would just work.
I also tried it setting up the mapping with a hbm.xml instead of using FluentNHibernate but it produced teh same error.
String.Length projections are not supported by the old (2.x) Linq provider.
The new Linq provider in NHibernate 3.x is integrated; you don't have to download anything extra.
The new extension method is session.Query instead of session.Linq; if you're using the latter, you are still using the old provider.

Resources