How could I get a Relative Entity's count using EF6 - c#

I have a Entity called "Client", and another Entity called "Card".
A Client may have many Cards.
My Client Entity looks like this:
public class Client{
public virtual ICollection<Card> Cards {get; set;}
}
Now I want to show the Client data in a DataGrid in WPF, and I want to get Cards Count data,so I add a property to Client Entity, which like this:
public class Client{
public virtual ICollection<Card> Cards {get; set;}
public int CardCount
{
return Cards.Count;
}
}
And then I query the data with Linq and Bind to view
var query = from n in db.Clients select n;
When I run the Application, I got a Exception just right on the return Cards.Count; line;
System.ObjectDisposedException
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So how could I correctly get the cards count?

There is a way simpler method than the other answers here show. Please also realize that solutions such as
var client = db.Clients.FirstOrDefault(c=> c.Id = someid); //get a client
if (client != null)
{
cardCount = client.Cards.Count;
}
will cause an issue called Select N+1 problem. Read up on it if interested, but in a nutshell, it means the following:
Because you are not only interested in one exact client, but you want to display N clients, you need to do one (1) query to get just the clients. Then, by doing the FirstOrDefault stuff, you are actually doing one (1) extra db roundtrip to the database per Client record, which results in an additional N * 1 = N roundtrips. What this means that, if you were to just query the Clients without any related data, you could get however many client records you like, in just one query. But by fetching related data to each of them one-by-one, you are doing way too many db roundtrips.
Here is a way to solve this issue, by using joins and projections. You can get all the data you need in a single DB access:
using (var context = GetDbContext())
{
return context.Clients.Select(cli => new YourViewModel
{
Name = cli.FullName,
// Other prop setters go here
CardCount = cli.Cards.Count
}).Skip((page - 1) * pageSize).Take(pageSize).ToList();
}
You might be wondering, what's the difference afterall? Well, here, you are not working with materialized objects, as others call them here, but with a DbContext. By applying the proper LINQ operators to it (note, that this works not just with DbContext, but also with any IQueryable (well obviously not if you call AsQueryable() on an already in-memory collection but whatever)), LINQ to Entities can construct a proper SQL to join the tables and project the results and therefore you fetch all required data in one go. Note that LINQ to Entities IS ABLE to translate the cli.Cards.Count into a proper SQL Count statement.

You can get the count without loading the entities like this:
using (var context = new MyContext())
{
var client = context.Client.Find(clientId);
// Count how many cards the client has
var cardsCount = context.Entry(client)
.Collection(b => b.Cards)
.Query()
.Count();
}
More information on MSDN page.

You get an ObjectDisposedException if you do not materialize the retreived query. In the following case, the query gets executed only when you Access the first time the list from GetNonMaterialized and not before leaving the method. Fact of this the db is disposed because of lost of scope.
public IEnumerable<Client> GetNonMaterialized()
{
return from n in db.Clients select n;
}
In the following case the query is executed before leaving the method.
public IEnumerable<Client> GetMaterialized()
{
return (from n in db.Clients select n).ToList();
}
Always be sure that the query is executed before exiting the scope of a ObjectContext.
If you want to know whether the query is executed and when enalbe Logging of EF.
How can I turn off Entity Framework 6.1 logging?

Related

Avoid parameter duplication in CQRS RESTful ASP.NET Core API

I'm trying to build a RESTful API using CQRS (with MediatR) in ASP.NET Core 6.
The issue I'm facing is when trying to expose child entities.
Consider a ItemProperty to have many ItemPropertyOptions.
The query I'm trying to expose is the following:
public record GetItemPropertyOptionsQuery(string PropertyReference, int PageNumber = 1, int PageSize= 10) : IRequest<PaginatedList<ItemPropertyOptionDto>>;
So in the controller my initial attempt was the following:
[HttpGet]
[Route("ItemProperties/{propertyReference}/Options")]
public async Task<ActionResult<PaginatedList<ItemPropertyOptionDto>>> GetAsync([FromQuery]GetItemPropertyOptionsQuery query, string propertyReference)
{
if (query.PropertyReference != propertyReference)
{
return BadRequest("You are querying for a different object.");
}
return Ok(await Mediator.Send(query));
}
As you can see, the main issue is that I need the property reference to process my query, but that is also part of the URL, so I have them duplicated. This means that the consumer of the API has to include them twice and I have to check for them to be equal.
The resulting call is something like:
/ItemProperties/test/Options?PropertyReference=test
This is a smell to me but I'm not sure how to solve it.
A first idea was to not expose directly the query but a different object that does not include this parameter and the create the Query myself, but I would need to create an extra record and match it to the query/command fields for every endpoint I create, and that doesn't sound very clean from a maintainability PoV since every time I change the Query to include a new parameter I need to do the same in the Request.
Or do you think it would be ok this way?
public record GetItemPropertyOptionsRequest(int PageNumber = 1, int PageSize= 10);
[HttpGet]
[Route("ItemProperties/{propertyReference}/Options")]
public async Task<ActionResult<PaginatedList<ItemPropertyOptionDto>>> GetAsync([FromQuery]GetItemPropertyOptionsRequest request, string propertyReference)
{
var query = new GetItemPropertyOptionsQuery(propertyReference, request.PageNumber, request.PageSize);
return Ok(await Mediator.Send(query));
}
What are you trying to achieve with this, return a list of
ItemPropertyOptions for a given ItemProperty?
#Peppermintology yes
In which case you do not require the additional (and superfluous second) propertyReferece parameter.
All you require is the first propertyReference parameter. That should operate as the lookup key for the relevant ItemProperty entity in your database and then all you need to do is retrieve the releated ItemPropertyOptions as a relationship.
So your URL would look as follows:
/item-properties/{propertyId}/options
Then you would use the propertyId parameter in your query handler to obtain your records and relation opptions. That might look something like:
var results = await _dbContext.PropertyItems
.Where(item => item.Id == propertyId)
.Include(item => item.Options)
.ToListAsync();

LINQ multiple joins with different objects

I've been working on a C# program that use LINQ to manage a simple SQLite database. The database consists in different tables that in my code are represented by different classes. This is how I create the tables:
public ITable<Doctors> Doctors => GetTable<Doctors>();
public ITable<Patients> Patients=> GetTable<Patients>();
public ITable<Prescriptions> Prescriptions=>GetTable<Prescriptions>();
public ITable<Assistants> Assistants=> GetTable<Assistants>();
public ITable<Medicines> Medicines => GetTable<Medicines>();
public ITable<General_medicines> General_medicines=> GetTable<General_medicines>();
public ITable<Stocks> Stocks=> GetTable<Stocks>();
public ITable<Dosages> Dosages=> GetTable<Dosages>();
public ITable<Recipes> Recipes=> GetTable<Recipes>();
public ITable<Prescriptions_MP> Prescriptions_MP=> GetTable<Prescriptions_MP>();
Now, I want to create a LINQ query (in a separate class) where I get different properties in all these tables and I put them inside an IEnumerable that I can later scan.
To do so, I proceed as following:
public IEnumerable<Therapy> TakePrescriptions()
{
HealthDataContext DbContext = DbFactory.Create();
var dbPrescriptions = DbContext.GetTable<Prescriptions>();
IEnumerable<Prescriptions> prescriptions= dbPrescriptions.AsEnumerable();
var dbPatients= DbContext.GetTable<Patients>();
IEnumerable<Pazienti> patients= dbPatients.AsEnumerable();
var dbPrescrizioniMP = DbContext.GetTable<Prescriptions_MP>();
IEnumerable<Prescriptions_MP> prescriptionsMP = dbPrescriptionsMP .AsEnumerable();
var dbRecipes = DbContext.GetTable<Recipes>();
IEnumerable<Recipes> recipes= dbRecipes .AsEnumerable();
var dbMedicines= DbContext.GetTable<Medicines>();
IEnumerable<Medicines> medicines= dbMedicines.AsEnumerable();
var dbGeneral_medicines = DbContext.GetTable<General_medicines>();
IEnumerable<General_medicines> general_medicines= dbGeneral_medicines.AsEnumerable();
var dbDosages = DbContext.GetTable<Dosages>();
IEnumerable<Dosages> dosages= dbDosages .AsEnumerable();
var query = from p in patients
join pr in prescriptions_MP on p.Id equals pr.Patient
join pre in prescriptions on pr.Prescription equals pre.Id
join fc in medicines on pre.Medicine equals fc.Id
join fg in general_medicines on fc.Medicine equals fg.Id
join ds in dosages on fg.Id equals ds.General_Medicine
where p.Doctor== IdDoctor
select new
{
IdDoctor, //int
p.Name, //string
pr.Prescription, //int
pre.Id, //int
fc.Format, //string
fc.Administration, //string
fc.Downloadable, //boolean
fc.Full_stomach, //boolean
nameM= fg.Name, //string
ds.Quantity, //int
ds.Hour //string
};
List < Therapy> therapy = new List<Therapy>();
foreach(var object in query)
{
Therapy t = new Therapy(IdDoctor, object.Name, object.Prescription, object.Id, object.Format, object .Administration, object.Downloadable, object.Full_stomach, object.nameM, object.Quantity, object.Hour);
therapy.Add(t);
}
return therapy;
}
Now when I try to load the page that should display a list of the results, I get InvalidOperationException: An open reader is associated with this command. Close it before changing the CommandText property. at the foreach operation.
When I try to debug, I can see that the tables I created before the query have items inside, but the result of the query is NULL.
I tried to dispose of the DBContext but then I get this exception: ObjectDisposedException: IDataContext is disposed, see https://github.com/linq2db/linq2db/wiki/Managing-data-connection Object name: 'DataConnection'.
The error you are getting “An open reader is associated with this command. Close it before changing the CommandText property”, suggests that multiple readers are open. However looking at your query it seems like one reader is open for your one query.
The reality however is different. You have 5 tables, such that each has a 1 to many relationship to another table. For example table patient has a 1 to many relationship to prescriptions table. As a patient can have multiple prescriptions.
Thus just considering these two tables, we first have one query to load all the patients, and then another query per patient to load all of its prescriptions, this means if you have N patients, this translates to 1 + N query , 1 to load all the patients, and N to load prescriptions of each of these patients.
Now given the 5- level join that you have in the code, doing the math, you can see how many potential open readers are out there.
The data is loaded on demand, meaning the readers are activated once you iterate through the results of your query, this is to avoid huge memory usage, in the cost of performance, and hence when in your foreach loop you start to iterate through the objects, the data is for real being fetched.
To solve the problem, you can try converting ToList at the end of your query to encourage binding (thus eager loading), or as one of the commenters are suggesting pass MultipleActiveResultSets=true to your connection string.
You should remove AsEnumerable() calls from tables you use in query, because they force linq2db to execute them as separate queries.
This has two consequences:
it attempts to start multiple queries over single db connection, which is not supported and you get InvalidOperationException
let's imagine it will work (e.g. you replaced AsEnumerable() with ToList() to read all data from each query). In this case it will load all data into application and perform joins in C# code - it will lead to really bad performance, especially in cases when you need to discard some data that doesn't meet join conditions.

Define dynamic entity property/function

I have an Entity called Task. A task can have child tasks. In my dal I am trying to automatically evaluate the child count every time I fetch a task from my entity model. The child count is not stored in the database.
For example, let's look at my Fetch method:
public TaskDto Fetch(Guid id)
{
using (var ctx = ObjectContextManager<MyDataContext>.GetManager("MyDataContext"))
{
var data = (from t in ctx.ObjectContext.Tasks
join tty in ctx.ObjectContext.TaskTypes on t.Id equals tty.Id
where t.EntityGuid == id
select new
{
Task = t,
ChildCount = TaskChildCount(t.EntityGuid)
}).FirstOrDefault();
if (data == null)
{
throw new RecordNotFoundException("Task");
}
return ReadData(data.Task, data.ChildCount);
}
}
But instead of calling ReadData with the 2 params, I just want to call it with a Task param: ReadData(data.Task) and the ChildCount to automatically be there. Is there some way I can bake this into my Task entity? It is a very simple function:
public int TaskChildCount(Guid currentTaskId)
{
var ret = 0;
using (var ctx = ObjectContextManager<MyDataContext>.GetManager("MyDataContext"))
{
ret = ctx.ObjectContext.Tasks.Count(x => x.ParentId != currentTaskId);
}
return ret;
}
There is a way to do this, but Task will always need some help. You could Include the child records of Task:
from t in ctx.ObjectContext.Tasks.Include(t => t.Tasks)
Now Task can have a property TaskCount that returns this.Tasks.Count.
You'd have to access this property outside the scope of a LINQ query, otherwise EF will try to translate it into SQL and fail.
The downside is that it populates all Tasks contained by a parent Task, which is a lot heavier than just getting a count from the database. Therefore a better way could be to use AutoMappers capability to project nested properties by name convention. Suppose your TaskDto would have a property TasksCount, then you could do:
Mapper.CreateMap<Task, TaskDto>();
var data = ctx.ObjectContext.Tasks.Project().To<TaskDto>();
(Project is an extension method in AutoMapper.QueryableExtensions, AutoMapper can be obtained through Nuget).
I leave out the join and where for simplicity, but these would not change the concept. AutoMapper does not find TasksCount in Task itself, so by convention it tries to find a property Tasks to which it can apply Count(). It finds the child collection Tasks (well, maybe you named it differently) and there you go. You will see that the count is neatly integrated in the SQL query that is generated.
The difference is that the dto is created by the Fetch method itself, not by ReadData. I can't judge whether this causes any problems. Maybe you have to shuffle some code in order to use this AutoMapper feature.

How to create and populate a nested ViewModel well

I have a View Model that has some serious nesting. I need to populate it from Entity Framework 4. I tried creating one big linq statement to populate it, but it says it doesn't recognize the .ToList() methods. It compiles fine. Runtime error is
LINQ to Entities does not recognize the method
'System.Collections.Generic.List`1[ProductDepartment] ToList[ProductDepartment]
(System.Collections.Generic.IEnumerable`1[ProductDepartment])' method,
and this method cannot be translated into a store expression.
What is a more efficient way to populate something like this without doing several thousand database calls?
List<Product> Products {
int ID
string Name
...
List<Department> Departments {
int ID
string Name
}
List<Image> Images {
int ID
string Name
}
List<Price> Prices {
int ID
string Name
List<Version> Versions {
int ID
string Name
List<Pages> Pages {
int ID
string Name
} } } }
The horrible Linq code looks something like this
var myProducts = (from myProduct in DC.MyProducts
where p => p.productGroup == 1
select new Product {
ID = myProduct.ID,
Name = myProduct.Name,
Departments = (from myDept in DC.MyDepartments
where q => q.fkey = myProduct.pkey
select new Department {
ID = myDept.ID,
Name = myDept.Name
}).ToList(),
...
//Same field assignment with each nesting
}).ToList();
Update:
The fix was to remove all the .ToLists(), which worked better anyway.
Now I have to do filtering and sorting on the end product.
Well for starters, that is one crazy model, but i'm assuming you already know this.
Do you really need all that info at once?
I'll play devil's advocate here and assume you do, in which case you have a couple of logical choices:
1) As #xandy mentioned - use .Include to eager load your associations in the one call. This is assuming you have setup navigational properties for your entites in your EDMX.
2) Use a View. Put all that crazy joining logic inside the database, making your EF work a very simple select from the view. The downside of this is your queries to the view basically become read only, as i don't believe you can perform updates to an entity which is mapped to a view.
So it's your choice - if this is a readonly collection for displaying data, use a View, otherwise eager-load your associations in the one hit.
Also, be careful when writing your LINQ queries - i see you have multiple .ToList statements, which will cause the query to be executed.
Build up your query, then perform the .ToList once at the end.
why do you require all this informataion at one go? You can use lazy loading when a nested property is accessed?

C# - Entity Framework - Understanding some basics

Model #1 - This model sits in a database on our Dev Server.
Model #1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model #2 - This model sits in a database on our Prod Server and is updated each day by automatic feeds. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
I have written what should be some simple code to sync my feed (Model #2) into my working DB (Model #1). Please note this is prototype code and the models may not be as pretty as they should. Also, the entry into Model #1 for the feed link data (mainly ClientID) is a manual process at this point which is why I am writing this simple sync method.
private void SyncFeeds()
{
var sourceList = from a in _dbFeed.Auto where a.Active == true select a;
foreach (RivWorks.Model.NegotiationAutos.Auto source in sourceList)
{
var targetList = from a in _dbRiv.Product where a.alternateProductID == source.AutoID select a;
if (targetList.Count() > 0)
{
// UPDATE...
try
{
var product = targetList.First();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
else
{
// INSERT...
try
{
long clientID = source.Client.ClientID;
var companyDetail = (from a in _dbRiv.AutoNegotiationDetails where a.ClientID == clientID select a).First();
var company = companyDetail.Company;
switch (companyDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var product = new RivWorks.Model.Negotiation.Product();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
company.Product.Add(product);
break;
}
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
}
}
Now for the questions:
In Model #2, the class structure for Auto is missing ClientID (see red circled area). Now, everything I have learned, EF creates a child class of Client and I should be able to find the ClientID in the child class. Yet, when I run my code, source.Client is a NULL object. Am I expecting something that EF does not do? Is there a way to populate the child class correctly?
Why does EF hide the child entity ID (ClientID in this case) in the parent table? Is there any way to expose it?
What else sticks out like the proverbial sore thumb?
TIA
1) The reason you are seeing a null for source.Client is because related objects are not loaded until you request them, or they are otherwise loaded into the object context. The following will load them explicitly:
if (!source.ClientReference.IsLoaded)
{
source.ClientReference.Load();
}
However, this is sub-optimal when you have a list of more than one record, as it sends one database query per Load() call. A better alternative is to the Include() method in your initial query, to instruct the ORM to load the related entities you are interested in, so:
var sourceList = from a in _dbFeed.Auto .Include("Client") where a.Active == true select a;
An alternative third method is to use something call relationship fix-up, where if, in your example for instance, the related clients had been queried previously, they would still be in your object context. For example:
var clients = (from a in _dbFeed.Client select a).ToList();
The EF will then 'fix-up' the relationships so source.Client would not be null. Obviously this is only something you would do if you required a list of all clients for synching, so is not relevant for your specific example.
Always remember that objects are never loaded into the EF unless you request them!
2) The first version of the EF deliberately does not map foreign key fields to observable fields or properties. This is a good rundown on the matter. In EF4.0, I understand foreign keys will be exposed due to popular demand.
3) One issue you may run into is the number of database queries requesting Products or AutoNegotiationContacts may generate. As an alternative, consider loading them in bulk or with a join on your initial query.
It's also seen as good practice to use an object context for one 'operation', then dispose of it, rather than persisting them across requests. There is very little overhead in initialising one, so one object context per SychFeeds() is more appropriate. ObjectContext implements IDisposable, so you can instantiate it in a using block and wrap the method's contents in that, to ensure everything is cleaned up correctly once your changes are submitted.

Categories

Resources