Create nested model from List - c#

I have an existing class
public class Employee
{
public int? EmployeeId { get; set; }
public string EmployeeName { get; set; }
public int? LocationId { get; set; }
public string LocationName { get; set; }
public int? DesignationId { get; set; }
public string DesignationName { get; set; }
}
Code for fetching data from database:
using (var ds = new EmployeeDS())
{
using (var dataAdapter = new DataSet.EmployeeDSTableAdapters.EmployeeTableAdapter())
{
dataAdapter.FillEmployee(ds.Employee,Id);
var result = (from DataRow row in ds.Employee.Rows
select new Employee
{
EmployeeId = (row["EmployeeID"] == DBNull.Value) ? null : (int?)row["EmployeeID"],
EmployeeName = (row["EmployeeName"] == DBNull.Value) ? string.Empty : (string)row["EmployeeName"],
LocationId = (row["LocationId"] == DBNull.Value) ? null : (int?)row["LocationId"],
LocationName = (row["LocationName"] == DBNull.Value) ? string.Empty : (string)row["LocationName"],
DesignationId = (row["DesignationId"] == DBNull.Value) ? null : (int?)row["DesignationId"],
DesignationName = (row["DesignationName"] == DBNull.Value) ? string.Empty : (string)row["DesignationName"],
}).ToList();
}
}
Its working fine.But this returning multiple rows for a employee having multiple location or designation.So I need to return the data as nested model like :
public class EmployeeNested
{
public int? EmployeeId { get; set; }
public string EmployeeName { get; set; }
public List<Location> Locations { get; set; }
public List<Designation> Designations { get; set; }
}
public class Location
{
public int? LocationId { get; set; }
public string LocationName { get; set; }
}
public class Designation
{
public int? DesignationId { get; set; }
public string DesignationName { get; set; }
}
I am using this code for returning nested model:
var tempList=result.GroupBy(x => x.EmployeeId, (key, g) => g.OrderBy(e => e.EmployeeId).First());
foreach (var item in tempList)
{
item.Designations = result
.Where(x => x.EmployeeId== item.EmployeeId)
.Select(x => new Designation
{
DesignationId = x.DesignationId,
DesignationName = x.DesignationName
}).ToList();
item.Locations = result
.Where(x => x.EmployeeId== item.EmployeeId)
.Select(x => new Location
{
LocationId = x.LocationId,
LocationName = x.LocationName
}).ToList();
}
Question:
Is there any better solution to convert a flat list to nested List?.
Is it possible to create a generic method for converting flat list to
nested list?. So that I can reuse it for another functions too.
Is it possible to create a nested list directly from dataset?
I'm sure there is a good solid pattern for doing this, I just can't find it.

I don't think there is a way to create a nested list from a dataset other than looping through the results and some programming logic. If your using a relational database I would recommend using entity framework which would generate something similar to your EmployeeNested class once you have your database properly configured.

Is there any better solution to convert a flat list to nested List?.
As per my knowledge better solution is to create utility methods which take input list and processed it into another type of list. Please refer below sample code.
public static List<EmployeeNested> ProcessEmployeeData(List<Employee> result)
{
return result.Where(x => x.EmployeeId.HasValue).GroupBy(x => x.EmployeeId.Value).Select(x => new EmployeeNested
{
EmployeeId = x.Key,
EmployeeName = x.First().EmployeeName,
Locations = x.Select(s => new Location
{
LocationId = s.LocationId,
LocationName = s.LocationName
}).ToList(),
Designations = x.Select(s => new Designation
{
DesignationId = s.DesignationId,
DesignationName = s.DesignationName
}).ToList()
}).ToList();
}
Is it possible to create a generic method for converting flat list to
nested list?. So that I can reuse it for another functions too.
We can't make generic methods unless if there is common property which inherits all property, and how that generic method knows that in which collection need to add some specific object. If there is the way then it going to be complex to implement.
Is it possible to create a nested list directly from dataset?
As per my understanding, we can't make nested collection directly from a dataset, because dataset has no knowledge about in which type of collection we want to bind data which is fetched from the database. if you want to make direct collection then there must be an underlying structure which query database according to our model and bind, that is how ORM framework works like entity framework.

Related

Entity Framework NullReferenceException when accessing lists

I am using Windows Forms and I'm just learning Entity Framework and I have a question: I created a Customer class with a list type of Item and with Entity Framework I created a table for it. But when I try to get the list from it, I get a NullReference exception.
This are my classes:
public class Item
{
public int Id{ get; set; }
public string ItemName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string UserName { get; set; }
public string PassWord { get; set; }
public List<Item> Items { get; set; }
}
And this is the method I created to get the customer list based on the ID of the customer. I get the Id from the login and it works just fine:
public List<Item> CustomerItems()
{
using var context = new CustomerContext();
var customer= context.Customers.Find(Id);
var items = customer.Items;
return items;
}
I wanted to use this method to update the datagrid and add new items to the list.
I added some entries to the Item table but they don't show up in the datagrid.
Please check the loading related data section of the docs for options how to load linked data.
Also in this case you can just use proper query returning only needed data. For example:
var items = context.Customers
.Where(c => c.Id == Id)
.SelectMany(c => c.Items)
.ToList();
In the code snippet presented above a NullReferenceExceptionmay occur in the access to Items in the expression customer.Items when customer is null. The code should be modified to handle the null value, e.g.
var items = (customer == null) ? new List<Item>() : customer.Items;

Entity Framework "FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it." error for a return object

I have this view
[Table("vw_Entity", Schema = "c")]
public partial class vw_Entity : BaseEntity
{
public long? PredictedEntityTypeID { get; set; }
public bool IsManuallyChanged { get; set; }
}
where BaseEntity is the class that stores only my ID and UUID.
This is my DTO return object:
public class EntityDTO
{
public long ID { get; set; }
public LookupTableDetails PredictedEntityTypeId { get; set; }
public bool IsManuallyChanged { get; set; }
}
where LookupTableDetails looks like:
public class LookupTableDetails
{
public long Id { get; set; }
public string Name { get; set; }
}
Now I have this stored procedure that does basically a PATCH. I call it using the following snippet:
var data = await _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.Select(x => new EntityDTO()
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToListAsync();
However, this crashes with an error
FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it
I'm aware what this error does, if I have a object of some other class inside my view then the stored procedure couldn't map it properly and return the error but in this case, my view is clear from obstacles of that type and all I need to do is just return the LookupTableDetails in my DTO object. The error is in
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" }
I tried most of the solutions that the Internet offers, such as wrapping it with IgnoreFilters.., AsEnumerable() etc.
Any ideas what is the cause and how can I prevent it from happening again in the future i.e fix it? :D
Since You iterate over result from the first query, try changing to:
var data = _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.ToList() // force change from IQueryable to IEnumerable and resolve result
.Select(x => new EntityDTO() // You are anyway using all results, so this is ok
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToList();

How to create a where condition to an object inside an object in c# linq?

Let's say I have a class like:
public class TrainingPlan
{
public int Id { get; set; }
public int ProjectId { get; set; }
public string TrainingPlanName { get; set; }
public List<Training> TrainingList { get; set; }
public bool IsDeleted { get; set; }
}
And a Training object inside it:
public class TrainingViewModel : AuditViewModel
{
public int Id { get; set; }
public int ProjectId { get; set; }
public int TrainingPlanId { get; set; }
public bool IsDeleted { get; set; }
public ProjectViewModel ProjectObject { get; set; }
public TrainingPlanViewModel TrainingPlanObject { get; set; }
}
I could write something like this in order to retrieve TrainingPlans where IsDeleted = false and also retrieve the Training Objects attached to it.
var result = _trainingPlanRepository.FindBy(t => t.ProjectId == projectId && t.IsDeleted == false).ToList();
But how do I set a condition for the Training objects where IsDeleted = false also?
You could use Any() like below. Assumption Your t has a List<Training> which you want to query as well to get the non-deleted ones.
var result = _trainingPlanRepository.
Where(t => t.ProjectId == projectId && !t.IsDeleted &&
t.TrainingList.Any(x => !x.IsDeleted)).ToList();
var trainingPlans = _trainingPlanRepository
.Where(t => t.ProjectId == projectId && t.IsDeleted == false)
.ToList();
Now, In trainingPlans variable, All related training objects exist in it, so we want to filter it bases on IsDelted Property. so you can use below code:
foreach (var item in trainingPlans)
{
item.trainingList = item.trainingList.Where(t => !t.IsDelete).ToList();
}
good luck.
I guess there is a one-to-many relation (possibly many-to-many) between TrainingPlan and Training: every TrainingPlan has zero or more Tranings, and every Training belongs to exactly one TrainingPlan, namely the Training that the foreign key TrainingId (or something similar) refers to.
So somewhere deep inside your repository, you'll have two IQueryable sequences:
IQueryable<Training> trainings = ...
IQueryable<TrainingPlan> trainingPlans = ...
You want to query all trainingplans that are not deleted and that have a ProjectId equal to projectId, together with all its not deleted TrainingPlans.
Using method syntax this is fairly straightforward:
var result = trainingPlans
// keep only the training plans you want to keep:
.Where(trainingPlan => trainingPlan.ProjectId == projectId && !trainingPlan.IsDeleted)
// GroupJoin each trainingPlan with its trainings:
.GroupJoin(trainings,
trainingPlan => trainingPlan.Id, // from each training plan take the Id
training => training.TrainingPlanId, // from each training take the foreign key
(trainingPlan, matchingTrainings) => new // take the training plan with all its matching trainings
{ // to make a new
// Select the trainingplan properties you plan to use
Id = trainingPlan.Id,
Name = trainingPlan.Name,
...
// Keep only the non-deleted trainings
Trainings = matchingTrainings.Where(training => !training.IsDeleted)
.Select(training => new
{
// Select only the training properties that you plan to use:
Id = training.Id,
Date = training.Date,
...
// not needed, you know the value:
// TrainingPlanId = training.TrainingPlanId,
})
.ToList(),
});
Another method would be to remove the non-deleted trainings before the GroupJoin:
var validTrainingPlans = trainingPlans.Where(...);
var validTrainings = trainings.Where(training => !training.IsDeleted);
var result = validTrainingPlans.GroupJoin(validTrainings,
... etc

Linq Value cannot be null on FK

Using C# MVC5 Visual studio 2015.
I have a method that contains the following code:
public List<OffersOnPropertyViewModel> Build(string buyerId)
{
var filtered = _context.Properties.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
var model = filtered.Select(c =>
{
var item = new OffersOnPropertyViewModel()
{
PropertyType = c.PropertyType,
NumberOfBedrooms = c.NumberOfBedrooms,
StreetName = c.StreetName,
Offers = c.Offers.Where(d => d.BuyerUserId == buyerId).Select(x => new OfferViewModel
{
Id = x.Id,
Amount = x.Amount,
CreatedAt = x.CreatedAt,
IsPending = x.Status == OfferStatus.Pending,
Status = x.Status.ToString(),
BuyerUserId = x.BuyerUserId
}),
};
return item;
}).ToList();
//TODO: refactor, shorten linq, duping where clause
return model;
}
Here is the model:
public class Property
{
[Key]
public int Id { get; set; }
[Required]
public string PropertyType { get; set; }
[Required]
public string StreetName { get; set; }
[Required]
public string Description { get; set; }
[Required]
public int NumberOfBedrooms { get; set; }
[Required]
public string SellerUserId { get; set; }
public bool IsListedForSale { get; set; }
public ICollection<Offer> Offers { get; set; }
}
In the DB Offers table has the property id as its FK.
The method fails at runtime saying the Value cannot be null.
When I step through I notice the filtered results (in the example its 1 result), is saying offers is null. Although the query just filtered the results based on "x.Offers".
I simply need a way to retrieve a list of property's that have offers made by the buyerId provided. Is my approach wrong? or am i missing a one liner?
Thanks
You will need to add Include() to your LINQ query to bring in child objects, as follows:
var filtered = _context.Properties.Include("Offers")
.Where(x => x.Offers.Any(c => c.BuyerUserId == buyerId)).ToList();
The reason your filter works with the Any() is because when generating the SQL query, this part forms the WHERE clause and is not included in the SELECT.

C# Web API DTO combining two Objects in MVC

Hi I'm a newbie to C# and DTO's and I'm looking for a bit of advice in writing a method. Basically I have two transfer objects, Members and Source. What I'm trying to achieve is display a list of Members from a specific Source.
The only problem is I need to be able to display Members associated with a SourceId from a SourceRef. As I dont want to pass the sensitive MemberID and SourceId so each has a reference id and thats how I will be identifying them in my API
Member Object
public class MemberObj
{
public int memId { get; set; }
public String memRef { get; set; }
public String fName { get; set; }
public String lName { get; set; }
public String email { get; set; }
public String detail { get; set; }
public int sourceId { get; set; }
}
Source Object
public class SourceObj
{
public int sourceId { get; set; }
public String sourceRef { get; set; }
}
So I would like to go to the address for example
http://..../api/Member/Source/{sourceRef}
and display the list of Members associated to the sourceId via the sourceRef
I came up with something along these lines....
public IEnumerable<MemberObj> GetMem(String code)
{
var sc = db.Sources;
var thisSrc = sc.Where(s => s.sourceRef == code).SingleOrDefault();
return db.Members.Select(s => new MemberObj
{
memId = s.memId,
firstName = s.firstName,
lastName = s.lastName,
email = s.emailAddress,
memRef = s.memRef
}).AsEnumerable().Where(s => s.sourceRef== thisSrc.sourceRef);
But this returns nothing.
The following accepts code as the sourceRef and returns the SourceID that the ref corresponds too.
From here, it simply filters all members to only the ones with the matching sourceID. (I don't have a copy of VS near me so the syntax may be out! If only Notepad++ had intelisense...)
public IEnumerable<MemberObj> GetMem(String code)
{
int soureID = db.Sources.Where(s => s.sourceRef == code).SingleOrDefault().sourceID; //I'm assuming code is the source ref??
//Insert and handle your sourceID == 0 checks here.
//...
return db.Members.Where(m => m.sourceId == sourceID);
}
This should work:
public IEnumerable<MemberObj> GetMem(String code)
{
var thisSrc = db.Sources
.Where(s => s.sourceRef == code)
.SingleOrDefault();
if(thisSrc == null)
return Enumerable.Empty<MemberObj>();
return db.Members.Where(m => m.sourceId == thisSrc.sourceId);
}
Take in account, that you should handle the case when there are more than one source by given code (SingleOrDefault throws an exception in that case.)
If you are sure that is not a case use FirstOrDefault instead.
Just building into Hamlet's answer, you could do something like this to return a DTO instead of your Member Entity:
public IEnumerable<MemberDTO> GetMem(String code)
{
//Get the source object by sourceRef
var thisSrc = db.Sources
.Where(s => s.sourceRef == code)
.SingleOrDefault();
if(thisSrc == null)
return Enumerable.Empty<MemberObj>();
//Filter Members by sourceId and map the results to a MemberDTO with less fields
var data = (from m in db.Members
where m.sourceId == thisSrc.sourceId
select new MemberDTO
{
MemberId = m.memId,
//map the rest of your DTO fields..
}).ToList();
return data;
}

Categories

Resources