Masking Entity Framework query result based on condition - c#

I'm using Entity Framework 6.0 and MVC and I have one of the following model:
public class Person
{
[Key]
public int Id { get; set; }
public String Name { get; set; }
public String SocialNumber {get;set;}
}
public class Organization
{
[Key]
public Guid? OrgId { get; set; }
public List<Person> Members { get; set; }
}
I'm getting this data by doing something like
bool hideData = false;
var sResult = DBContext.Organization.FirstOrDefault(s => s.OrgId == inputID)
Is there an elegant way for me to set the SocialNumber = "######" when hideData is true and show the value when it is false?
Before you tell me that I can just loop through the object and set them explicitly, the reason I don't want to do this because I simpled down the above example, in reality, my class is buried deep in many many objects.

Related

Entity Framework Core filter related entities and get top 2 for each group

I am using Entity Framework Core 2.0.1 and I have the following models
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Slug { get; set; }
public int Approved { get; set; }
public DateTime ArticleDate { get; set; }
// ... some other fields
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategory
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//... soem other fields
[ForeignKey("ArticleCategoryParent")]
public int? ArticleCategoryParentID { get; set; }
public virtual ArticleCategory ArticleCategoryParent { get; set; }
public virtual ICollection<ArticleCategory> SubCategories { get; set; }
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategoryRelation
{
[Column(Order = 0)]
public int ArticleId { get; set; }
public Article Article { get; set; }
[Column(Order = 1)]
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory {get; set;}
}
Every article belongs to one or more categories. Categories might have parent category.
I want to get from database last two articles (where Approved = 1) with related category details, for each category that belongs to a parent category which id is given as input.
I have tried but with no success. I can't filter results of an .Include() entity. Is it possible... or I don't know how to do it?
All my data are accessed through entity framework with appContext (the context used to get entities from database). Can I achieve what I want through entity framework core (lambda expression is preferred over Linq if possible), or should I use ADO.NET library (which I know how to execute custom queries).
P.S. I want to get data only to show in the view... no edit is needed.
You don't actually need to include here at all, as far as I can tell. Whenever you use data from a nav property, EF will go get the data from that table, as best it can filter it.
var CategoriesUnderParent = AppContext.ArticleCategories
.Where(c => c.ArticleCategoryParent == {parent});
foreach(var category in CategoriesUnderParent)
{
var ArticlesAllowed = category.ArticleCategoryRelations
.Where(acr => acr.Article.Approved == 1).Select(a => a.Article);
var ArticlesPicked = ArticlesAllowed
.OrderByDescending(ar => ar.ArticleDate)
.Take(2);
// Do something with your data
}

Get entity member from key in Entity Framework

In a project that involves an Asp.Net Core API and Entity Framework, I have the following entities:
public class A
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
[Required] public string Name { get; set; }
[Required] public B MyObj { get; set; }
}
public class B
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; private set; }
}
My API has this model, which represents a POST request body for the creation of a new A:
public class A_Post
{
public string Name { get; set; }
public string Obj_Id { get; set; }
}
Here, Obj_Id refers to the Id of the B instance that I want A.MyObj to have as its value.
What I want to do is add a method to A that creates an instance of A from an instance of A_Post. Something like this:
public static A CreateFromPost(A_Post post)
{
var entity = new A();
entity.Name = post.Name;
// post.Obj_Id is accessible here, but cannot set entity.MyObj
return entity;
}
However, I'm not sure how to go from having the Obj_Id to having the entity it corresponds to, without getting it from the context. Is there a way to do this? Or will I have to set MyObj somewhere else in the program, where the context can be accessed?

Why is my code doing lazy loading even after I turned it off at every possible point?

I would like to get Exams and Test entities that have a UserTest entity with a UserId that is either equal to "0" or to a provided value. I had a number of suggestions but so far none have worked. One suggestion was to start by getting UserTest data and the other solution was to start by getting Exam data. Here's what I have when I used the UserTests as the source starting point.
I have the following LINQ:
var userTests = _uow.UserTests
.GetAll()
.Include(t => t.Test)
.Include(t => t.Test.Exam)
.Where(t => t.UserId == "0" || t.UserId == userId)
.ToList();
When I check _uow.UserTests with the debugger it's a repository and when I check the dbcontext's configuration.lazyloading then it is set to false.
Here's my classes:
public class Exam
{
public int ExamId { get; set; }
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestId { get; set; }
public int ExamId { get; set; }
public string Title { get; set; }
public virtual ICollection<UserTest> UserTests { get; set; }
}
public class UserTest
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int TestId { get; set; }
public int QuestionsCount { get; set; }
}
When I looked at the output I saw something like this:
[{"userTestId":2,
"userId":"0",
"testId":12,
"test":{
"testId":12,"examId":1,
"exam":{
"examId":1,"subjectId":1,
"tests":[
{"testId":13,"examId":1,"title":"Sample Test1",
"userTests":[
{"userTestId":3,
"userId":"0",
Note that it gets a UserTest object, then gets a test object and then an exam object. However the exam object contains a test collection and then it heads back down again and gets the different tests and the unit tests inside of those:
UserTest > Test > Exam > Test > UserTest ?
I have tried hard to ensure lazy loading is off and debug tell me it's set to false. I am using EF6 and WebAPI but not sure if that makes a difference as I am debugging at the C# level.
You can't avoid that the inverse navigation properties are populated by EF, no matter if you load related entities with eager or lazy loading. This relationship fixup (as already explained by #Colin) is a feature you can't turn off.
You could solve the problem by nullifying the unwished inverse navigation properties after the query is finished:
foreach (var userTest in userTests)
{
if (userTest.Test != null)
{
userTest.Test.UserTests = null;
if (userTest.Test.Exam != null)
{
userTest.Test.Exam.Tests = null;
}
}
}
However, in my opinion the flaw of your design is that you try to serialize entities instead of data transfer objects ("DTOs") that are specialized to the view where you want to send the data to. By using DTOs you can avoid the inverse navigation properties that you don't want altogether and maybe other entity properties that you don't need in your view. You would define three DTO classes, for example:
public class ExamDTO
{
public int ExamId { get; set; }
public int SubjectId { get; set; }
public string Name { get; set; }
// no Tests collection here
}
public class TestDTO
{
public int TestId { get; set; }
public string Title { get; set; }
// no UserTests collection here
public ExamDTO Exam { get; set; }
}
public class UserTestDTO
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int QuestionsCount { get; set; }
public TestDTO Test { get; set; }
}
And then use a projection to load the data:
var userTests = _uow.UserTests
.GetAll()
.Where(ut => ut.UserId == "0" || ut.UserId == userId)
.Select(ut => new UserTestDTO
{
UserTestId = ut.UserTestId,
UserId = ut.UserId,
QuestionsCount = ut.QuestionsCount,
Test = new TestDTO
{
TestId = ut.Test.TestId,
Title = ut.Test.Title,
Exam = new ExamDTO
{
ExamId = ut.Test.Exam.ExamId,
SubjectId = ut.Test.Exam.SubjectId,
Name = ut.Test.Exam.Name
}
}
})
.ToList();
You could also "flatten" the object graph by defining only a single DTO class that contains all the properties you need for the view:
public class UserTestDTO
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int QuestionsCount { get; set; }
public int TestId { get; set; }
public string TestTitle { get; set; }
public int ExamId { get; set; }
public int ExamSubjectId { get; set; }
public string ExamName { get; set; }
}
The projection would become simpler and look like this:
var userTests = _uow.UserTests
.GetAll()
.Where(ut => ut.UserId == "0" || ut.UserId == userId)
.Select(ut => new UserTestDTO
{
UserTestId = ut.UserTestId,
UserId = ut.UserId,
QuestionsCount = ut.QuestionsCount,
TestId = ut.Test.TestId,
TestTitle = ut.Test.Title,
ExamId = ut.Test.Exam.ExamId,
ExamSubjectId = ut.Test.Exam.SubjectId,
ExamName = ut.Test.Exam.Name
})
.ToList();
By using DTOs you do not only avoid the problems of inverse navigation properties but also follow good security practices to "white-list" the exposed property values from your database explicitly. Imagine you would add a test access Password property to the Test entity. With your code that serializes eagerly loaded full entities with all properties the password would get serialized as well and run over the wire. You don't have to change any code for this to happen and in the worst case you wouldn't be aware that you are exposing passwords in a HTTP request. On the other hand when you are defining DTOs a new entity property would only be serialized with your Json data if you add this property explicitly to the DTO class.
Your query will load all UserTests into the context where UserId == "0" || UserId == userId and you have eagerly loaded the related Test and its related Exams.
Now in the debugger you can see that the Exams are linked to some Tests in memory and are assuming that is because they have been lazy-loaded. Not true. They are in memory because you loaded all UserTests into the context where UserId == "0" || UserId == userId and you have eagerly loaded the related Test. And they are linked to the navigation property because EF performs a "fix-up" based on foreign keys.
The Exam.Tests navigation property will contain any entities loaded into the context with the correct foreign key, but will not necessarily contain all Tests linked to the Exam in the database unless you eagerly load it or turn on lazy loading
I believe that deferred execution causes nothing to happen unless something is actually read from userTests. Try to include var userTestsAsList = userTests.ToList() and check with the debugger if userTestsAsList contains the desired sequence.
As far as I can read your POCO-Relationships and your query, your Repo is returning what you asked for. But did you know you asked for this?
You are navigating from Grand-Child <UserTest> to Child <Test> to Parent <Exam>
Your Entity of <Exam> is being treated as a Grand-Child when it seems to be a Grand-Parent (in fact, a graph root) having children of <Test> who have children / grand-children of type <UserTest>.
As you are eager loading (and serializing?), of course your <Exam> should eager load its <Test> Collection, which should load their <UserTest> Collections.
By working your way down the graph, you are causing a full circle.
Did you mean to have the opposite relationships?
public class Exam
{
public int ExamId { get; set; }
public int TestId { get; set; }
public int SubjectId { get; set; }
public string Name { get; set; }
}
public class Test
{
public int TestId { get; set; }
public int ExamId { get; set; }
public string Title { get; set; }
public virtual ICollection<UserTest> UserTests { get; set; }
}
public class UserTest
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int TestId { get; set; }
public int QuestionsCount { get; set; }
public virtual ICollection<Exam> Exams { get; set; }
}
I'm making many assumptions about your data. This relationships simply makes more sense as real-world entities and by your usage. That Users have Tests and Exams rather than the reverse. If so, this relationship should work with your linq query.
If you try something like:
var userTest = _uow.UserTests
.GetAll()
.Where(t => t.UserId == "0" || t.UserId == userId)
.First();
var name = userTest.Test.Title;
Would your code throw an exception because the Test property hasn't been loaded? I suspect the problem is your repository is using LINQ to SQL and not LINQ to Entities. You can't turn off Lazy Loading with LINQ to SQL. You would have to show how your repository works to fix the problem in that case.
Is there any reson you are using "virtual" for your collections? If you're using "include", I would recommend getting rid of the "virtual"

query base object based on a non related entity list using entity framework

I have the following model:
A workflowconfiguration has a Brand and also has a type.
A Workflowconfiguration has a collection of Approvers.
The Approvers entity has property Status and username.
In another part of the application, we have an entity called RequestBase
This entity has a string property called CurrentStatus.
I need to make a query with linq or EF Lambda expressions that returns me ALL requests which status matches the username on the approvers entity.
I know its a little bit complicated, or really much complicated, lol.
These are my entities(simplified)
public class RequestBase
{
public int RequestBaseId { get; set; }
public string CurrentStatus { get; set; }
}
public class WorkflowConfiguration
{
public int WorkflowConfigurationId { get; set; }
public WorkflowType WorkflowType { get; set; }
public Brand Brand { get; set; }
public virtual ICollection<Approver> Approvers { get; set; }
}
public class Approver
{
public Approver()
{
}
public Approver(string approverUserName)
{
Name = approverUserName;
}
public int Id { get; set; }
public string Name { get; set; }
public string StepName { get; set; } -----||||>>>>>> Must match status?
public int Order { get; set; }
}
and my query?
obviously it does not even compile
return _context.RequestBases.
.Where(a => a.CurrentStatus.Any(workflowconfiguration.Select(b=>b.Approvers.Select(c => c.StepName))));
_context.RequestBases
.Where(rb => _context.Workflowconfiguration
.Any(wc => wc.Approvers
.Any(a => a.StepName == rb.CurrentStatus)));

Retrieving Array or list from object in database

I'm having trouble when I pull an object from the database with getting the framework to also get an an array in the object as well. I found that for sub objects the .Include("subobject") seems to work, but I can't get it to work for arrays or lists.
My Model:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public string[] DataLabels { get; set; }
}
List of Entities:
public class ProgramEntities:DbContext
{
public DbSet<RunData> RunData { get; set; }
public DbSet<RunElement> RunElement { get; set; }
}
Controller Code:
public ViewResult Details(int id)
{
RunData rundata = (from RunData in db.RunData.Include("Runs").in where RunData.id == id select RunData).First();
return View(rundata);
}
I did have all kinds of trouble with it not returning the list of Runs objects, but when I did the .Include("Runs") that fixed the problem. So, now my trouble is the DataLabels string array. If I try .Include("DataLabels") the program fails and says:
A specified Include path is not valid. The EntityType
'Program_Dataviewer.Models.RunData' does not declare a navigation
property with the name 'DataLabels'.
I have searched online some, I'm not seeing any clear cut answers. Thank you for the help.
You can not have collections of primitives in your data model, since each collection must be mapped to a table in the relational space (think about it - how is the database going to organize/save your collection?). What you can do is introduce a table / entity for DataLabels, e.g. something like this:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public List<DataLabel> DataLabels { get; set; }
}
public class DataLabel
{
[Key]
public int id { get; set; }
public string LabelName { get; set; }
}

Categories

Resources