How to build nested view model via LINQ query - c#

This query below returns 4 rows, all rows corresponding an adres info so there is a "adresType" column which represent its HomeAddress, WorkAdress, etc.
And this is AllAddressViewModel:
public class AllAddressViewModel
{
public AddressModel homeAddress{ get; set; }
public AddressModel workAddress { get; set; }
}
public class AddressModel
{
public adresTypeEnum adresType{ get; set; }
...
This is what I am trying;
var result = ( from Muayene in muayeneRepo
join Adres in adresRepo on Muayene.HastaTc equals Adres.HastaTc
where Muayene.HastaTc == hastaTc.ToString() && Muayene.IsDeleted != true
select new HastaMuayeneKayitViewModel()
{
homeAddress= new AddressModel {
adresType= Adres.AddressType,
...
},
workAddress = new AddressModel
{
adresType= Adres.AdresTipi,
...
I think u already understand what bothers me, I should match all adresType with corresponding view model.. How can i populate the model properly within linq, dont want another step.

A join like that for a Muayene /w 4 addresses would result in 4 records for the same parent record, each having one address.
If the address records have a discriminating field (address type) then a first step might be to introduce inheritance to your domain model so that a Muayene contains a collection of Addresses, but that collection would contain 0-1 (or more) of each applicable address type as a distinct entity type that inherits from Address. This way you would potentially have an easier time converting these to a corresponding view model. (See Table per Hierarchy inheritance. https://msdn.microsoft.com/en-us/data/jj618292)
For building the view model structure you will want to group by the Muayene so that you get a group for each parent and it's collection of child addresses or better, utilize navigation properties to handle this.
It's hard to tell from your code sample but if muayeneRepo and addressRepo are Repositories returning IEnumerable or the like based on database tables this will be a potential issue for performance. Normally you'd have these pulling from DBSets. You should also take advantage of navigation properties in your domain so that a Muayene entity has an ICollection of Addresses.
Using a context-based example:
var muayenes = _context.Muayenes.Include(x => x.Addresses)
.Where(x => x.HastAc == hastAc && false == x.IsDeleted)
.ToList(); // Materializes these entities so further operations are Linq2Object
If you don't have navigation properties and want to join on the DbSets then you can accomplish the same by joining the entities then doing a GroupBy where the group key is the parent entity and the grouped values are the addresses.
The next step I would normally use is either add a constructor or factory class to handle populating a new instance of a view model from my entity, or wiring up something like AutoMapper to do this. Typically I just wire it up myself since it's generally easier to follow than looking elsewhere at mapping rules trying to figure out why something has changed.
for instance: (Constructor)
var viewModels = muayenes.Select(x => new HastaMuayeneKayitViewModel(x)).ToList();
or (Factory)
var viewModels = muayens.Select(x => HastaMuayeneKayitViewModelFactory.Create(x)).ToList();
Addresses can be constructed using the same principles.
The logic for populating the view model and child view models can be moved off to supporting code and re-used rather that ending up in a large Linq expression.

Related

AsNoTracking throwing error Method 'System.Data.Entity.Infrastructure.DbQuery declared on type For DbSet with collection inside

I have an object
public partial class FileAttachment
{
public FileAttachment()
{
this.Tags = new HashSet<Tag>();
}
public virtual ICollection<Tag> Tags {get; set;}
...other propertties
}
My context class is having
public virtual DbSet<FileAttachment> FileAtttachments {get; set;}
public virtual DbSet<Tag> Tags {get; set;}
public virtual DbSet<Item> Items {get; set;}
...
I am using it in my controller like
MyContext.Items.AsNoTracking().Where(i => i.ItemId == inputId &&
MyContext.FileAttachments.AsNoTracking.Where(f => f.RecordId == i.ReferenceId && f.RecordType == 'item').Any()).Any();
The error I am getting is
'Method 'System.Data.Entity.Infrastructure.DbQuery1[Demo.FileAttachment] AsNoTracking()' declared on type 'System.Data.Entity.Infrastructure.DbQuery1[Demo.FileAttachment]' cannot be called with instance of type 'System.Data.Entity.Core.Objects.ObjectQuery`1[Demo.FileAttachment]''
I am new to AsNoTracking(). Anyone can tell where I am doing wrong?
It looks like the actual requirement is to check whether an Item with a specific ID has a related FileAttachment of a specific type. There's no need for AsNoTracking() for this since the query returns a single boolean, not objects that need tracking.
If the classes had proper navigation properties, the query would be a simple:
var found= MyContext.FileAttachments.Any(f=>f.RecordType == 'item'
&& f.Item.Id = ThatItemID);
Without them, the LINQ query will have to JOIN the two "unrelated" entities:
var query = from item in MyContext.Items
join person in MyContext.FileAttachmentes
on file.RecordId equals item.ReferenceId
where item.ItemID == ThatId &&
file.RecordType == 'item'
select 1;
var found=query.Any();
The lack of navigation properties is a very strong sign that the model is wrong though.
A DbContext isn't a model of the database and doesn't have to mirror it. There's no need to have a single DbContext for the entire application either. Even if there are no Foreign Key Constraints between Items and FileAttachments it's possible to add relations between the classes
AsNoTracking() is used to prevent entity tracking by EF. Tracking allows EF to keep track of modifications made and on saving, these changes can be persisted. So, if a query doesn't need tracking (like a simple get query where no data change is persisted), we use AsNoTracking().
In your case, use navigation property if the entities are related instead of calling Context.Entity. Also, AsNoTracking() needs to specified only once in the query before loading the data.

How to use entity's objects which are not loaded because of laziness (lazy-loading)?

I am trying to set up property for an entity which uses entity's objects (other entities loaded from database based on foreign key). My code looks like this:
[Table("Notes")]
public class Note : FullAuditedEntity
{
public virtual int EntityAId { get; set; }
public EntityA EntityA { get; set; }
public virtual int EntityBId { get; set; }
public EntityB EntityB { get; set; }
public List<NoteXHashtag> AdditionalHashtags { get; set; } = new List<CardXHashtag>();
[NotMapped]
public List<Hashtag> AllHashtags
{
get
{
List<Hashtag> allHashtags = new List<Hashtag>();
allHashtags.AddRange(AdditionalHashtags.Select(x => x.Hashtag));
allHashtags.Add(EntityA.Hashtag);
allHashtags.Add(EntityB.Hashtag);
return allHashtags;
}
}
}
When I try to use property AllHashtags from outside, EntityA and EntityB are null. How to tell Entity Framework to load them from database when I need to work with them?
Note: When I am calling AllHashtags from outside, I have both EntityA and EntityB Included:
noteRepository
.GetAll()
.Include(x => x.AdditionalHashtags).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityA).ThenInclude(x => x.Hashtag)
.Include(x => x.EntityB).ThenInclude(x => x.Hashtag)
.Select(x => new NoteDetailDto()
{
Id = x.Id,
AllHashtags = x.AllHashtags
});
If you are using projection (.Select()) then you do not need to use .Include() to have access to related entities.
I would start by looking at what your repository .GetAll() method is returning. .Include() only works against IQueryable so if the repository method is effectively doing something like this:
return _context.Notes.AsQueryable();
or this:
return _context.Notes.Where(x => x.SomeCondition);
Then you can leverage Include() outside of the repository method. However, if you were returning something like this:
return _context.Notes.Where(x => x.SomeCondition).ToList().AsQueryable();
Then the type would grant access the Include() method, but Include would not actually include the related tables. You can observe this by using a profiler on the database to inspect the queries. With Include, the query would join the EntityA and EntityB tables into the SELECT statement.
With regards to your example statement, it's obviously a simplified example you've supplied, but from what I can see it should not execute if the GetAll() method was returning an EF IQueryable. If it did return an EF IQueryable then accessing the AllHashtags property in a Select would result in an error because AllHashtags is not a mapped property of Note. This means that if your real code looks something like that, then GetAll() is not returning an EF IQueryable, or you've possibly created extension methods for Include/ThenInclude for IEnumerable that do an AsQueryable() to satisfy the EF include operations. (These will not work)
To get the results you want, I would recommend avoiding the use of an unmapped property on the entity, but rather take a 2-step approach with an anonymous type and keep the business logic transformation in the business layer:
Step 1. Ensure that the repository method is just returning an EF IQueryable, so context.[DbSet<TEntity>].AsQueryable() or context.[DbSet<TEntity>].Where([condition]) is fine:
return _context.Notes.AsQueryable();
Step 2. Define a DTO for your Hashtag. (avoid send/receive entities)
[Serializable]
public class HashtagDto
{
public int Id { get; set;}
public string Text { get; set; }
// etc.
}
Step 3. Select the appropriate fields you care about into an anonymous type and materialize it:
var noteData = repository.GetAll()
.Select( x= > new {
x.Id,
AdditionalHashtags = x.AdditionalHashtags.Select(h => new HashtagDto { Id = h.Id, Text = h.Text }).ToList(),
EntityAHashtag = new HashTagDto { Id = x.EntityA.Hashtag.Id, Text = x.EntityA.Hashtag.Text },
EntityBHashtag = new HashTagDto { Id = x.EntityB.Hashtag.Id, Text = x.EntityB.Hashtag.Text },
).ToList();
Step 4. Compose your view model / DTO:
var noteDetails = noteData.Select(x => new NoteDetailDto
{
Id = x.Id,
AllHashTags = condenseHashtags(x.AdditionalHashtags, EntityAHashtag, EntityBHashtag);
}).ToList();
Where the condenseHashtags is just a simple utility method:
private static ICollection<HashTagDto> condenseHashtags(IEnumerable<HashtagDto> source1, HashtagDto source2, HashtagDto source3)
{
var condensedHashtags = new List<HashtagDto>(source1);
if (source2 != null)
condensedHashtags.Add(source2);
if (source3 != null)
condensedHashtags.Add(source3);
return condensedHashtags;
}
The above example is synchronous, it can be transformed into async code without too much trouble if load performance is a concern for server responsiveness.
Steps 3 & 4 can be combined in a single statement, but there needs to be a .ToList() between them as Step 4 needs to run against Linq2Object to condense the hashtags. Step 3 ensures that the Linq2EF expression composes an efficient query to just return the information about the Note, and the associated hashtags that we care about, nothing more. Step 4 condenses those individual details into a DTO structure we intend to return.
Step 2 is important as you should avoid sending Entities back to the client/consumers. Sending entities can lead to performance issues and possible errors if lazy loading is enabled, or leads to incomplete renditions of data being passed around if lazy loading isn't enabled and you neglect to eager-load related info. (Is a related detail really #null, or did you forget to include it?) It can also reveal more about your data structure than consumers should necessarily know, and reflects larger data transfer packets than are needed. Accepting entities back from a client is highly inadvisable since it opens the door for unexpected data tampering, stale data overwrites, and your typical gambit of bugs around dealing with reattaching detached entities that a context may know about. If your code simply attaches entities to a DbContext, sets the modified state, and saves changes. Even if you don't do that today, it opens the door for later modifications to start doing it given an entity is already present in the call. Receive DTOs, load the entity, validate a row version, validate the snot out of the DTO against the entity, and only update the fields that are expected to change.

ViewModels and object manipulation in MVC

I want to understand (and finally appreciate because now it's only pain...) more ViewModels and strongly-typed Views in MVC.
My ViewModel
public class Combined
{
public IEnumerable<Domain> Domains { get; set; }
public IEnumerable<RegInfo> RegInfos { get; set; }
public Combined(IEnumerable<Domain> domains, IEnumerable<RegInfo> reginfos)
{
this.Domains = domains;
this.RegInfos = reginfos;
}
In Controller I pass data from repositories to an object of type Combined.
public ActionResult RegDetails(int id = 0)
{
var domain = from x in unitofwork.DomainRepository.Get(n => n.ID == id)
select x;
var reginfo = from y in unitofwork.ReginfoRepository.Get(n => n.ID == id)
select y;
var regdetails = new Combined(domain, reginfo);
return View(regdetails);
}
In a View (using Razor) I have #model project.namespace.Combined so I'm passing an object that holds two lists.
1/ Why can't I access each list item like this #Model.Domain.Name (noobish question but please help me to understand logic behind it)? I can do it form View "level" by using join but it's totally against MVC pattern. I think that only place to join those two tables is in Controller but it will create totally new object so do I need to create a Model for it?
2/ What's the best approach to get an IEnumerable object that will hold data from 2 or more tables that can be used to populate View (join, automapper)?
3/ Is there an approach that will allow me to create a Model that I will be able to use to POST to multiple tables from one FORM?
Thanks in advance.
The logic of fetching the entities in your controller action is fine; that's the job of the controller. You don't, however, need a custom constructor on your view model: just use object initialization.
var regdetails = new Combined { Domains = domain, RegInfos = reginfo }
Now, as far as your view model goes, Domains and RegInfos are IEnumerables, but you're only fetching a single object for each. If your intention is to have a list type, then you should modify your LINQ to select multiple items, but if your intention is to in fact have just one object for each, then you should not use IEnumerables in your view model.
public class Combined
{
public Domain Domain { get; set; }
public RegInfo RegInfo { get; set; }
}
If you do that, then you will be able to access the Name property on the Domain instance with just #Model.Domain.Name. However, if you keep them list-types, then you must loop through them in your view (even if there's only one item in the list):
#foreach (var domain in Model.Domains)
{
// Do something with domain.Name
}
You get indeed a Model property in your view, and you can use Domains and RegInfos properties like you would do in c#. For example :
#{
var firstDomain = Model.Domains.FirstOrDefault();
// do some process with this variable
}
Or :
#foreach(var regInfo in Model.RegInfos)
{
// do some other process
}
If displayed data come from various data sources, it is best to make a specific view model. You need to avoid making calculations or applying business rules in your view, data should be preformatted before that. Another benefit is to pass only required data to your view : you don't want to fetch a huge object (or collection) from your database just for 2 displayed properties.
That view model can also be used when you submit a form. You can get it back if your post action has a parameter of the view model type, and if you correctly generate inputs in your view (by using some HtmlHelper like Html.Editor(For), etc).
Anyway, there's a lot to say about strongly typed views, and many resources / tutorials can be found across the web.

LINQ Group By if an object has x number of properties, then select on each group

I have some experience with LINQ but writing this query is proving to be a bit above my head.
Excuse my pseudo-code...
class Person{
Collection<Communications> {get;set}
}
class Communication{
Collection<PersonSender> {get;set;}
Collection<BuildingSender> {get;set;}
}
class PersonSender{
Collection<Posts> {get;set}
}
class BuildingSender{
Collection<Posts> {get;set}
}
What I want to accomplish: Group the Communication collection on whether it contains an instance of PersonSender or BuildingSender when those instances have a Post instance themselves.
Then I want to perform a select query on each group of Collection objects so I can return an IEnumerable collection of another object Package that is created in the select statement using each Collection's properties. The key here is that I need to perform a seperate select statement on each group returned
This is what I've got for the actual query so far
m.Communications.GroupBy(x =>
new {fromMember = (x.PersonSender.Any() && x.Posts.Any()),
fromBuilding = (x.BuildingSender.Any() && x.Posts.Any())})
.Select(u => new Package(u.PersonSender.First().Member,u.Posts.First()));
However I'm pretty sure this doesn't compile and it doesn't offer me the multiple select statements I need.
Is GroupBy the right way to go about this? Is it even possible?
UPDATE: Per #Hogan I have been able to hack together a work solution. Let me try to clear up what I was trying to do though, my original question wasn't very clear...
This code is part of the class PackageFactory . Each method in this class can be invoked by a Controller in my application asking for a set of Package objects. The Package accepts several types of IEntity objects as parameters and wraps the content that is associated with the relationships the IEntity objects have into an interface that any other controller displaying information on my application can read. TLDR Package is a glorified Adapter pattern design object.
Each method in PackageFactory has the job of querying the Communication repository, finding the relevant Communication objects with the right set of properties, and then passing the subset of objects(that are properties of the Communication object) to the a new Package instance to be wrapped before returning the whole set of Package objects to the controller so they can be rendered on a page.
In the case of the method I am writing for this question the user m has a collection of Communication objects where each Communication comes from an IEntity object(either PersonSender or BuildingSender) that was directed at the user m . My query was an attempt to segregate the Communication objects into two sets where one contains all Communication where PeronSender exists and one where BuildingSender exists. This way my method knows which group gets passed to their respective Package type.
My logic for trying to use GroupBy was that I would rather make the query as generic as possible so that I can expand to more sets later AND/OR increase the performance of the method by not having to call many seperate queries and then join them all. However it seems like specifying distinct select queries on each group is not a possibility.
#Hogan 's answer is close to what I want to be able to do.
Hogan's Answer
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));
Modified Answer
This is what works:
return m.Communications.Where(x => x.SendingPerson.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.SendingPerson.First().Member,m,u.Posts.First()))
.Union(m.Communications.Where(x=> x.BuildingSender.Any() && x.Posts.Any()).Select(u =>
new PostPackage(u.BuildingSender.First().Building,m,u.Posts.First())));
Not exactly the same -- My head was a bit foggy when I wrote this question yesterday.
I think the key is the SelectMany not GroupBy -- This will "flatten" a sub-lists. As I've shown below:
With
class Person{
Collection<Communications> comm {get; set;}
}
class Communication{
Collection<PersonSender> person {get; set;}
Collection<BuildingSender> building {get; set;}
}
class PersonSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
class BuildingSender{
string name {get; set; }
Collection<Posts> posts {get; set;}
}
given that m is a person:
var result =
m.comm.SelectMany(x => x.person).Where(x => x.posts.Any()).Select(new () { x.name, x.posts})
.Union(m.comm.SelectMany(x=> x.building).Where(x => x.posts.Any()).Select(new () {x.name, x.posts}));

Filtering navigation properties in EF Code First

I'm using Code First in EF. Let's say I have two entities:
public class Farm
{
....
public virtual ICollection<Fruit> Fruits {get; set;}
}
public class Fruit
{
...
}
My DbContext is something like this:
public class MyDbContext : DbSet
{
....
private DbSet<Farm> FarmSet{get; set;}
public IQueryable<Farm> Farms
{
get
{
return (from farm in FarmSet where farm.owner == myowner select farm);
}
}
}
I do this so that each user can only see his farms, and I don't have to call the Where on each query to the db.
Now, I want to filter all the fruits from one farm, I tried this (in Farm class):
from fruit in Fruits where fruit .... select fruit
but the query generated doesn't include the where clause, which is very important because I have dozens of thousands of rows and is not efficient to load them all and filter them when they're Objects.
I read that lazy loaded properties get filled the first time they're accessed but they read ALL the data, no filters can be applied UNLESS you do something like this:
from fruits in db.Fruits where fruit .... select fruit
But I can't do that, because Farm has no knowledge of DbContext (I don't think it should(?)) but also to me it just loses the whole purpose of using navigation properties if I have to work with all the data and not just the one that belongs to my Farm.
So,
am I doing anything wrong / making wrong assumptions?
Is there any way I can apply a filter to a navigation property that gets generated to the real query? (I'm working with a lot of data)
Thank you for reading!
Unfortunately, I think any approach you might take would have to involve fiddling with the context, not just the entity. As you've seen, you can't filter a navigation property directly, since it's an ICollection<T> and not an IQueryable<T>, so it gets loaded all at once before you have a chance to apply any filters.
One thing you could possibly do is to create an unmapped property in your Farm entity to hold the filtered fruit list:
public class Farm
{
....
public virtual ICollection<Fruit> Fruits { get; set; }
[NotMapped]
public IList<Fruit> FilteredFruits { get; set; }
}
And then, in your context/repository, add a method to load a Farm entity and populate FilteredFruits with the data you want:
public class MyDbContext : DbContext
{
....
public Farm LoadFarmById(int id)
{
Farm farm = this.Farms.Where(f => f.Id == id).Single(); // or whatever
farm.FilteredFruits = this.Entry(farm)
.Collection(f => f.Fruits)
.Query()
.Where(....)
.ToList();
return farm;
}
}
...
var myFarm = myContext.LoadFarmById(1234);
That should populate myFarm.FilteredFruits with only the filtered collection, so you could use it the way you want within your entity. However, I haven't ever tried this approach myself, so there may be pitfalls I'm not thinking of. One major downside is that it would only work with Farms you load using that method, and not with any general LINQ queries you perform on the MyDbContext.Farms dataset.
All that said, I think the fact that you're trying to do this might be a sign that you're putting too much business logic into your entity class, when really it might belong better in a different layer. A lot of the time, it's better to treat entities basically as just receptacles for the contents of a database record, and leave all the filtering/processing to the repository or wherever your business/display logic lives. I'm not sure what kind of application you're working on, so I can't really offer any specific advice, but it's something to think about.
A very common approach if you decide to move things out the Farm entity is to use projection:
var results = (from farm in myContext.Farms
where ....
select new {
Farm = farm,
FilteredFruits = myContext.Fruits.Where(f => f.FarmId == farm.Id && ...).ToList()
}).ToList();
...and then use the generated anonymous objects for whatever you want to do, rather than trying to add extra data to the Farm entities themselves.
Just figured I'd add another solution to this having spent some time trying to append DDD principles to code first models. After searching around for some time I found a solution like the one below which works for me.
public class FruitFarmContext : DbContext
{
public DbSet<Farm> Farms { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Farm>().HasMany(Farm.FruitsExpression).WithMany();
}
}
public class Farm
{
public int Id { get; set; }
protected virtual ICollection<Fruit> Fruits { get; set; }
public static Expression<Func<Farm, ICollection<Fruit>>> FruitsExpression = x => x.Fruits;
public IEnumerable<Fruit> FilteredFruits
{
get
{
//Apply any filter you want here on the fruits collection
return Fruits.Where(x => true);
}
}
}
public class Fruit
{
public int Id { get; set; }
}
The idea is that the farms fruit collection is not directly accessible but is instead exposed through a property that pre-filters it.
The compromise here is the static expression that is required to be able to address the fruit collection when setting up mapping.
I've started to use this approach on a number of projects where I want to control the access to an objects child collections.
Lazy loading doesn't support filtering; use filtered explicit loading instead:
Farm farm = dbContext.Farms.Where(farm => farm.Owner == someOwner).Single();
dbContext.Entry(farm).Collection(farm => farm.Fruits).Query()
.Where(fruit => fruit.IsRipe).Load();
The explicit loading approach requires two round trips to the database, one for the master and one for the detail. If it is important to stick to a single query, use a projection instead:
Farm farm = (
from farm in dbContext.Farms
where farm.Owner == someOwner
select new {
Farm = farm,
Fruit = dbContext.Fruit.Where(fruit => fruit.IsRipe) // Causes Farm.Fruit to be eager loaded
}).Single().Farm;
EF always binds navigation properties to their loaded entities. This means that farm.Fruit will contain the same filtered collection as the Fruit property in the anonymous type. (Just make sure you haven't loaded into the context any Fruit entities that should be filtered out, as described in Use Projections and a Repository to Fake a Filtered Eager Load.)

Categories