At first, here what I have:
4 Classes have some relations between them as:
Project has many Bridges.
Bridge has many Foundations.
Foundation has many PierCaps and many PierColumns.
Project.cs:
public class Project
{
public Project()
{
Bridges = new HashSet<Bridge>();
StartDate = DateTime.Now;
EndDate = DateTime.Now;
}
// Fields
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
// Calculated Fields
public int BridgeCount => Bridges.Count;
public int PierFoundationCount = 0;
public int PierCapCount = 0;
public int PierColumnCount = 0;
// Relation To Children
public virtual ICollection<Bridge> Bridges { get; private set; }
}
Bridge.cs:
public class Bridge : BasicBaseEntityWithName
{
public Bridge()
{
PierFoundations = new HashSet<PierFoundation>();
}
// Fields
public int ProjectID { set; get; }
public Project Project { set; get; }
// Calculated Fields
public int PierFoundationCount => PierFoundations.Count;
public int PierCapCount => PierFoundations.Count;
public int PierColumnCount => PierFoundations.Count;
// Relation To Children
public ICollection<PierFoundation> PierFoundations { get; private set; }
}
PierFoundation.cs:
public class PierFoundation : BasicBaseEntityWithName
{
public PierFoundation()
{
PierCaps = new HashSet<PierCap>();
PierColumns = new HashSet<PierColumn>();
}
// Fields
public float Length { set; get; }
// Calculated Fields
public int PierColumnCount => PierColumns.Count;
public int PierCapCount => PierCaps.Count;
public float FoundationQty => (Length * Width * Height);
// Relations To Parents
public int BridgeID { set; get; }
public Bridge Bridge { set; get; }
// Relations To Children
public ICollection<PierCap> PierCaps { get; set; }
public ICollection<PierColumn> PierColumns { get; set; }
}
PierColumn.cs:
public class PierColumn : BaseEntityNoName
{
// Fields
public float Diameter { set; get; }
// Relations To Parents
public int PierFoundationID { set; get; }
public PierFoundation PierFoundation { set; get; }
}
PierCap.cs:
public class PierCap : BaseEntityNoName
{
// Fields
public float CapWidth { set; get; }
// Relations To Parents
public int PierFoundationID { set; get; }
public PierFoundation PierFoundation { set; get; }
}
BaseEntityNoName.cs:
public class BaseEntityNoName
{
public BaseEntityNoName()
{
AddedBy = Core.General.Global.CurrentUseId;
AddedDate = DateTime.Now;
}
public int Id { get; set; }
public DateTime AddedDate { get; set; }
public int AddedBy { get; set; }
// Becuase they will not appear at new, they will be nullable
public DateTime? ModifiedDate { get; set; }
public int? ModifiedBy { get; set; }
}
BasicBaseEntityWithName.cs:
public class BasicBaseEntityWithName : BaseEntityNoName
{
public string Name { get; set; }
}
Adding to that, I have this method in my Repository:
public IEnumerable<TEntity> Include(params Expression<Func<TEntity, object>>[] includeExpressions)
{
DbSet<TEntity> dbSet = Context.Set<TEntity>();
IEnumerable<TEntity> query = null;
foreach (var includeExpression in includeExpressions)
{
query = dbSet.Include(includeExpression);
}
return query ?? dbSet;
}
My questions are:
Q1. When I use Include the related entities (PierFoundation, PierCap, PierColumn), I don't get them all!. So, when I type:
PierFoundations = CurrentUnitOfWork.PierFoundationRepository.Include(x => x.PierColumns, x => x.PierCaps);
I received just PierFoundation and PierCap, but not PierColumn
What is the wrong here?. I want to retrieve all data in the all three of them.
Q2. In the Project class, I have four calculate fields:
BridgeCount which is solved by Bridges.Count
PierFoundationCount
PierCapCount
PierColumnCount
The last three, I don't know how to get them, because they don't have direct relation with Project
BTW: I am using EF Core 2
The two issues are unrelated, so answering only the main (as indicated by the post title) Q1:
When I use Include the related entities (PierFoundation, PierCap, PierColumn), I don't get them all!.
It's caused by the following line in your repository method implementation:
query = dbSet.Include(includeExpression);
As with many LINQ method, it's important to use the Include method return value when chaining with other calls, and as you can see, this code stores only the last Include call, hence the behavior you are observing.
It can be fixed by changing the implementation like this:
var query = Context.Set<TEntity>().AsQueryable();
foreach (var includeExpression in includeExpressions)
query = query.Include(includeExpression); // Note the usage of query variable
return query;
or simply
return includeExpresions.Aggregate(Context.Set<T>().AsQueryable(), (q, e) => q.Include(e));
But note that EF Core uses different approach for including nested data levels (ThenInclude) which cannot be expressed with Expression<Func<T, object>>, so you might consider exposing IQueryable<TEntity> returning method from your repository and use the EF Core provided Include / ThenInclude extension methods.
What about Q2, consider posting it in a separate SO question (post).
Related
I would like to filter my 'TranslationSet' entities, based on their 'Translations' Collection Navigation Property.
E.g.
If a 'Translation' has a 'LanguageId' of 5 (Italian), then the 'TranslationSet' that contains this 'Translation' should be removed from the result.
Here are my Entity classes:
public class Language
{
public int LanguageId { get; set; }
public string NationalLanguage { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public List<Translation> Translation { get; set; } = new List<Translation>();
}
public class Translation
{
public int TranslationId { get; set; }
public string TranslatedText { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public int TranslationSetId { get; set; }
public TranslationSet TranslationSet {get; set;}
}
public class TranslationSet
{
public int TranslationSetId { get; set; }
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public IEnumerable<Translation> Translations { get; set; }
}
Here is my attempt
From the image you can see that the query fails because a Translation exists with LanguageId of 5.
I have tried many many attempts to resolve this but I can't even get close the LINQ which returns my query correctly.
Please let me know if any further clarification is needed and thanks in advance to anybody who offers help.
My rule of the thumb that nearly always work is: start by querying the entities you want. That will prevent duplicates as you see in your query result. Then add predicates to filter the entities, using navigation properties. That will be:
var sets = TranslationSets // start the query here
.Where(ts => ts.Translations.All(t => t.LanguageId != 5)); // Filter
Or if you like this better:
var sets = TranslationSets // start the query here
.Where(ts => !ts.Translations.Any(t => t.LanguageId == 5)); // Filter
EF will translate both queries as WHERE NOT EXISTS.
I am trying to get data from a table with a get request with a controller. When I make the request with a normal table (TestTable) it is ok, but if I make the request with a relational table I get the fail message:
"The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/xml; charset=utf-8'."
My controller (Mdata):
namespace ScThAsp.Controllers
{
public class MDataController : ApiController
{
public IEnumerable<Måledata> Get()
{
using (var e = new SCTHDBEntities())
{
return e.Måledata.ToList();
}
}
public TestTable Get(int id)
{
using (SCTHDBEntities entities = new SCTHDBEntities())
{
return entities.TestTable.FirstOrDefault(e => e.Id == 1);
}
}
}
}
My Table for måledata is:
public partial class Måledata
{
public int MDid { get; set; }
public Nullable<int> BBid { get; set; }
public Nullable<decimal> Måling1 { get; set; }
public Nullable<decimal> Måling2 { get; set; }
public Nullable<decimal> Måling3 { get; set; }
public Nullable<decimal> Måling4 { get; set; }
public Nullable<System.DateTime> RegTid { get; set; }
public virtual BuildBoard BuildBoard { get; set; }
}
My database looks like:
Database
See link..
I think I mayby should make a inner join with the other table connected to Måledata table - I am not sure how to do that in a EF environment.
I have really tried a lot now - hope for an answer. Thanks
Your class Måledata contains more data that you presented (it is marked as partial) and probably contains stuff related to EF. This magic stuff is not serializable. To avoid problem rewrite results to a plain object with properties you need. This object must be serializable (if contains plain properties and classes it will).
Building upon Piotr Stapp's answer you need to create a DTO (Data Transfer Object) for your Måledata which contains properties as your model, Måledata other than the EF properties. Use some sort of Mapper, maybe AutoMapper to map the required properties in your final response.
public class MaledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
public IEnumerable<MaledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
var result = e.Måledata.ToList();
return Mapper.Map<List<MaledataDTO>>(result);
}
}
I found 2 solutions.
1) Solution was with Automapper (thanks Abdul). Installing automapper and a Using Automapper. Added a class called MåledataDTO : ` public class MåledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
`
In my controller I used the following code
public IEnumerable<MåledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
Mapper.Initialize(config =>
{
config.CreateMap<Måledata, MåledataDTO>();
});
var result = e.Måledata.ToList();
return Mapper.Map<List<MåledataDTO>>(result);
2: In the second solution: In the picture you see the relations bewtween the tables - made in VS - but that creates a problem in the tables Get SET classes. The relation creates a Virtual object in the class - like mentioned before
public virtual BuildBoard BuildBoard { get; set; }
If you delete the relations and make the public partial class Måledata like
in this video
https://www.youtube.com/watch?v=4Ir4EIqxYXQ
the controller should then have one of two solutions:
using (SCTHDBEntities e = new SCTHDBEntities()) {
//this works
//var knud = new List<Måledata>();
//knud = (e.BuildBoard.Join(e.Måledata, c => c.BBid, o => o.BBid,
// (c, o) => o)).ToList();
//return knud;
//this works too
return (from p in e.BuildBoard
where p.BBid == 1
from r in e.Måledata
where r.BBid == p.BBid
select p).ToList();
That was that
Gorm
On my previous work EF keeps query as IQueryable until I used some materialized methods, for example, ToList() or FirstOrDefault(), and I could create big, flexible and fast queries. But on my new work I noted, that sequences inside IQueryable methods have ICollection type and, of course, they have IEnumerable methods (not IQueriable). I can't understand what wrong and how I can change it. I haven't find solution in google. The version of EF is the same as on my previous work (6.1.3).
For Example, I have 2 entity classes and my own class:
public class Client // Entity
{
public int ID { get; set; }
public string FullName { get; set; }
public string Address { get; set; }
...
public virtual ICollection<Parcel> ParcelsSender { get; set; }
public virtual ICollection<Parcel> ParcelsReceiver { get; set; }
}
public class Parcel // Entity
{
public int ID { get; set; }
public string ParcelNumber { get; set; }
...
public int ClientSenderID { get; set; }
public int ClientReceiverID { get; set; }
public virtual Client ClientSender { get; set; }
public virtual Client ClientReceiver { get; set; }
}
public class ClientCustom // My class
{
public int ID { get; set; }
public string FullName { get; set; }
public bool IsInexperienced { get; set; }
}
and I created this EF query:
var clients = context.Clients
.OrderBy(x => x.FullName)
.Select(x => new ClientCustom()
{
ID = x.ID,
FullName = x.FullName,
IsInexperienced = x.ParcelsSender.Select(y => y.ID).FirstOrDefault() == 0
&& x.ParcelsReceiver.Select(y => y.ID).FirstOrDefault() == 0
})
.ToList();
In this case, the problem is that x.ParcelsSender and x.ParcelsReceiver in query are ICollection<Parcel> type; In turn, x.ParcelsSender.Select() and x.ParcelsReceiver.Select() methods returns IEnumerable<Parcel> instead IQueriable<Parcel>. As I know, that means, queries become very slowly with big amount of data.
I have looked through similar questions here, but not seen this specific scenario.
Using EF 6 Code First, I have three tables, A, B and C. The relationship is A => B = 1:M, and B=>C = 1:1
The end result in this schema is that there is an implicit 1:M between A and C.
I do NOT want the consumer of the Entity Framework model to know about B. Ideally they would have a 1:M Navigation property from A to C (and I'd like to be able to surface this Entity Model through Web API and OData as IQueryable)
How could I do this?
If I add a custom [NotMapped] property to A which a collection of C, I have no way of populating C within the getter of that property because the entity doesn't know about its context.
Anyone have any ideas as to how to implement an IQueryable where A has a navigation property to C and B is 'abstracted' out of existence?
EDIT
Attempted to put the following into the code first entity A:
[NotMapped]
public ICollection<C> Cs
{
get { return this.Bs.Select(b => b.C) as ICollection<C>; }
}
But got this error:
The navigation property 'C' is not a declared property on type 'A'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Thanks.
Here is an example.
public static class OrderDAL
{
public static Order Get(int key)
{
using (var context = new AppContext())
{
var order = context.Orders.Include(a => a.OrderDetails.Select(b => b.Information)).FirstOrDefault(a => a.Id == key);
// Fills C.
order.OrderDetailAdditionalInformation = order.OrderDetails.Select(b => b.Information).ToArray();
// Hides information about B.
foreach (var information in order.OrderDetailAdditionalInformation)
{ information.OrderDetail = null; }
order.OrderDetails = null;
return order;
}
}
}
public class AppContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}
// A
public class Order
{
public int Id { get; set; }
public DateTime Date { get; set; }
public ICollection<OrderDetail> OrderDetails { get; set; }
[NotMapped]
public ICollection<OrderDetailAdditionalInformation> OrderDetailAdditionalInformation { get; set; }
}
// B, one A many B
public class OrderDetail
{
public int Id { get; set; }
public int Qty { get; set; }
public string Item { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public OrderDetailAdditionalInformation Information { get; set; }
}
// C, one B one C
public class OrderDetailAdditionalInformation
{
[ForeignKey("OrderDetail")]
public int Id { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Long { get; set; }
public OrderDetail OrderDetail { get; set; }
}
Suppose I have an interface.
public interface IBlogRepository
{
IList<Blog> Blogs(int pageNo, int pageSize);
int TotalPosts();
}
Now I created a class to implement it and use NHibernate.
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Linq;
using NHibernate.Transform;
using System.Collections.Generic;
using System.Linq;
namespace JustBlog.Core
{
public class BlogRepository: IBlogRepository
{
// NHibernate object
private readonly ISession _session;
public BlogRepository(ISession session)
{
_session = session;
}
public IList<Post> Posts(int pageNo, int pageSize)
{
var query = _session.Query<Post>()
.Where(p => p.Published)
.OrderByDescending(p => p.PostedOn)
.Skip(pageNo * pageSize)
.Take(pageSize)
.Fetch(p => p.Category);
query.FetchMany(p => p.Tags).ToFuture();
return query.ToFuture().ToList();
}
public int TotalPosts()
{
return _session.Query<Post>().Where(p => p.Published).Count();
}
}
The above code is from somewhere on the web for creating a blog engine. However I don't know NHibernate at all, I use Entity Framework to do my job.
How can I rewrite the code without using NHiberate?
Entity Models
Assuming we have our entity models like this:
public class Post
{
public Post() { Tags = new List<Tag>(); }
public int Id{ get; set; }
public string Title{ get; set; }
public string ShortDescription{ get; set; }
public string Description{ get; set; }
public string Meta{ get; set; }
public string UrlSlug{ get; set; }
public bool Published{ get; set; }
public DateTime PostedOn{ get; set; }
public DateTime? Modified{ get; set; }
public int CategoryId { get; set; }
public virtual Category Category{ get; set; }
public virtual IList<Tag> Tags{ get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string UrlSlug { get; set; }
public string Description { get; set; }
public virtual IList<Post> Posts { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public string UrlSlug { get; set; }
public string Description { get; set; }
public virtual IList<Post> Posts { get; set; }
}
Context
Our context class is pretty simple. The constructor takes in the name of the connection string in web.config and we define three DbSets:
public class BlogContext : DbContext
{
public BlogContext() : base("BlogContextConnectionStringName") { }
public DbSet<Category> Categories { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Repository
The interface for our blog repository is nice and easy and doesn't change much:
public interface IBlogRepository
{
IEnumerable<Post> Posts(int pageNo, int pageSize);
int TotalPosts();
}
And it's the blog repository itself where things get interesting!
public class BlogRepository : IBlogRepository
{
// NHibernate object replace with our context
private readonly BlogContext _blogContext;
public BlogRepository(BlogContext blogContext)
{
_blogContext = blogContext;
}
//Function to get a list of blogs
public IEnumerable<Post> Posts(int pageNo, int pageSize)
{
//We start with the blogs db set:
var query = _blogContext.Posts
//Filter by Published=true
.Where(p => p.Published)
//Order by date they were posted
.OrderByDescending(p => p.PostedOn)
//Jump through the list
.Skip(pageNo * pageSize)
//Get the required number of blogs
.Take(pageSize)
//Make sure the query include all the categories
.Include(b => b.Category);
//Just return what we have!
return query;
}
//Much simpler function, should be pretty self explanatory
public int TotalPosts()
{
return _blogContext.Posts.Where(p => p.Published).Count();
}
}
Next Steps
OK, so now we have all that set up, and a nice connection string is set up in your web.config, what do we do with it? Well let's get some blogs!
var context = new BlogContext();
var repository = new BlogRepository(context);
var posts = repository.Posts(0, 10);
Then we can do some stuff with those blogs:
foreach(var blog in blogs)
{
Console.WriteLine("Blog Id {0} was posted on {1} and has {2} categories", blog.Id, blog.PostedOn, blog.Categories.Count());
}
Notes
I didn't implement the FetchMany/ToFuture parts as they're not needed here.
An easy way is to use Linq to Entities mapping your model from the DB.
First of all, create in your database the tables exactly as the objects defined in the post you link, remember to add the relationships.
Then add an ADO Entity data model and populate it from your database selecting those tables, you will end with an XXXEntities which is an ObjectContext with all the classes already created.
Finally, doing the query will be something like this (supose your Entities are named BlogEntities):
public IEnumerable<Blog> Posts(int pageNo, int pageSize)
{
using(BlogEntities con = new BlogEntities())
{
var query = con.Post.Include("Category") //<-- If you set "Pluralize fields and properties" when creating your model, it will be "Posts" and "Categories"
.Where(p => p.Published)
.OrderByDescending(p => p.PostedOn)
.Skip(pageNo * pageSize)
.Take(pageSize)
.ToList();
return query;
}
}
public int TotalPosts()
{
using(BlogEntities con = new BlogEntities())
{
return con.Post.Where(p => p.Published).Count();
}
}