Linq with EF, include specific columns - sql

I have two classes:
public class Category
{
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string Name { get; set; }
public int? CategoryId { get; set; }
public double Weight { get; set; }
public ICollection<Article> Articles { get; set; }
public bool Hidden { get; set; }
}
public class Article
{
public int Id { get; set; }
[StringLength(255)]
public string Title { get; set; }
public string Body { get; set; }
public double Weight { get; set; }
public Category Category { get; set; }
public int? CategoryId { get; set; }
}
I would like to select some Categories including Articles, but without Article.Body. Method syntax is more preferred.
Something like:
IEnumerable<Category> categories = _context
.Categories
.Where(c => c.Hidden == false)
.Include(c => c.Articles)
.OrderBy(c => c.Weight);
Not sure how to specify which columns exactly to select (eagerly) on the included Articles.

Include doesn't allow projections, you can only include complete entities.
But there is a way out.
This is a typical case that you should solve by table splitting. By table splitting you "split" a table over two (or more) entities, so it's easier to filter e.g. light data from heavy data or public data from secure data.
In your case the class model (for Article) would look like this:
public class Article
{
public int Id { get; set; }
[StringLength(255)]
public string Title { get; set; }
public double Weight { get; set; }
public Category Category { get; set; }
public int? CategoryId { get; set; }
public virtual ArticleBody ArticleBody { get; set; }
}
public class ArticleBody
{
public int Id { get; set; }
public string Text { get; set; }
}
And the mappings:
modelBuilder.Entity<Article>()
.HasRequired(a => a.ArticleBody)
.WithRequiredPrincipal();
modelBuilder.Entity<Client>().ToTable("Article");
modelBuilder.Entity<ArticleBody>().ToTable("Article");
Now if you do...
_context.Categories
.Where(c => !c.Hidden)
.Include(c => c.Articles)
...you'll see that only Articles without body texts will be selected in the generated SQL.
If you want the body as well, you do
_context.Categories
.Where(c => !c.Hidden)
.Include(c => c.Articles.Select(a => a.ArticleBody))

Sorry if i did not understand your question, but I think you can specify what columns you want in your select statement.
Simple example:
var query = from c in Categories
select c.Name, c.CategoryId;

Related

How to establish one-to-many relationship for a code-first approach?

I'm trying to build a recipe app for my spouse. I'm trying to set it up so she can add new recipes to the database as the app grows.
When adding new recipe, she will have three drop-down to pick from to construct her new recipe ingredients. First one will contain a list of ingredients that she can choose from, the second one a list of measuring units and the third one a list of quantities.
Here is what I got so far. Am I heading in the right direction or am I off? I'm using Entity Framework with a code-first approach:
public class Recipes
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
}
public class Units model
{
[Key]
public int Id { get; set; }
public string UnitName { get; set; }
}
public class UnitQty
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class IngredientsModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class RecipeIngredients
{
public int Id { get; set; }
public int RecipesId { get; set; }
[ForeignKey("RecipesId")]
public Recipes Recipes { get; set; }
public int IngredientsModelId { get; set; }
[ForeignKey("IngredientsModelId")]
public IngredientsModel IngredientsModel { get; set; }
public int UnitQtyId { get; set; }
[ForeignKey("UnitQtyId")]
public UnitQty UnitQty { get; set; }
public int UnitsModelId { get; set; }
[ForeignKey("UnitsModelId")]
public UnitsModel UnitsModel { get; set; }
}
After creating the table, controller and the views, this is what I get in the recipe ingredients index view.
Any suggestion will be more than welcome please and thank you
RecipeIngredient class's view
First of all. You are over engineering your domain model. On relational databases Join is bottleneck you should prevent from joins if it doesn't helps you.
public class Recipt
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
public ICollection<RecipeIngredient> Ingredients { get; set; }
}
public class IngredientModel
{
public int Id { get; set; }
public string Name { get; set; }
public IngredientUnit UnitType { get; set; } // Unit model is best to be added here. if it doesn't change in a single IngredientModel.
}
public class RecipeIngredient
{
public int Id { get; set; }
public int UnitQuantiy { get; set; } // No need to more classes.
public IngredientModel Model { get; set; }
public Recipt Recipt { get; set; }
}
public Enum IngredientUnitType // Same Unit Model but less database relation as its small finite collection.
{
Killogram,
Count,
....
}
and according to the Microsoft documents its best to use fluentApi configuration for the relations.
Override this method in your Context:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Recipt>.HasMany(P => P.Ingredients).WithOne(P => P.Recipt);
builder.Entity<RecipeIngredient>.HasOne(P => P.Model);
// There is no need to explicit foreign key definition. but you can explicitly define your foreign keys.
}
And for the last part. in Views you can use extra models called ViewModels.
As above domain turned to a minimal domain you just need to pass a list of IngredientModels to your view to complete your View.

How can I Get the results of the nested query in the form of entities with EF

I have some models in my project:
**Model ServiceDeliveryDoc**
public string Id { get; set; }
public string PartnerDocId { get; set; }
public DateTime Date { get; set; }
public decimal Cost { get; set; }
public string LegalEntityId { get; set; }
[ForeignKey("LegalEntityId")]
[InverseProperty("ServiceDeliveryDoc")]
public LegalEntity LegalEntity { get; set; }
[InverseProperty("ServiceDeliveryDoc")]
public ICollection<ServiceRegistryToServiceDeliveryDoc> ServiceRegistryToServiceDeliveryDoc { get; set; }
**Model ServiceRegistry**
public string Id { get; set; }
public DateTimeOffset Date { get; set; }
public decimal Cost { get; set; }
[InverseProperty("ServiceRegistry")]
public ICollection<ServiceRegistryToServiceDeliveryDoc> ServiceRegistryToServiceDeliveryDoc { get; set; }
**Model ServiceRegistryToServiceDeliveryDoc**
public string Id { get; set; }
public string ServiceRegistryId { get; set; }
public string ServiceDeliveryDocId { get; set; }
[ForeignKey("ServiceDeliveryDocId")]
[InverseProperty("ServiceRegistryToServiceDeliveryDoc")]
public ServiceDeliveryDoc ServiceDeliveryDoc { get; set; }
[ForeignKey("ServiceRegistryId")]
[InverseProperty("ServiceRegistryToServiceDeliveryDoc")]
public ServiceRegistry ServiceRegistry { get; set; }
I write some nested query in SQL to get ServiceDeliveryDoc with
LegalEntity, filtered by ServiceRegistryId:
SELECT ReturnsTable.[Id]
,ReturnsTable.[PartnerDocId]
,ReturnsTable.[Date]
,ReturnsTable.[Cost]
,ReturnsTable.[LegalEntityId]
,LegalEntityTable.[Name]
,ReturnsTable.[DocProcessId]
FROM [Vishnya].[dbo].[ServiceDeliveryDoc] as ReturnsTable
left join [Vishnya].[dbo].[LegalEntity] as LegalEntityTable
on ReturnsTable.[LegalEntityId] = LegalEntityTable.[Id]
where ReturnsTable.Id in ( select ServiceDeliveryDocId
from ServiceRegistry_To_ServiceDeliveryDoc
where ServiceRegistryId = #ServiceRegistryId)
How I can recieve simular result, using Enity framework?
From your SQL query and the model design , there is a one-to-one relationship between ServiceDeliveryDoc and LegalEntity , many-to-many relationship between ServiceDeliveryDoc and ServiceRegistry . For loading related data , you could use include like below:
var result = _context.ServiceDeliveryDoc
.Include(sd => sd.LegalEntity)
.Where(sd =>
_context.ServiceRegistryToServiceDeliveryDoc
.Where(srt => srt.ServiceRegistryId == "101")
.Select(srt => srt.ServiceDeliveryDocId)
.Contains(sd.Id)
)
.Select(sd => new
{
sd.Id,sd.PartnerDocId , sd.Date,sd.Cost,sd.LegalEntityId, sd.LegalEntity.Name
}).ToList();
DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ServiceRegistryToServiceDeliveryDoc>()
.HasOne(sr => sr.ServiceDeliveryDoc)
.WithMany(sd => sd.ServiceRegistryToServiceDeliveryDoc)
.HasForeignKey(sr => sr.ServiceDeliveryDocId);
modelBuilder.Entity<ServiceRegistryToServiceDeliveryDoc>()
.HasOne(sr => sr.ServiceRegistry)
.WithMany(sd => sd.ServiceRegistryToServiceDeliveryDoc)
.HasForeignKey(sr => sr.ServiceRegistryId);
}

map string filed to generic list in automapper based on .net core

I have a DomainModel and a DTO like this :
public class PostEntity: IEntity
{
[Required]
public string Description { get; set; }
public int Id { get; set; }
[Required]
public string Slug { get; set; }
[Required]
public string Tags { get; set; }
[Required]
public string Title { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public PostStatus Status { get; set; }
public User Writer { get; set; }
public int WriterId { get; set; }
}
public class PostDto
{
public int Id { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public string Tags { get; set; }
public string Title { get; set; }
public DateTime CreatedOn { get; }
public List<string> TagList { get; set; }
public PostDto()
{
TagList = new List<string>();
}
}
PostEntity'Tags contains some tags seperated by ",", now I want to split tags value by "," and convert it to List, to do this, I've tried this but I get the below compilation error
CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(",").ToList()));
I get this error :
An expression tree may not contain a call or invocation that uses optional arguments
I can't reproduce your error, it seems to work fine.
Below is an example where the TagList is correctly mapped
The code I used :
MapperConfiguration MapperConfiguration = new MapperConfiguration(configuration =>
{
configuration
.CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(',').ToList()));
});
IMapper mapper = MapperConfiguration.CreateMapper();
PostEntity postEntity = new PostEntity
{
Tags = "Tag1,Tag2,Tag3,Tag4"
};
var mappedObject = mapper.Map<PostEntity, PostDto>(postEntity);
Please bear in mind that Expression.Call API does not support optional parameters. So, you should Replace Split(',') with
Split(',', System.StringSplitOptions.None)
or
Split(',', System.StringSplitOptions.RemoveEmptyEntries)
doing so you won't see that error again.

QueryOver Criteria with Contains

I am trying to get if list of subclass contains a match. I am new at NHibernate and looking for help.
thank you
public class Shop
{
public virtual int ShopId { get; set; }
public virtual string ShopName { get; set; }
public virtual IList<DeliveryDistrict> DeliveryDistricts { get; set; }
}
public class DeliveryDistrict
{
public virtual int DeliveryDistrictId { get; set; }
public virtual Location.District District { get; set; }
}
public class District
{
public virtual int DistrictId { get; set; }
public virtual string DistrictName { get; set; }
}
stores = session.QueryOver<Entities.Shop.Shop>()
.Where(f => f.DeliveryDistricts.Contains(District)).ToList();
stores = session.QueryOver<Entities.Shop.Shop>()
.Where(p => p.DeliveryDistricts.Any(c => c.District.DistrictId == District.DistrictId)).List();
I could make it work with Query(Linq) like this;
stores = session.Query<Entities.Shop.Shop>()
.Where(p => p.DeliveryDistricts.Any(c => c.District.DistrictId == District.DistrictId)).ToList();
but I would like to see if anyone has an example on QueryOver

How to use Raven LoadDocument

I'm having trouble querying RavenDB with even the simplest of queries, probably I'm doing something wrong, but after a few hours I just can't see it anymore. I've googled almost anything I can think of..
I have these entities:
public class User
{
public string Id { get; set; }
public string DisplayName { get; set; }
public string RealName { get; set; }
public string Email { get; set; }
public string PictureUri { get; set; }
public List<Comment> Comments { get; set; }
public List<Role> Roles { get; set; }
}
public class NewsItem
{
public string Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public DateTime Created { get; set; }
public string UserId { get; set; }
public List<Tag> Tags { get; set; }
public List<Comment> Comments { get; set; }
public List<WebImage> Images { get; set; }
}
I want to query these so I get a list of newsItems, but with the user information alongside it. So I read the docs and tried the LoadDocument feature, the index:
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result
{
public string AuthorName { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
select new
{
AuthorName = LoadDocument<User>(newsItem.UserId).DisplayName
};
}
}
Which I try to use like:
var result = _documentSession.Query<NewsItemIndexWithComments.Result, NewsItemIndexWithComments>().AsProjection<NewsItemIndexWithComments.Result>().ToList();
Now I get the number of documents in my list, but the AuthorName is always null. If I don't use the AsProjection method, I won't get any results. Can anyone show me a proper example on which I can experiment further?
Thanks.
_ edit:
That helped a lot, thanks :) Now for step two, I'm sorry if I'm being a bit newbish, but you'll have to start somewhere. In the newsitems there are comments, in these comments there is another reference to the userid. You can probably guess what I want to do: I want the user info for the comments with the comments as well.
new Index:
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result : NewsItem
{
public string AuthorName { get; set; }
public string AuthorId { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
let user = LoadDocument<User>(newsItem.UserId)
select new
{
AuthorName = user.DisplayName,
AuthorId = user.Id,
};
Store(x => x.AuthorName, FieldStorage.Yes);
Store(x => x.AuthorId, FieldStorage.Yes);
}
}
Comment class:
public class Comment
{
public string Id { get; set; }
public string Text { get; set; }
public string UserId { get; set; }
public string User { get; set; }
public DateTime Created { get; set; }
}
How can I query the comments and expand the results for that? Or is it better to create a new index just for the comments and get the user info analog to the solution above?
You're almost there, you just need to store the field you are projecting. Add this to the index constructor, after the map.
Store(x=> x.AuthorName, FieldStorage.Yes);
This is because you want it returned and available for AsProjection to find. If you just wanted to use the author name in a where or orderby, you wouldn't need it.
If you just want to include the comments in your AsProjection, you can simply index the entire object along.
Note that indexing a custom object will mean that you're not able to query on it using a .Where(). RavenDB can only query on flattened results (ints, decimals, strings, dates).
In order to, for instance, query on the title, you will need to create a seperate Property public string Title { get; set; } and map it with Title = newsItem.Title.
public class NewsItemIndexWithComments : AbstractIndexCreationTask<NewsItem, NewsItemIndexWithComments.Result>
{
public class Result : NewsItem
{
public string AuthorName { get; set; }
public string AuthorId { get; set; }
public List<Comment> Comments { get; set; }
}
public NewsItemIndexWithComments()
{
Map = newsItems => from newsItem in newsItems
let user = LoadDocument<User>(newsItem.UserId)
select new
{
AuthorName = user.DisplayName,
AuthorId = user.Id,
Comments = newsItem.Comments.
};
Store(x => x.AuthorName, FieldStorage.Yes);
Store(x => x.AuthorId, FieldStorage.Yes);
Store(x => x.Comments, FieldStorage.Yes);
}
}