Nhibernate and MVC. Null object from query - c#

So I am rather new to using NHibernate for database access and after studying its usage elsewhere in an application I am editing, I cannot seem to get it to work for me and I do not know why. Effectively, I am trying to populate an object with data from my database so that I can pull pieces in and present them to the user. The issue is that despite my syntax and code looking correct, my object remains null after query execution.
The class that is being used to represent the table in the database:
public class AllocateLog
{
public virtual string UserName { get; set; }
public virtual int Id { get; set; }
public virtual int OwnerId { get; set; }
public virtual int MemberId { get; set; }
public virtual int? ResId { get; set; }
public virtual string RequestComments { get; set; }
public virtual DateTime DateEntered { get; set; }
public virtual DateTime? DateExited { get; set; }
public virtual string EntryAccessPoint { get; set; }
public virtual string ExitAccessPoint { get; set; }
}
The mapping code:
public class AllocateLogOverride : IAutoMappingOverride<AllocateLog>
{
public void Override(AutoMapping<AllocateLog> map)
{
#if LOCAL_INSTALL
map.Schema("dbo");
#else
map.Schema("cred");
#endif
map.Table("allocate_log");
map.CompositeId()
.KeyProperty(x => x.OwnerId, "owner_id")
.KeyProperty(x => x.MemberId, "member_id")
.KeyProperty(x => x.DateEntered, "date_entered");
map.Map(x => x.UserName).Column("user_name");
map.Map(x => x.ResId).Column("res_id");
map.Map(x => x.RequestComments).Column("request_comments");
map.Map(x => x.DateExited).Column("date_exited");
map.Map(x => x.EntryAccessPoint).Column("entry_access_point");
map.Map(x => x.ExitAccessPoint).Column("exit_access_point");
}
}
The query code:
public class AllocateLogsForAccessPoints : IQuery<IQueryOver<AllocateLog>, AllocateLog>
{
private readonly string accessPoint;
public AllocateLogsForAccessPoints(string accessPoint)
{
this.accessPoint = accessPoint;
}
public IQueryOver<AllocateLog> BuildQuery(ISession session)
{
return session.QueryOver<AllocateLog>()
.Where(d => d.EntryAccessPoint == accessPoint);
}
public AllocateLog Execute(IQueryOver<AllocateLog> query)
{
return query.SingleOrDefault();
}
}
And the code that I am using just as a test to see if my query will return anything to my object:
var asdf = DbQueryExecutor.ExecuteQuery(new AllocateLogsForAccessPoints((string)"north gate"));
There is only one record in the database that fits that query as it is the only row in the database with any data in entry_access_point and the string is "north gate". The table is cred.allocate_log and all of the columns are named correctly in the mapping file. Furthermore, removal of the mapping file or rather commenting it out does not result in any runtime error which means to me that NHibernate never even tries to use the mapping file because if I try to do this (meaning commenting out the contents of the file) with any other mapping file who's query works, I get a runtime error. So I am entirely stumped as to why my object remains null after executing the query which runs without error. Any ideas? I will update my original post if you require more information.

You might try downloading an application called NHProf from Hibernating Rhinos. It traces all nHibernate calls and shows you exactly the SQL that nHibernate is trying to run. It has been a godsend for me in trying to figure out just what the hell nHibernate is trying to do.

The answer to my issue was to check the namespace and location of the various files associated with the query. I had originally put one of the files in the wrong folder and so the namespace was mapped to that incorrect folder. I moved the file into the correct folder but never changed the namespace. So in the override file I had a reference to the folder the namespace said it was part of when in actuality it was not. Correcting these errors was the solution.

Related

Getting an 'Cannot create an instance of an interface'-error with EF Plus IncludeFilter

Having an IList<Guid>, I wish to locate all matches from the context and then include a number of related tables, where outdated data are not included.
Because of the size of the data, I try to use EF Plus IncludeFilter, to avoid loading all to memory and perform the filtering there.
The problem occurs when I call ToListAsync() on the query. IncludeFilter (as far as I can see) then throws a System.MissingMethodException : Cannot create an instance of an interface exception.
The project is done in .NET Core 2.2, and I using Z.EntityFramework.Plus.EFCore 2.0.7
Sample project
This sample recreates the issue: https://dotnetfiddle.net/MYukHp
Data structure
The database structure centers around the Facts table which contains an immutable Guid, identifing each entry. Entries in all other tables link to this guid to bind together to a single entry. The other tables contain a DateTime ValidTo to track changes. No entries are ever updated or deleted. Instead, on change a new entry is made with ValidTo = DateTime.MaxValue, and the entry being updated has it's ValidTo set to DateTime.Now.
This ensures that all changes are preserved historically.
Over time, the vast majority of data will be historical, so it's crucial that we can filter this out in the SQL query.
The data structure for the Fact-table is like this:
public class FactModel
{
public Guid FactId { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
// Navigation properties
public IEnumerable<PersonModel> Persons { get; set; }
// Repeat for all other tables
}
All other tables inherits from a ModelBase, linking them to the Fact table.
public class ModelBase
{
public Guid FactId { get; set; } // Link to the Fact
public FactModel Fact { get; set; } // Navigation property
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; } // ValidTo == DateTime.MaxValue -> active record
}
Example tables for Person and Patient
public class PersonModel : ModelBase
{
public Guid PersonId { get; set; } // Key - A new is created on every update
public string FirstName { get; set; } // data
public string LastName { get; set; } // data
}
public class PatientModel : ModelBase
{
public Guid PatientId { get; set; } // Key - A new is created on every update
public Guid ActiveCompanyId { get; set; } // Data
public int HealthInsuranceGroup { get; set; } // Data
public PatientStatusType Status { get; set; } // Data
}
Changing the parameter to IQueryable produces a new error:
System.InvalidCastException : Unable to cast object of type System.Guid' to type 'System.String'
Call sequense
The call sequence is rather complex, but simplified we start by declaring the call parameter IQueryable<FactModel> facts. This is filtered by only adding Patients from the company the user is logged into. Then the search term is applied. Finally, the parameter is transformed into a list containing only the need guids, before calling AssignDataToPatientByFactId.
// Performing a search for an Address
IQueryable<FactModel> facts = null;
facts = _context.Facts.AsNoTracking().Where(p => p.Patients.Any(a => a.ActiveCompanyId == _identificationHandler.Identification.CompanyId));
facts = facts.AsNoTracking().Where(p => p.Addresses.Where(z => z.ValidTo == DateTime.MaxValue).Any(q => q.Street.Any() && q.Street.StartsWith(searchDTO.SearchString)));
return await AssignDataToPatient(facts.Select(x => x.FactId).ToList()), cancel);
public async Task<List<FactModel>> AssignDataToPatientByFactId(IList<Guid> factIds, CancellationToken cancel)
{
return await _context.Facts.Where(x => factIds.Contains(x.FactId))
.IncludeFilter(x => x.Patients.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Persons.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Communications.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Addresses.Where(c => c.ValidTo == DateTime.MaxValue))
.ToListAsync(cancel);
}
So AssignDataToPatientByFactId takes a list of guids, finds all matching in the Facts-table and then adds the entries from the other tables where the ValidTo timestamp is Max. So all other entries should not be included.
Separating the code into several statements reveal that IncludeFilter seems to be working, but calling ToListAsync produces the error.
Disclaimer: I'm the owner of the project Entity Framework Plus
The version v2.0.8 has been released fixing this issue.
The issue was caused because the class Fact was using IEnumerable<T> properties which were not yet supported by our library.
The Fiddle is now working as expected: https://dotnetfiddle.net/MYukHp

How to use twice a reference to another table?

Considering the following classes:
public class Unidade
{
public int UnidadeId { get; set; }
public string Apelido { get; set; }
public string Descricao { get; set; }
}
And
public class Estrutura
{
public int Id { get; set; }
…
public int UnidadeId { get; set; }
public virtual Unidade Unidade { get; set; }
…
public int UnidadeCompraId { get; set; }
public virtual Unidade UnidadeCompra { get; set; }
…
}
The query Estruturas.Single(e => e.Id == 120898).Unidade.Descricao will return an error, actually because Estruturas.Single(e => e.Id == 120898).Unidade is null.
The Id (120898) used in this example is valid as there is a valid value of UnidadeId set.
What’s wrong? How can I access de value of Descricao having a valid Estrura?
In C# 6:
struturas.Single(e => e.Id == 120898).Unidade?.Descricao
you will gen null if Unidade is null
The problem is that you have lazy loading turned off. Though that is probably better. The issue then is you need to .Include before calling .Single or it won't fetch the other table data.
Edit
Either that or the Foreign Key to this other table is either not defined at all or setup on the wrong column. Thus, it ends up trying to link the wrong 2 pieces of data and you end up getting no data found there.
An easy way to test this is to simply do this:
string sql = Estruturas.Where(e => e.Id == 120898).Include(a => a.Unidade).ToString();
This will show you the SQL that EF will run, minus the actual parameter value. Make sure this shows up how you would expect it to look. You can even just run this query in Sql Server directly with the parameter filled in to make sure you get back the data, too.
Also, you have to have the [Key] defined somewhere for the tables. The Foreign Key setup assumes it links back to the PK of the other table only.

NullReferenceException Query SQLite database with Where on a concatenated string property

I'm trying to select a record using the following code:
Location item = connection
.Table<Location>()
.Where(l => l.Label.Equals(label))
.FirstOrDefault();
This results in:
System.NullReferenceException: Object reference not set to an instance of an object.
When I try the same, on a different property (Postcode), it all works fine also when no records are found.:
Location item = connection
.Table<Location>()
.Where(l => l.Postcode.Equals(label))
.FirstOrDefault();
This is the Location Class:
// These are the Locations where the Stock Take Sessions are done
public class Location : DomainModels, IComparable<Location>
{
[JsonProperty("id"), PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public string Street { get; set; }
public int Number { get; set; }
public string Postcode { get; set; }
public string City { get; set; }
public bool Completed { get; set; }
[Ignore] // Removing this does not have an impact on the NullReferenceException
public string Label => $"{Name ?? ""} - ({Postcode ?? ""})";
public int CompareTo(Location other)
{
return Name.CompareTo(other.Name);
}
// Navigation property
// One to many relationship with StockItems
[OneToMany(CascadeOperations = CascadeOperation.All), Ignore]
public List<StockItem> StockItems { get; set; }
// Specify the foreign key to StockTakeSession
[ForeignKey(typeof(StockTakeSession))]
public int StockTakeSessionId { get; set; }
// One to one relationship with StockTakeSession
[OneToOne]
public StockTakeSession StockTakeSession { get; set; }
}
What am I doing wrong?
Thanks for any suggestions!
Your where filters in the data store on Label but your markup on your class Location has decorated the Label property with IgnoreAttribute. This means the Label property will not be set until after the entity has been materialized to memory and you can't do anything with it in the data store.
.Where(l => l.Label.Equals(label))
Fixes
There are some options.
You could set this to computed and create a computed column in the store with that same logic. This involves manually changing your table schema either directly in your RDBMS manager or editing your migration scripts. The property gets marked with [DatabaseGenerated(DatabaseGeneratedOption.Computed)] (if using attributes, which your code above is).
You could change the Where to filter on the Properties that compose Label that are found in the store. ie: .Where(l => l.Postcode.Equals(Postcode) && l.Name.Equals(Name))
You could materialize everything before that particular filter to memory and then apply the filter. This is not recommended if everything up to that point leads to a lot of records. Example, with the code below if the table is large you would be retrieving everything for a single record.
Location item = connection
.Table<Location>()
.AsEnumerable()
.Where(l => l.Label.Equals(label))
.FirstOrDefault();
Edit
[Ignore] // Removing this does not have an impact on the NullReferenceException
No, it should not unless you go through and add the column with the same name to your existing schema and populate it with all data. (or create a computed column in your schema with the same name)

Access count of related records of an entity Entity Framework

I have two models:
public class HouseType
{
public int Id { get; set; }
public string TypeName { get; set; }
public virtual IEnumerable<HouseModel> HouseModels { get; set; }
}
and
public class HouseModel
{
public int Id { get; set; }
public string ModelName { get; set; }
[DisplayFormat(DataFormatString = "{0:n2}")]
public double StandardPrice { get; set; }
[ForeignKey("HouseType")]
public int HouseTypeID { get; set; }
public virtual HouseType HouseType { get; set; }
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
}
I am returning a JSON result, so as expected I cannot manipulate it in a view, because the display is handled by a javascript file that I made.
I am trying to retrieve the number of HouseModel that is contained by HouseType. I have tried:
db.HouseTypes.Select(h => new
{
HouseCount = h.HouseModels.Count()
}).ToList();
But Entity Framework complains about it. How can I access the count of related records inside an entity? Any help will be much appreciated. Thanks.
Use
public virtual ICollection<HouseUnit> HouseUnits { get; set; }
instead of
public virtual IEnumerable<HouseUnit> HouseUnits { get; set; }
Hope this helps.
Simply speaking, the trouble is that EF is trying to execute the .Select() statement on the db server but, of course, the db server does not know how to create a new object.
You first need to bring back the counts then create your objects so something like this should work better:
var listOfCounts = db.HouseTypes
.Select(h => h.HouseModels.Count())
.ToList()
.Select(c => new
{
HouseCount = c
})
.ToList();
in this example when the first .ToList() is executed the db needs only return a set of numbers (the counts of HouseModels in each HouseType) then we have a List<int> in local memory from which we can create our objects with the second Select statement.
As an aside...
It wasn't part of your original question but maybe you'd want to consider a dictionary rather than a list so you have some means of identifying which count of HouseModels belonged to each HouseType? in which case we could do something like:
Dictionary<int,string> houseModelCounts = db.HouseTypes
.ToDictionary(h => h.Id, h => h.HouseModels.Count());
which would give a dictionary keyed with the HouseType Id with values for the count of HouseModels in each type. I don't know your context though so maybe unnecessary for you?

NHibernate - Fetch referenced column value

I have this NHibernate model:
public class RootTable
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual DateTime? Start { get; set; }
public virtual DateTime? Finish { get; set; }
public virtual IList<Leaf1> ChildCollection1 { get; set; }
}
public class Leaf1
{
public virtual int ID { get; set; }
public virtual string Info1 { get; set; }
public virtual string Info2 { get; set; }
public virtual RootTable Parent { get; set; }
}
public class RootMapping : ClassMap<RootTable>
{
public RootMapping()
{
Table("RootTable");
Id(c => c.Name);
Map(c => c.Description, "Desc").Length(20);
Map(c => c.Start).Length(20);
Map(c => c.Finish).Length(20);
HasMany(c => c.ChildCollection1)
.Cascade.All()
.LazyLoad()
.Inverse();
}
}
public class Leaf1Mapping : ClassMap<Leaf1>
{
public Leaf1Mapping()
{
Table("LeafTable1");
Id(c => c.ID, "RowID").GeneratedBy.Identity();
Map(c => c.Info1).Length(20);
Map(c => c.Info2).Length(20);
References(c => c.Parent).Column("Parent").LazyLoad();
}
}
What I'm trying to do is access the value of the referenced column in Leaf1 without lazyloading RootTable.
In otherwords, I have this:
this.LogMessage("Loading leaves...");
var allleafs = session.CreateCriteria(typeof(Leaf1)).List<Leaf1>();
this.LogMessage("Loaded leaves...");
var leaf = allleafs[0];
this.LogMessage("Leaf metadata:");
// This causes a lazy-load of the RootTable object.
this.LogMessage("Leaf parent is " + leaf.Parent);
Now what I actually want is the value of "Parent" as it's stored in the underlying database - I don't care for the parent object, I don't want to load it, all I want to do get the raw value. I can't access the field that contains the value (i.e., leaf.Parent.Name) as I want this to work in a generic fashion...
[Background]
Ultimately this is plugging into an auditing framework that uses an NHibernate interceptor, so this needs to work in a generic way so that for any object passed in I can report on the changed values. It's entirely possible the child node will have changed with no change to the root node, so when the interceptor's OnFlushDirty() is called, I do not want the interceptor to cause a lazy-load of other objects.
I know I can reference the parent property directly (e.g., I can say "leaf.Parent.Name") and this will get me the value without the lazy load, but there doesn't seem to be a quick way to determine that "Name" is the key property I want to return.
[Edited to add...]
Walking the tree doesn't seem to work as I get a null reference exception:
var theType = leaf.Parent.GetType();
// This line returns a NULL due to the proxy class.
var metadata = factory.GetClassMetadata(theType);
var idProp = metadata.IdentifierPropertyName;
var prop = theType.GetProperty(idProp);
var val = prop.GetValue(leaf.Parent, null);
this.LogMessage("Leaf parent is " + val);
Now, theType comes back as RootTableProxy, so is just a placeholder because the main class isn't loaded. Which means metadata is null as there is no class metadata and thus idProp fails with a null reference exception.
So I can't actually see how to get referenced column value without a lazy load somewhere along the way: surely this can't be right?
Edited to add (more!)
I thought an easy solution have been found by using session.GetIdentifier(). However this doesn't seem to work in all cases: in an interceptor calling session.GetIdentifier(state[i]) on some objects caused an exception stating that the object wasn't part of the current session, so still looking for a more reliable solution that doesn't resort to reflection. Any ideas welcome...
What about adding another property to your Leaf1 Class.
e.g.
public class Leaf1
{
public virtual int ID { get; set; }
public virtual string Info1 { get; set; }
public virtual string Info2 { get; set; }
public virtual RootTable Parent { get; set; }
public virtual int ParentId { get; set; }
}
and then map is as readonly
public class Leaf1Mapping : ClassMap<Leaf1>
{
public Leaf1Mapping()
{
Table("LeafTable1");
Id(c => c.ID, "RowID").GeneratedBy.Identity();
Map(c => c.Info1).Length(20);
Map(c => c.Info2).Length(20);
References(c => c.Parent).Column("Parent").LazyLoad();
Map(c => c.ParentId).Column("Parent").ReadOnly();
}
}
You could make your entities implement interfaces (would be cleaner), but if you want to get the actual type of the proxy, use NHibernateUtil.GetClass(proxy) which will return the underlying type that you're looking for
It transpires that ISession has a method GetIdentifier which returns the cached value of a property. Taking the example from my original post, the code changes to this:
this.LogMessage("Loading leaves...");
var allleafs = session.CreateCriteria(typeof(Leaf1)).List<Leaf1>();
this.LogMessage("Loaded leaves...");
var leaf = allleafs[0];
this.LogMessage("Leaf metadata:");
this.LogMessage("Leaf parent is " + session.GetIdentifier(leaf.Parent));
Now in testing this seems to be working nicely, and can be called from OnFlushDirty within an interceptor on both currentState and previousState objects.
I've been trying to find more information on GetIdentifier, but the NH documentation is sadly lacking, and there seems to be few, if any, blog posts that mention this. If anyone is aware of any caveats, problems, or "funnies" that I need to know aobut in relation to this method, I'd be more than happy to hear about them...
[Edited to add]
...as it turns out, this doesn't work if the object isn't a proxy object(!). Still looking for a reliable method to do this that doesn't involve reflection or decorating objects with attributes to identify fields.

Categories

Resources