Limit objects in a collection from being serialized based on User - c#

I have a webapi that returns an object that contains some collections of objects which also contains a collection of objects. Each of these objects has a bool that represents if the object is 'published' See classes below for general idea.
public class A {
public int ID { get; set; }
public List<B> b { get; set;}
}
public class B {
public List<C> c { get; set; }
public List<D> d { get; set; }
public bool published { get; set; }
}
public class C {
public string Title { get; set; }
public bool published { get; set; }
}
public class D {
public string Title { get; set; }
public bool published { get; set; }
}
What is the best way to make it so when I serialize any of these objects that an unpublished child objects are not included if the user doesn't meet the requirements, IE not in a specific role. Can I add data attributes to my model in some way? I have had a look into using a custom IContractResolver but I'm not sure its the best way to handle the nested objects. Should I be handling this in the serializing stage or should I be removing the unpublished objects from the children after I get the object from the database.

As the comments (rightly) pointed out I was going about this in slightly the wrong way.
I solved my problems by having two database requests basic looks something like this.
A a = null;
if(User.IsInRole("Role")){
a = db.A.Find(Id);
} else {
a = (from a in db.A
join b in db.B on a.ID equals b.ID
join c in db.C on b.ID equals c.ID
join d in db.D on b.ID equals d.ID
where a.ID == id && b.Published && c.Published && d.Published
select a);
}
if(a == null)
return NotFound();
return Ok(a);
I was trying to avoid code like this but I'm not sure there is a better way to do it.

Actually I would propose for a certain users to load only needed data as this will increase the performance. The case if you want to load all data (both published and unpublished) is trivial. For the user who may view only published items I would make such query:
A model = context.ACollection.Where(a => a.ID == id).Select(a =>
new A {
ID = a.ID,
b = a.b.Where(i => i.published == true).Select(i =>
new B{
published = true,
c = i.c.Where(c_item => c_item.published == true),
d = i.d.Where(d_item => d_item.published == true)
})
}).Single();
This query should give you good performance.

Related

Filter list of entity objects by string child object property using Linq Lambda

I am trying to return an IQueryable lands filtered by a child object property Owner.Name. Is working well with the query style solution, but I want to use a lambda one.
On short these are my classes mapped by EntityFramework:
public class Land
{
public int Id { get; set; }
public virtual ICollection<Owner> Owners { get; set; }
}
public class Owner
{
public int Id { get; set; }
public string Name { get; set; }
public int LandId { get; set; }
public virtual Land Lands { get; set; }
}
The query which is working fine:
var list = from land in db.Lands
join owner in db.Owners on land.Id equals Owner.LandId
where owner.Name.Contains("Smit")
select land;
I was trying using this:
var list = db.Lands.Where(lnd => lnd.Owners.Count() > 0 &&
lnd.Owners.Where(own => own.Name.Contains("Smit")).Count() > 0);
It works only for small lists, but for some with thousands of records it gives timeout.
Well, one issue which may be causing the speed problem is that your lambda version and your non-lambda versions do very different things. You're non lambda is doing a join with a where on one side of the join.
Why not just write the lambda equivalent of it?
var list = db.Lands.Join(db.Owners.Where(x=> x.Name.Contains("Smit")), a=> a.Id, b => b.LandId, (a,b) => a).toList();
I mean, that is the more direct equivalent of your non lambda
I think you can use this one:
var list = db.Lands.Where(lnd => lnd.Owners.Any(x => x.Name.Contains("Smit")));
Try something more straightforward:
var lands = db.Owners.Where(o => o.Name.Contains("Smit")).Select(o => o.Lands);
You just need to make sure that Owner.Name is not null and LINQ will do the rest.

How to query data with calculated fields and map to a ViewModel with a nested ViewModel Collection?

I have referenced numerous questions on this site related to calculated fields and ViewModels, but I can't seem to extrapolate from examples given. I hope that laying out a specific scenario would allow someone to pin point what I can't see. I am new to WebApp design in general. Please take that into consideration. Also, if I've left off any relevant information, please let me know and I will update the question.
Here is the scenario:
I have a complex query that is spanning multiple tables to return data used in calculations. Specifically, I store units for a recipe converted to a base unit and then convert the quantity to the units specified by the user.
I am using AutoMapper to map from entities to ViewModels and vice versa, but I am not sure how to handle the calculated values. Especially with the nested ViewModel Collection thrown into the mix.
Option 1
Do I return an autonomous set of data? Like the following... and then somehow use AutoMapper to do the mapping? Perhaps I would need to do the mapping manually, which I haven't found a solid example which includes nested ViewModels. At this point, I'm not even sure if the following code handles the nested collection correctly for the autonomous data.
var userId = User.Identity.GetUserId();
var recipes = from u in db.Users.Where(u => u.Id == userId)
from c in db.Categories
from r in db.Recipes
join ur in db.UserRecipes.Where(u => u.UserId == userId) on r.Id equals ur.RecipeId
join mus in db.MeasUnitSystems on ur.RecipeYieldUnitSysId equals mus.Id
join muc in db.MeasUnitConvs on mus.Id equals muc.UnitSysId
join mu in db.MeasUnits on mus.UnitId equals mu.Id
join msy in db.MeasUnitSymbols on mu.Id equals msy.UnitId
select new
{
Id = c.Id,
ParentId = c.ParentId,
Name = c.Name,
Descr = c.Descr,
Category1 = c.Category1,
Category2 = c.Category2,
Recipes = new
{
Id = r.Id,
Title = r.Title,
Descr = r.Descr,
Yield = String.Format("{0} {1}", ((r.Yield * muc.UnitBaseConvDiv / muc.UnitBaseConvMult) - muc.UnitBaseConvOffset), msy.Symbol)
}
};
Option 2
Another option that crossed my mind was to return the entities and use AutoMapper as I normally would. Then iterate through the collections and perform the calculations there. I feel like I could make this work, but it seems inefficient to me because it would result in many queries back to the database.
Option 3
???? I can't think of any other method to do this. But, please, if you have suggestions, I am more than willing to hear them.
Relevant Data
Here is the query returning the data I want in SQL Server (more or less).
declare #uid as nvarchar(128) = 'da5435ae-5198-4690-b502-ea3723a9b217'
SELECT c.[Name] as [Category]
,r.Title
,r.Descr
,(r.Yield*rmuc.UnitBaseConvDiv/rmuc.UnitBaseConvMult)-rmuc.UnitBaseConvOffset as [Yield]
,rmsy.Symbol
FROM Category as c
inner join RecipeCat as rc on c.Id = rc.CategoryId
inner join Recipe as r on rc.RecipeId = r.Id
inner join UserRecipe as ur on r.Id = ur.RecipeId and ur.UserId = #uid
inner join MeasUnitSystem as rmus on ur.RecipeYieldUnitSysId = rmus.Id
inner join MeasUnitConv as rmuc on rmus.Id = rmuc.UnitSysId
inner join MeasUnit as rmu on rmus.UnitId = rmu.Id
inner join MeasUnitSymbol as rmsy on rmu.Id = rmsy.UnitId
inner join UserUnitSymbol as ruus on rmsy.UnitId = ruus.UnitId and rmsy.SymIndex = ruus.UnitSymIndex and ruus.UserId = #uid
ViewModels
public class CategoryRecipeIndexViewModel
{
public int Id { get; set; }
public int ParentId { get; set; }
[Display(Name = "Category")]
public string Name { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }
public CategoryRecipeIndexViewModel Category2 { get; set; }
public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}
public class RecipeIndexViewModel
{
public int Id { get; set; }
[Display(Name = "Recipe")]
public string Title { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
[Display(Name = "YieldUnit")]
public string Yield { get; set; }
}
UPDATE 2/10/2018
I found an answer here that does a very good job of explaining exactly what I'm looking at. Particularly under the A Better solution ? section. Mapping queries directly to my ViewModels looks like it would allow me to get my calculated values as well. Problem is, the example given is once again too simplistic.
He gives the following DTO's
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public UserTypeDto UserType { set; get; }
}
public class UserTypeDto
{
public int Id { set; get; }
public string Name { set; get; }
}
And does the following for mapping:
var users = dbContext.Users.Select(s => new UserDto
{
Id = s.Id,
Name = s.Name,
UserType = new UserTypeDto
{
Id = s.UserType.Id,
Name = s.UserType.Name
}
});
Now what if the UserDTO looked like this:
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
public ICollection<UserTypeDto> UserTypes { set; get; }
}
How would the mapping be done if the UserTypes were a collection?
Update 2/13/2018
I feel I am making progress, but am currently headed in the wrong direction. I found this and came up with the following (which currently errors because of the method call in the linq query):
*Note: I removed Category2 from the ViewModel as I found it was not needed and only complicated this further.
query inside index controller method
IEnumerable<CategoryRecipeIndexViewModel> recipesVM = db.Categories
.Where(x => x.ParentId == null)
.Select(x => new CategoryRecipeIndexViewModel()
{
Id = x.Id,
ParentId = x.ParentId,
Name = x.Name,
Descr = x.Descr,
Category1 = MapCategoryRecipeIndexViewModelChildren(x.Category1),
Recipes = x.Recipes.Select(y => new RecipeIndexViewModel()
{
Id = y.Id,
Title = y.Title,
Descr = y.Descr
})
});
Recursive Method
private static IEnumerable<CategoryRecipeIndexViewModel> MapCategoryRecipeIndexViewModelChildren(ICollection<Category> categories)
{
return categories
.Select(c => new CategoryRecipeIndexViewModel
{
Id = c.Id,
ParentId = c.ParentId,
Name = c.Name,
Descr = c.Descr,
Category1 = MapCategoryRecipeIndexViewModelChildren(c.Category1),
Recipes = c.Recipes.Select(r => new RecipeIndexViewModel()
{
Id = r.Id,
Title = r.Title,
Descr = r.Descr
})
});
}
At this point, I don't even have the calculations I require, but that doesn't matter until I get this working (small steps). I quickly discovered you can't really call a method inside a Linq Query. Then a thought occurs to me, if I need to force the Linq Query to execute and then perform all the mapping on the in memory data, then I would essentially be doing the same thing as Option 2 (above), but I could perform the calculations within the ViewModel. This is the solution I will pursue and will keep everyone posted.
You have to iterate over UserType Collection and map the value to UserType dto's collection.
Use this code.
var users = dbContext.Users.Select(s => new UserDto
Id = s.Id,
Name = s.FullName,
UserType = s.UserType.Select(t => new UserTypeDto
{
Id = t.Id,
Name = t.Name
}).ToList()
Hope this will help.
I got it working! ...I think. ...Maybe. If anything, I'm querying the data, mapping it to my ViewModels and I have the calculations too. I do have additional questions, but they are a lot more specific. I will layout the solution I followed and where I think it requires work below.
I basically implemented my Option 2 from above, but instead of iterating through the collections, I just performed the calculations within the ViewModels.
Controller Method
public ActionResult Index()
{
var userId = User.Identity.GetUserId();
var recipes = db.Categories.Where(u => u.Users.Any(x => x.Id == userId))
.Include(c => c.Category1)
.Include(r => r.Recipes
.Select(u => u.UserRecipes
.Select(s => s.MeasUnitSystem.MeasUnitConv)))
.Include(r => r.Recipes
.Select(u => u.UserRecipes
.Select(s => s.MeasUnitSystem.MeasUnit.MeasUnitSymbols)));
IEnumerable<CategoryRecipeIndexViewModel> recipesVM = Mapper.Map<IEnumerable<Category>, IEnumerable<CategoryRecipeIndexViewModel>>(recipes.ToList());
return View(recipesVM);
}
View Models
public class CategoryRecipeIndexViewModel
{
public int Id { get; set; }
public int ParentId { get; set; }
[Display(Name = "Category")]
public string Name { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public ICollection<CategoryRecipeIndexViewModel> Category1 { get; set; }
public ICollection<RecipeIndexViewModel> Recipes { get; set; }
}
public class RecipeIndexViewModel
{
public int Id { get; set; }
[Display(Name = "Recipe")]
public string Title { get; set; }
[Display(Name = "Description")]
public string Descr { get; set; }
public double Yield { get; set; }
public ICollection<UserRecipeIndexViewModel> UserRecipes { get; set; }
[Display(Name = "Yield")]
public string UserYieldUnit
{
get
{
return System.String.Format("{0} {1}", ((Yield *
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvDiv /
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvMult) -
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnitConv.UnitBaseConvOffset).ToString("n1"),
UserRecipes.FirstOrDefault().MeasUnitSystem.MeasUnit.MeasUnitSymbols.FirstOrDefault().Symbol);
}
}
}
public class UserRecipeIndexViewModel
{
public MeasUnitSystemIndexViewModel MeasUnitSystem { get; set; }
}
public class MeasUnitSystemIndexViewModel
{
public MeasUnitIndexViewModel MeasUnit { get; set; }
public MeasUnitConvIndexViewModel MeasUnitConv { get; set; }
}
public class MeasUnitIndexViewModel
{
public ICollection<MeasUnitSymbolIndexViewModel> MeasUnitSymbols { get; set; }
}
public class MeasUnitConvIndexViewModel
{
public double UnitBaseConvMult { get; set; }
public double UnitBaseConvDiv { get; set; }
public double UnitBaseConvOffset { get; set; }
}
public class MeasUnitSymbolIndexViewModel
{
public string Symbol { get; set; }
}
This appears to be working, but I know it needs some work.
For instance, the relation shown between the Recipe and UserRecipe shows one to many. In reality, if the UserRecipe were filtered by the current user, the relationship would be one to one. Also, the same goes for the MeasUnit and the MeasUnitSymbol entities. Currently, I'm relying on the FirstOrDefault of those collections to actually perform the calculations.
Also, I have seen numerous posts that state that calculations should not be done in the View Models. Except for some who say it's okay if it is only a requirement of the View.
Last I will say that paying attention to variable names within the ViewModels would have saved me some headaches. And I thought I knew how to utilize Linq Queries, but had issues with the data returned. It was easier to rely on the eager loading provided by Entity Framework to bring back the hierarchical data structure needed, versus the flat table structures I'm used to working with.
I'm still new to a lot of this and wrapping my head around some of the quirks of MVC and Entity Framework leaves me brain dead after a few hours, but I will continue to optimize and adopt better programming methods as I go.

Simple Linq query that joins two tables and returns a collection

I have two tables LookUpCodes and LookUpValues they are defined as below:
public partial class LookUpCodes
{
public int Id { get; set; }
public int? CodeId { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
public partial class LookUpValues
{
public int Id { get; set; }
public int CodeId { get; set; }
public string CodeValue { get; set; }
public bool? IsActive { get; set; }
}
Each LookUpCode can have multiple Values associated with it. I want to pass in a code and get associated list of values back.
This is probably a common question as I have seen this everywhere, I am not looking for an answer per se, if someone can just explain how to build the proper query I would be obliged.
Here is what I have done so far:
public IEnumerable<LookUpValues> GetValuesByCode(string cd)
{
var query = from code in _context.LookUpCodes
join values in _context.LookUpValues on code.CodeId equals values.CodeId
where code.Code == cd
select new { LookUpValues = values };
return (IEnumerable<LookUpValues>) query.ToList();
}
You are very close to that you are looking for:
public IEnumerable<LookUpValues> GetValuesByCode(string cd)
{
var query = from code in _context.LookUpCodes
join values in _context.LookUpValues
on code.CodeId equals values.CodeId
where code.Code == cd
select values;
return query;
}
Since you have written the join, I assume that you have understood how it works. However let's revisit it:
from a in listA
join b in listB
on a.commonId equals b.commonId
In the above snippet we join the contents of listA with the contents of listB and we base their join on a commonId property existing in items of both lists. Apparently the pair of a and b that fulfill the join criterion it would form one of the possible many results.
Then the where clause is applied on the results of the join. The joined items that pass thewherefilter is the new result. Even at this point the results is still pairs ofaandb`.
Last you project, using the select keyword each pair of the results to a new object. In your case, for each pair of code and values that passed also the where filter, you return only the values.

LINQ - Left Join of 3 ObservableCollections

I have three classes defined like this:
public class MaterialByOperator
{
public int IdOperator{ get; set; }
public int IdMaterial { get; set;}
}
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
}
public class AssignedOperator
{
public int idOperation { get; set; }
public int idOperator { get; set; }
}
IdMaterial in MaterialByOperator is a "ForeignKey" for Material. The relationship is One to Many.
IdOperator in MaterialByOperator is a "ForeignKey" for AssignedOperator in a One to One relationship.
Then I define this 3 ObservableCollection:
public ObservableCollection<Material> Materials;
public ObservableCollection<MaterialByOperator> MaterialsXOperator;
public ObservableCollection<AssignedOperator> AssignedOperators;
What i want is to get the operator names who does not have any materials asigned. I now do it like this:
var mate = MaterialsXOperator.GroupBy(x => x.idOperator); //Group materials by operatorId
//left join assignedOperators with the grouped materials
var opeasigmate = AssignedOperators.GroupJoin(mate, oper => oper.idOperator,
grupo => grupo.Key, (oper, grupo) => new { oper, grupo });
var operWithoutmate = opeasigmate.Where(x => x.grupo.Count() == 0);
What I want to know, as my LINQ knowledge is not very wide (believe it or not, i had it forbidden in my job for years) is there any simplest way of archieving what i want? As i have told, my solution works but i'd like to see other points of view to hopefully learn by the way.
Using Any is definitely simpler:
var operWithoutmate = AssignedOperators
.Where(ao => !MaterialsXOperator.Any(mo => mo.IdOperator == ao.idOperator);
But using join in general is more efficient, so I would suggest you keeping it that way. The only improvement could be to replace x.grupo.Count() == 0 with !x.grupo.Any(). Also the GroupBy in this case is redundant, so the query could be:
var operWithoutmate = AssignedOperators
.GroupJoin(MaterialsXOperator, ao => ao.idOperator, mo => mo.IdOperator,
(ao, moGroup) => new { ao, moGroup })
.Where(r => !r.moGroup.Any())
.Select(r => r.ao);
I personally find the query syntax to be easier and more readable when there are joins involved:
var operWithoutmate =
from ao in AssignedOperators
join mo in MaterialsXOperator on ao.idOperator equals mo.IdOperator into moGroup
where !moGroup.Any()
select ao;

Entity Framework LINQ Get all items part of another collection

Get all the NWatchRelation records from the DBContext that overlap those in the relationsCollection.
The same Id, RelatedNodeId, and RelationType (enum: int) should be what's considered a match.
public class NWatchRelation : INWatchRelation
{
public int Id { get; set; }
public int NodeId { get; set; }
public NWatchNode Node { get; set; }
public int RelatedNodeId { get; set; }
public NWatchNode RelatedNode { get; set; }
public NWatch.NWatchRelationType RelationType { get; set; }
}
INWatchRelation[] relationsCollection = GetRelations();
You can do a LINQ join between these 2 collections.
var result = from a in db.NWatchRelations.AsEnumerable()
join b in relationsCollection on a.RelatedNodeId equals b.RelatedNodeId
&& a.Id equals b.Id
&& a.RelationType equals b.RelationType
select a;
The only way you can do that fully in LINQ to Entities is to manually compose UNION ALL query by using Queryable.Concat like this:
IQueryable<NWatchRelation> query = null;
foreach (var relation in relationsCollection)
{
var m = relation;
var subQuery = db.NWatchRelations
.Where(r => r.Id == m.Id
&& r.RelatedNodeId == m.RelatedNodeId
&& r.RelationType == m.RelationType);
query = query == null ? subQuery : query.Concat(subQuery);
}
But please note that it's a limited approach and will not work if the relationsCollection is big.
You could create a kind of unique key using the three values:
//To create a unique key (an string, which is a primitive type) combining the three values
var keys=relationsCollection.Select(e=>e.Id+"-"+e.RelatedNodeId+"-"+ ((int)e.RelationType)).Distinct();
var query=db.NWatchRelations.Where(r=>keys.Any(k=>k == (SqlFunctions.StringConvert((double)r.Id)+"-"+
SqlFunctions.StringConvert((double)r.RelatedNodeId )+"-"+
SqlFunctions.StringConvert((double)((int)r.RelationType)) ));
If your NWatchRelations table doesn't have many rows or relationsCollection is a small collection, please, use one of the alternatives that were proposed earlier at your convinience.
Also you can have the directly linked like this
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public NWatchRelation()
{
this.INWatchRelation = new HashSet<INWatchRelation>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<INWatchRelation> INWatchRelation { get; set; }
But the entiry relation must be liked like this in order to work properly
Then you could select/list it like this
db.NWatchRelation.INWatchRelation.ToList();

Categories