NullReferenceException Query SQLite database with Where on a concatenated string property - c#

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)

Related

Are DBSet<> object's ids set when assigning one to the other?

I have two entities with relation one-many in DB and corresponding classes generated by reverse engineering the database.
public partial class Assets
{
public int AssetsId { get; set; }
public string Description { get; set; }
public int? PersonId { get; set; }
public virtual Persons Person { get; set; }
}
public partial class Persons
{
public Persons()
{
Assets = new HashSet<Assets>();
}
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Assets> Assets { get; set; }
}
When I assign one entity to the other do the corresponding ID's are set automatically (i am curious if they are set before SaveChanges is called, so i could use it without committing all current changes).
static void Main(string[] args)
{
Context c = new Context();
var a = new Assets();
a.AssetsId = 1;
var p = new Persons();
p.PersonId = 2;
c.Add(a);
c.Add(p);
a.Person = p; //does it set a.PersonId = 2
p.Assets.Add(a); //does it set a.PersonId = 2
c.SaveChanges();
}
(You can just check the answer to your question in the debugger or with a simple test using your provided code.)
When I assign one entity to the other do the corresponding ID's are set automatically [before SaveChanges is called later?]
No, they are not. The reason is, that your navigation properties here are really just POCO properties (meaning simple .NET properties). The Assets.Persons property is really just a property of type Persons and the Persons.Assets property contains really just the HashSet<Assets> instance that you assigned in the constructor. There is no magic here.
The IDs are being synchronized automatically during change tracking, which happens for example when SaveChanges() is called, but can also manually be triggered by calling context.ChangeTracker.DetectChanges().
As a side note, be aware that you should not set IDs in your code explicitly, if you are using identity/auto increment columns for primary keys, because this can lead to exceptions when saving (depending on the database server being used), due to EF Core trying to insert the explicitly assigned ID to the identity/auto increment column (e.g. this is not allowed by default for SQL Server).

EntityFramework error when saving record with list of child records

In my ASP.NET Core we're using EF Core. When saving a record that has a list of child records we get the following error:
The instance of entity type Child cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
Step-by-step:
1. fetch record from the controller and pass it to the view (view has a )
2. update some properties via the html form
3. click save
4. catch model passed in the controller Save method. Get the original item from DB and save changes (as made via the form)
5. call update/save in the repository
Simplified code below:
// Get the record from the database
var record = _dbContext.Parents
.Include(x => p.SomeOtherObject)
.Include(x => x.ListChildren)
.FirstOrDefault(x => x.IdParent == id);
// then we do some changes to Parent and ListChildren
// we do not do any changes to SomeOtherObject!!!
// save changes
_dbContext.Update(record);
_dbContext.SaveChanges();
// definition of entities
public class Parent
{
public int IdParent { get; set; }
public string Name {get; set;}
public string Surname {get; set;}
public int IdSomeOtherObject { get; set; }
[ForeignKey("IdSomeOtherObject")]
public virtual SomeOtherObject SomeOtherObject { get; set; }
public virtual List<Child> ListChildren { get; set; }
}
public class Child
{
public int IdChild { get; set; }
public int IdParent { get; set;}
public string Name { get; set; }
}
public class SomeOtherObject
{
public int IdSomeOtherObject { get; set; }
public string PropertiesBlahBla { get; set; }
}
Now, I know that we can add .AsNoTracking() to the Get operation, but then the problem is that when saving Parent EntityFramework will perform and UPDATE SQL statement even for the SomeOtherObject (that was not changed in any way) and that is not acceptable for our data/input scenario.
Is there any other way to get pass this error?
Try removing _dbContext.Update(record);. Your entities should already be tracked, so changes should be saved.
As Update docs state:
Begins tracking the given entity and entries reachable from the given entity using the Modified state by default
So it seems that in this scenario it is not needed.
UPD
During discussions in chat was discovered that child tracked collection was substituted like this:
record.ListChildren = someModel.ListChildren.Where(...).ToList()
Which resulted in addition of elements with already tracked ids. So the ListChildren should be updated with saving already tracked items like recommended here.

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

C# GroupBy Trouble

As of now, I am trying to create a list that groups based on certain criteria and then display that list in the view.
I have two database tables and one is an association table.
First Table
public partial class InitialTraining
{
public InitialTraining()
{
InitialTrainingAssociations = new HashSet<InitialTrainingAssociation>();
}
public int Id { get; set; }
[ForeignKey("MedicInfo")]
public int TfoId { get; set; }
[ForeignKey("InstructorInfo")]
public int? InstructorId { get; set; }
[ForeignKey("PilotInfo")]
public int? PilotId { get; set; }
public DateTime DateTakenInitial { get; set; }
public decimal FlightTime { get; set; }
public bool Active { get; set; }
[StringLength(2000)]
public string Narrative { get; set; }
[Required]
[StringLength(20)]
public string TrainingType { get; set; }
[ForeignKey("CodePhase")]
public int PhaseId { get; set; }
[ForeignKey("PhaseTrainingType")]
public int PhaseTrainingTypeId { get; set; }
public string EnteredBy { get; set; }
public DateTime? EnteredDate { get; set; }
public virtual MedicInfo MedicInfo { get; set; }
public virtual MedicInfo InstructorInfo { get; set; }
public virtual MedicInfo PilotInfo { get; set; }
public virtual Code_Phase CodePhase { get; set; }
public virtual Code_PhaseTrainingType PhaseTrainingType { get; set; }
public virtual ICollection<InitialTrainingAssociation> InitialTrainingAssociations { get; set; }
}
Second Table (Association Table)
public class InitialTrainingAssociation
{
public int Id { get; set; }
[ForeignKey("InitialTraining")]
public int InitialTrainingId { get; set; }
[ForeignKey("CodePerformanceAnchor")]
public int? PerformanceAnchorId { get; set; }
[ForeignKey("GradingSystem")]
public int? GradingSystemId { get; set; }
public virtual AviationMedicTraining.CodePerformanceAnchor CodePerformanceAnchor { get; set; }
public virtual InitialTraining InitialTraining { get; set; }
public virtual GradingSystem GradingSystem { get; set; }
}
Here is my GroupBy in C#.
// get list of initial training record ids for statistics
var lstInitialTrainings = db.InitialTrainings.Where(x => x.TfoId == medicId && x.Active).Select(x => x.Id).ToList();
// get list of initial training performance anchors associated with initial training records
var lstPerformanceAnchors = db.InitialTrainingAssociations
.Where(x => lstInitialTrainings.Contains(x.InitialTrainingId)).GroupBy(t => t.PerformanceAnchorId)
.Select(s => new MedicStatistic()
{
PerformanceAnchorName = db.CodePerformanceAnchor.FirstOrDefault(v => v.Id == s.Key).PerformanceAnchor,
AnchorCount = s.Count()
}).ToList();
My Goal
Obviously from my code I want to group by the performance anchor in the association table, but I need more information from the Initial Training table to include in my ViewModel MedicStatistic, but I am having trouble figuring out the best way to do it.
My overall goal is to be able to get the most recent time a performance anchor was completed from the Initial Training table.
Visual
Initial Training Table (not all fields were captured in snippet b/c they're not important for the purpose of this question)
Initial Training Association Table
What I expect
So, from the pictures provided above as you can see there are multiple 1's for performance anchor id's in the association table, but they each have different InitialTrainingId. So, this specific performance anchor has been done multiple times, but I need to get the most recent date from the Initial Training table. Also, I need to get the corresponding grade with the anchor from the Grading System table, based on the most recent date.
So, for the performance anchor that equals 1.. I would want the grade that corresponds to the InitialTrainingId of 17 because that record was the most recent time that the performance anchor of 1 was done.
If you have any questions please let me know.
You want the data grouped by CodePerformanceAnchor, so the most natural way to start the query is at its DbSet which immediately eliminates the necessity of grouping:
from pa in db.CodePerformanceAnchors
let mostRecentInitialTraining
= pa.InitialTrainingAssociations
.Select(ita => ita.InitialTraining)
.OrderByDescending(tr => tr.DateTakenInitial)
.FirstOrDefault()
select new
{
pa.PerformanceAnchor,
mostRecentInitialTraining.DateTakenInitial,
mostRecentInitialTraining. ...
...
AnchorCount = pa.InitialTrainingAssociations.Count()
}
As you see, only navigation properties are used and the query as a whole is pretty straightforward. I assume that the PerformanceAchor class also has an InitialTrainingAssociations collection.
I can't guarantee that EF will be able to execute it entirely server-side though, that's always tricky with more complex LINQ queries.
I'm going to ignore the virtual properties in your InitialTrainingAssociation class, since you didn't mention anything about them and it's not immediately apparent to me whether they actually contain data, or why they are virtual.
It seems like IQueryable.Join is the easiest way to combine the data you want.
In the following example, we will start with the entries from the InitialTrainings table. We will then Join with the InitialTrainingAssociations table, which will result in a collection of paired InitialTraining and InitialTrainingAssociation objects.
var initialTrainingResults =
// Start with the InitialTrainings data.
db.InitialTrainings
// Add association information.
.Join(
// The table we want to join with
db.InitialTrainingAssociations,
// Key selector for the outer type (the type of the collection
// initiating the join, in this case InitialTraining)
it => it.Id,
// Key selector for the inner type (the type of the collection
// being joined with, in this case InitialTrainingAssociation)
ita => ita.InitialTrainingId,
// Result selector. This defines how we store the joined data.
// We store the results in an anonymous type, so that we can
// use the intermediate data without having to declare a new class.
(InitialTraining, InitialTrainingAssociation) =>
new { InitialTraining, InitialTrainingAssociation }
)
From here, we can add data from the PerformanceAnchors and GradingSystems tables, by performing more Joins. Each time we perform a Join, we will add a new entity to our anonymous type. The result will be a collection of anonymous types representing data we retrieved from the database.
// Add performance anchor information.
.Join(
db.PerformanceAnchors,
x => x.InitialTrainingAssociation.PerformanceAnchorId,
pa => pa.Id,
(x, PerformanceAnchor) =>
new { x.InitialTrainingAssociation, x.InitialTraining, PerformanceAnchor }
)
// Add grading system information.
.Join(
db.GradingSystems,
x => x.InitialTrainingAssociation.GradingSystemId,
gs => gs.Id,
// No need for InitialTrainingAssociation anymore, so we don't
// include it in this final selector.
(x, GradingSystem) =>
new { x.InitialTraining, x.PerformanceAnchor, GradingSystem }
);
(This was a verbose example to show how you can join all the tables together. You can use less Joins if you don't need to access all the data at once, and you can filter down the InitialTrainings collection that we start with if you know you only need to access certain pieces of data.)
At this point, initialTrainingResults is an IEnumerable containing one entry for each association between the InitialTrainings, PerformanceAnchors, and GradingSystems tables. Essentially, what we've done is taken all the InitialTrainingAssociations and expanded their Ids into actual objects.
To get the most recent set of data for each performance anchor:
var performanceAnchors = initialTrainingResults
// Group by each unique Performance Anchor. Remember, the IEnumerable
// we are operating on contains our anonymous type of combined Training,
// Performance Anchor and Grading data.
.GroupBy(x => x.PerformanceAnchor.Id)
// Order each Performance Anchor group by the dates of its training,
// and take the first one from each group
.Select(g => g.OrderByDescending(x => x.InitialTraining.DateTakenInitial).First());
In the Select you can order the group result to get the most recent associated InitialTraining by DateTakenInitial, and from there get the desired data
//...omitted for brevity
.GroupBy(t => t.PerformanceAnchorId)
.Select(g => {
var mostRecent = g.OrderByDescending(_ => _.InitialTraining.DateTakenInitial).First();
// get the corresponding grade with the anchor from the Grading System table
var gradeid = mostRecent.GradingSystemId;
var gradingSystem = mostRecent.GradingSystem;
//get the most recent date from the Initial Training
var mostRecentDate = mostRecent.InitialTraining.DateTakenInitial
//..get the desired values and assign to view model
var model = new MedicStatistic {
//Already have access to CodePerformanceAnchor
PerformanceAnchorName = mostRecent.CodePerformanceAnchor.PerformanceAnchor
AnchorCount = g.Count(),
MostRecentlyCompleted = mostRecentDate,
};
return model;
});

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.

Categories

Resources