I need help with a problem I cant get my head around. I want to get a questionnaire with only the questions that arent answered.
To be clear, a person should be able to request a questionnaire, answer one question out of, lets say, a total of 5 questions. And then when one requests the questionnaire the next time, it should be returned but with only 4 of the questions (the unanswered ones).
I tried it with lambda includes also but cant get it to work. Any help is truly appreciated!
The classes are structured like
public class Questionnaire()
{
public int Id {get; set;}
public List<Question> {get; set;}
}
public class Question()
{
public int Id {get; set;}
public int QuestionnaireId {get; set;}
public List<Answer> {get; set;}
}
public class Answer()
{
public int Id {get; set;}
public int QuestionId {get; set;}
public int QuestionnaireId {get; set;}
public List<ReceivedAnswer> {get; set;}
}
public class ReceivedAnswer()
{
public int AnswerId {get; set;}
public int QuestionId {get; set;}
public int QuestionnaireId {get; set;}
public int PersonId {get; set;}
}
Assuming you have tables :
Questionnaires[QuestionnairesID, QuestionID]
ReceivedAnswers[PersonID , AnswerID]
Questions[QuestionID,Question]
Answers[AnswerID,QuestionID,Answer]
Linq Query would be like:
var pp = (from q in questionnaire.Questions
join a in question.Answers on q.questionid equals a.questionid
join ra in ReceivedAnswers on ra.answerid equals a.answerid
where ra.personid == inPersonID
select q.questionid).ToList();
var output = from questionnaire in _unitOfWork.Questionnaires
where questionnare.Id == questionnaireId && !pp.contains(q.questionid)
select questionnaire
Related
Hi I have a simple database, and what I am trying to do is build simple include maps as string using eager loading mechanism in EF CORE.
So in other words mu db models looks like:
And models that are supporting them:
public class StartTable
{
public int Id {get; set;}
public ICollection<TableA> TableA {get; set;}
public ICollection<TableB> TableA {get; set;}
}
public class TableA
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableAChild TableAChild {get; set;}
public int TableAChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TableB
{
public int Id {get; set;}
public StartTable StartTable {get; set;}
public int StartTableId {get; set;}
public TableBChild TableBChild {get; set;}
public int TableBChildId {get; set;}
public TableAB TableAB {get; set;}
public int TableABId {get; set;}
}
public class TablAChild
{
public int Id {get; set;}
public TableA TableA {get; set;}
}
public class TableBChild
{
public int Id {get; set;}
public TableB TableB {get; set;}
}
public class TableAB
{
public int Id {get; set;}
public TableA TableA {get; set;}
public TableB TableB {get; set;}
}
I think relations are readible from models. Now I just want to create a map, that is I want to select start table and with include of all branches so final include path should looks like:
_context.StartTable.Include("StartTable.TableA.TableAChild")
_context.StartTable.Include("StartTable.TableA.TableAB")
_context.StartTable.Include("StartTable.TableB.TableBChild")
_context.StartTable.Include("StartTable.TableB.TableAB")
And If I type this manually it works, but this will grow a lot so I don't want to update this every time something will come up, I tried AutoInclute() in context on main table but it includes only 1 level down.
I thought I can create some sort of map function that looks like:
private static IEnumerable<string> BuildIncludeTree(DbContext context, Type type)
{
var entityAssemblyTypes = Assembly.GetExecutingAssembly().GetReferencedAssemblies().SelectMany(assembly => Assembly.Load(assembly).GetTypes());
void AddAssetByString(ref HashSet<string> navigation, List<string> createdPaths)
{
foreach (var path in createdPaths)
{
var splitPath = path.Split('.');
var relationNavigationNode = splitPath.Last();
var parentNavigationType = entityAssemblyTypes.FirstOrDefault(t => t.Name == relationNavigationNode);
if (parentNavigationType == null)
{
throw new ArgumentException($"Unknown type parent: {relationNavigationNode}");
}
var parentNodesProperties =
parentNavigationType.GetProperties().Where(prop => !prop.PropertyType.IsSimple() && !splitPath.Contains(prop.Name)).ToArray();
if (!parentNodesProperties.Any())
{
navigation.Add(path);
continue;
}
navigation.Add(path);
AddAssetByString(ref navigation, parentNodesProperties.Select(prop => $"{path}.{prop.Name}").ToList());
}
}
IEntityType entityType = context.Model.FindEntityType(type);
if (entityType == null) throw new ArgumentException($"Unknown entity type {type.Name}");
var navigationsByString = new HashSet<string>();
var relationsByString = entityType.GetNavigations().Select(nav => $"{type.Name}.{nav.Name}");
AddAssetByString(ref navigationsByString, relationsByString.ToList());
return new List<string>();
}
But problem here is relation to TableAB, I mean when I get to mapping this part function goes circular and creates map:
StartTable.TableA.TableAChild.TableB.StartTable.TableA ... and so on
Can this be prevented and what am I missing?
Can EF Core detect in some sort of way navigation downwards and upwards?
Or is there any other and simpler way to do that?
You can't Include all .There is already post about that here
Is there a way to Include() all with dbcontext?
But if correctly understand you can use Linq to make you code shorter like:
_context.StartTable.Include(x => x.TableA.TableAChild && x.TableA.TableAB && x. ....)
And in that case you will need to add entities you want to include in your class also you can use [NotMapped] attribute if you working direct with your class instead of dto. So in that way you can access the mapped entities direct from the class like class.TableAChild
+
public virtual TableAChild TableAChild { get; set; }
public virtual TableAB TableAB { get; set; }
Greetings and good luck
I have the following models:
public class Order
{
public int Id {get; set;}
public int CustomerId {get; set;}
public virtual Category Category {get; set;}
//Many more properties...
}
public class OrderLine
{
public int Id {get; set;}
public int OrderId {get; set;}
public virtual Order Order {get; set;}
public virtual Product Product {get; set;}
//Other properties...
}
I need to get the orders of a particular customer. In order not to retrieve too many information, I created a class:
public class CustomerOrder
{
public int CustomerId {get; set;}
public int OrderId {get; set;}
public string ProductName {get; set;}
public virtual ICollection<OrderLine> {get; set;}
}
I have mapping configuration for the Order and OrderLine classes but none for CustomerOrder as I was thinking that I can project data into this class.
I can:
Use EF to retrieve the data by specifying includes. After the data is retrieved I can project it into the CustomerOrder class. However, will this force EF to retrieve all columns for the main and included tables?
Use a custom SQL query to retrieve the required details from the Order table (maybe directly from a view). The use Linq to join this resultset with OrderLine to have the complete projection. However, will I need to have mapping configuration for the view?
To avoid too many columns and join in the SQL select statement, what is the best way to project the data into CustomerOrder?
You can do it as shown below.You have to do some changes on your models as well.I have done that.Please see that too.
public class Order
{
public int Id {get; set;}
public int CustomerId {get; set;}
public virtual Category Category {get; set;}
public virtual ICollection <OrderLine> OrderLines {get; set;}
//Many more properties...
}
public class OrderLine
{
public int Id {get; set;}
public int OrderId {get; set;}
public virtual Order Order {get; set;}
public virtual Product Product {get; set;}
//Other properties...
}
public class CustomerOrder
{
public int CustomerId {get; set;}
public int OrderId {get; set;}
public string ProductName {get; set;}
public virtual ICollection<OrderLine> OrderLines {get; set;}
}
Final Query :
var orderList = (from order in _context.Orders
from orderLine in order.OrderLines)
select new CustomerOrder
{
CustomerId = order.CustomerId,
OrderId = orderLine.OrderId,
ProductName= orderLine.Product.ProductName,
OrderLines = order.OrderLines
}).AsNoTracking().ToList();
A 1 : No.Only the projected columns will be fetched from the db.
Best Approach : Always use the custom projection (like CustomerOrder).That is the best when we consider the Performance of the EF query.You can use that to send data to the View too (it's like a DTO (Data Transfer Object)).
My classes (and tables):
class A {
public int Id {get; set;}
public string Name {get; set;}
public virtual C CField {get; set;}
}
class B {
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
public virtual C CField {get; set;}
}
class C {
public int Id {get; set;}
public string Name {get; set;}
}
class D {
public int Id {get; set;}
public string Title {get; set;}
public virtual A AField {get; set;}
public virtual B BField {get; set;}
}
I need update my D object. I use dependency injection so I can use repositories in controller:
A aObj = aService.GetAById(id);
B bObj = bService.GetBByName(name);
D dObj = new D()
{
Title = "MyTitle",
AField = aObj,
BField = bObj
};
dService.Update(dObj);
In each class repository I just create context as private field:
private MyContext db = new MyContext();
and I use it in every method like:
var model = db.A.Where(x=>x.Id == id);
return model;
But it can't work because both classes A and B has field with C class so I still got excepted: An entity object cannot be referenced by multiple instances of IEntityChangeTracker when I call dService.Update(dObj) (second listing).
I found that I should detach context in every method in each repositroy like this:
var model = db.A.Where(x=>x.Id == id);
db.Entry(model).State = System.Data.Entity.EntityState.Detached;
return model;
Exception was gone but now CField in aObj and bObj is always null and dObj.BField is not updated.
How can I fix that and what I'm doing wrong? I lost a few days for finding out what I should do: I even try remove private context field in repositories and in every method just use using(var db = new MyContext()) but then exception "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" is back.
You can't mix entities from different contexts in EF. Use ids instead.
Update models:
class A {
public int Id {get; set;}
public string Name {get; set;}
public int CFieldId {get; set;}
public virtual C CField {get; set;}
}
class B {
public int Id {get; set;}
public string Name {get; set;}
public DateTime Date {get; set;}
public int CFieldId {get; set;}
public virtual C CField {get; set;}
}
class C {
public int Id {get; set;}
public string Name {get; set;}
}
class D {
public int Id {get; set;}
public string Title {get; set;}
public int AFieldId {get; set;}
public int BFieldId {get; set;}
public virtual A AField {get; set;}
public virtual B BField {get; set;}
}
And in controller:
A aObj = aService.GetAById(id);
B bObj = bService.GetBByName(name);
D dObj = new D()
{
Title = "MyTitle",
AFieldId = aObj.Id,
BFieldId = bObj.Id
};
dService.Update(dObj);
I need to get all Students which have Registration on specyfic Realisation.
I was thinking it would be s => s.Registrations.RealisationId == realisationId but it doesn't work :). I'm trying to make this like in the example code but I'm getting:A lambda expression with a statement body cannot be converted to an expression tree. I have no idea how can I write this expression correctly, can anyone help me with this?
I couldn't figure out how to title this question better, sorry.
Database:
public class Student : BaseEntity {
public int StudentId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
public class Registration : BaseEntity {
public int RegistrationId {get; set;}
public int StudentId {get; set;}
public int RealisationId {get; set;}
public Student Student {get; set;}
public Realisation Realisation {get; set;}
}
public class Realisation : BaseEntity {
public int RealisationId {get; set;}
public virtual ICollection<Registration> Registrations {get; set;}
}
My try:
public IEnumerable<Student> GetByRealisationId(int realisationId) {
return Context.Set<Student>().Where(s => {
foreach(Registration r in s.Registrations) {
if (r.RealisationId == realisationId)
return true;
}
return false;
});
}
You'll need to select the ID's out and use Contains:
return Context.Set<Student>()
.Where(s => s.Registrations
.Select(r => r.RealisationId)
.Contains(realisationId));
Generally this is converted to a WHERE IN clause.
I have the following item model:
public class Item
{
public int Id {get; set;}
public string name {get; set;}
public virtual ICollection<Comment> Comments {get; set;}
public virtual Comment Comment {get; set;}
}
I have the following User model:
public class User
{
public int Id {get; set;}
public string UserName {get; set;}
public string email {get; set;}
}
And I have the following Comment model:
public class Comment
{
public int Id {get; set;}
public string text {get; set;}
public DateTime DateCreated {get; set;}
public int ItemId {get; set;}
[ForeignKey("ItemId")]
public virtual Item Item {get; set;}
public int UserId {get; set;}
[ForeignKey("UserId")]
public virtual User User {get; set;}
}
In my onModelCreating Context I have
modelBuilder.Enitity<Item>().HasOptional(c=>c.Comment).WithMany();
My aim is to return to a view the items which only the requesting user has commented on.
So far I have done:
int UserId = db.Users.Where(u=>u.UserName.Equals(User.Identity.Name))
.Select(u=>u.Id).FirstOrDefault();
var itemsWithComments = db.Items.Include(c=>c.Comments).......
At this point I want to be able to say: Select the items which the UserId == Comments.UserId, return the Items as a list.
And my View (using Razor)
#model IEnumerable <project.Models,Item>
#foreach (var item in Model)
.....
.....
Any help is much appreciated.
If you need me to clarify any point(s) please ask.
Kind regards
Assuming you want to filter only the comments from a specific user, you can't use Include() but you can use a projection to select a filtered list.
var itemsWithComments = db.Items.Select(o => new
{
Item = o,
Comments = o.Comments.Where(c => c.UserId == userId)
});
If the user is attached to the item itself then the query is simple:
var itemsWithComments = db.Items.Include(o => o.Comments).Where(o => o.UserId == userId);
One thing to remember - calling Select() will almost always reset any Include() calls before it so query.Include().Select() will not have the include while query.Select().Include() will.