I'm creating a forum website. More for a learning project than anything else.
My database is fairly simple: a Board has many Thread, and a Thread has many Post. So my domain objects look like this:
public class Board
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Thread> Threads { get; set; }
}
public class Thread
{
public int Id { get; set; }
public string Title { get; set; }
public int BoardId { get; set; }
public virtual Board Board { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Author { get; set; }
public DateTime CreationDate { get; set; }
public string Content { get; set; }
public int ThreadId { get; set; }
public virtual Thread Thread { get; set; }
}
I want my Board ViewModel to look like this:
public class BoardViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int ThreadCount { get; set; }
// Date of the latest post.
public DateTime LastUpdatedDate { get; set; }
}
EDIT: My View will use an IEnumerable<BoardViewModel>. I don't need to use the Post or Thread collections directly.
Creating this view model when using the DbContext object directly is easy, but I don't know what to do if I want to refactor this behind a repository.
I know that returning a view model from the data layer is a bad idea, but just returning a board object would mean the Threads and Posts collections aren't initialized.
Should I just include the collections in the database query (which sounds expensive), or should I include the information in the Board class? Or are there other options?
That depend what you are going to do in your view, if you are going to use the collection of Threads in your view, then you need to load them before use it. In my humble opinion, to achieve the escenario that you want, you should disable lazy loading, wich is the default behavior. It can be turned off for all entities in the context by setting a flag on the Configuration property:
public class YourContext : DbContext
{
public YourContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
Then, you could load the navigation properties by demand in your queries using, for example, one of these extension methods:
public static class IQueryableExtensions
{
public static IQueryable<TEntity> Includes<TEntity>(this IQueryable<TEntity> queryable, params string[] includeProperties)
{
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
public static IQueryable<TEntity> IncludesWithFunc<TEntity>(this IQueryable<TEntity> queryable, params Expression<Func<TEntity, object>>[] includeProperties)
{
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
}
To give you some examples, if you need to load the boards with their Threads to create your ViewModel instances, you could do this:
db.Boards.IncludesWithFunc(b=>b.Threads);
Or this:
db.Boards.Includes("Threads");
And if you need to load another level, then you could do this:
db.Boards.IncludesWithFunc(b=>b.Threads.Select(t=>t.Posts));
Or this:
db.Boards.Includes("Threads.Posts");
If you are using a Generic Repository, you can adapt those methods in your class, for example:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly DbSet<TEntity> _dbSet;
//...
public IQueryable<TEntity> IncludesWithFunc<TEntity>(params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> queryable = _dbSet;
return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
}
Using a class like this you can do something like this in your Business Layer (where I guess you are creating the ViewModel instances):
var rep=new Repository<Board>();
var someBoard=rep.IncludesWithFunc(b=>b.Threads).FirstOrDefault(b=>b.Name="SomeName");
Related
I am using Automapper in order to map a BookingDbModel to a BookingViewModel. The Models looks like this:
public class BookingDbModel : DatabaseModel
{
public ResourceDbModel Resource { get; set; }
public int? ResourceId { get; set; }
//...more
}
public class ResourceDbModel : DatabaseModel
{
public ICollection<ChildDbModel> Children { get; set; }
//...more
}
public class ChildDbModel : DatabaseModel
{
public string name { get; set; }
//...more
}
When mapping from the BookingDbModel to the BookingViewModel, I am using a default Manager and .ProjectTo, that also takes care of all other tables on the database:
(T = ViewModel)
public virtual async Task<List<T>> GetAll()
{
List<T> allEntities = await DbAccess
.GetAll()
.AsNoTracking()
.ProjectTo<T>(Mapper.ConfigurationProvider)
.ToListAsync();
return allEntities;
}
My Problem is now that I would like to include the ResourceObject, but exclude the children that are attached to the Resource whenever I map explicitly the Booking. When I retrieve the Resource itself, the children should be included.
What can I do in my MappingProfile or what parameters for the projectTo Method can I use in order to achieve these results?
CreateMap<BookingDbModel, BookingViewModel>();
I have the following model:
public partial class Device
{
public int Id { get; set; }
public virtual Tablet Tablet { get; set; }
where Tablet is the following:
public class Tablet
{
public string TabletId { get; set; }
public int DeviceId { get; set; }
public virtual Device Device { get; set; }
private ICollection<TabletTransferRequest> _tabletTransferRequests;
public virtual ICollection<TabletTransferRequest> TabletTransferRequests { get => _tabletTransferRequests ?? (_tabletTransferRequests = new List<TabletTransferRequest>()); protected set => _tabletTransferRequests = value; }
}
and the mapping class:
public class TabletMap : IEntityTypeConfiguration<Tablet>
{
public void Configure(EntityTypeBuilder<Tablet> builder)
{
builder.ToTable(nameof(Tablet));
builder.HasKey(p => p.TabletId);
builder.HasOne(p => p.Device)
.WithOne(o => o.Tablet)
.HasForeignKey<Tablet>(p => p.DeviceId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict)
;
}
}
DTO classes:
public class DeviceDisplayDto
{
public int Id { get; set; }
public TabletPartDto Tablet { get; set; }
}
public class TabletPartDto
{
public string TabletId { get; set; }
public List<TabletTransferRequestElementDto> TabletTransferRequests { get; set; }
}
public class TabletTransferRequestElementDto : DeviceRequestElementAbstractDto
{
public string TabletId { get; set; }
public int DeviceId { get; set; }
}
when I try to do the following
var query = _context.Devices.Include(d => d.Tablet).ThenInclude(d => d.TabletTransferRequests);
var devices = new PagedList<DeviceDisplayDto>(query.ProjectTo<DeviceDisplayDto>(_mapperConfig), pageIndex, pageSize);
I got the following:
Error generated for warning
'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning:
An attempt was made to lazy-load navigation property
'TabletTransferRequests' on detached entity of type 'TabletProxy'.
Lazy-loading is not supported for detached entities or entities that
are loaded with 'AsNoTracking()'.'. This exception can be suppressed
or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning'
to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or
'AddDbContext'.
why it's detached?
Is the error occurring on the var devices = new PagedList(...) or later, such as when the controller method completes? I don't recognize that PagedList implementation, but from what you've typed that rendition appears to be initializing with IQueryable<T> rather than a static collection IList<T> which may be an issue if the PagedList ends up attempting to resolve paging from the IQueryable after an initial load.
Normally PagedList would utilize a ToPagedList(page, pageSize) method when working with IQueryable which would trigger a one-off load of a Page. If written as a class initialized with an IQueryable, a data retrieval query could be non-fillable if queried after the DbContext is disposed.
I work on a multilingual site and I have built this structure for my "localizable" entities.
The interfaces:
public interface ILocalizable<T>
where T : ILocalized
{
ICollection<T> Content { get; set; }
}
public interface ILocalized
{
int LanguageId { get; set; }
virtual Language Language { get; set; }
}
And implementation:
public class Entity : ILocalizable<EntityLocalized>
{
public int Id { get; set; }
public ICollection<EntityLocalized> Content { get; set; }
}
public class EntityLocalized : ILocalized
{
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public int EntityId { get; set; }
public string Title { get; set; }
}
The reason for this is that I could write an extension method that allows me to get the right string this way:
Entity entity; // get entity from database
string localizedString = entity.Content.Translate(localized => localized.Title);
Everything works just fine. I just had an idea to write another extension method that would save me some work while building a query. So that I don't have to write all the time:
IQueryable<Entity> query; // some query
return query.Include(entity => entity.Content.Select(localized => localized.Language));
So I have this:
public static IQueryable<TEntity> WithLocalization<TEntity>(this IQueryable<TEntity> query)
{
return query.Include(entity => entity.Content.Select(content => content.Language));
}
But obviously I need to specify the generic type. This won't compile. I tried everything.
public static IQueryable<TEntity> WithLocalization<TEntity>(this IQueryable<ILocalizable<ILocalized>> set) where TEntity : ILocalizable<ILocalized> {}
IQueryable<Entity> query; // get query
query.WithLocalization(); // intellisense doesn't display and this won't compile
I kind of understand the reason. It's been discussed there many times. I'm just wondering if there's a way how to build such an extension method without a need to explicitly use and pass 2 generics types, like:
public static IQueryable<TEntity> WithLocalization<TEntity, TEntityLocalized>(this IQueryable<TEntity> set)
where TEntityLocalized : ILocalized
where TEntity : ILocalizable<TEntityLocalized> {}
Thank you!
I think you want...
public static IQueryable<ILocalizable<TEntityLocalized>> WithLocalization<TEntityLocalized>(this IQueryable<ILocalizable<TEntityLocalized>> query)
where TEntityLocalized : ILocalized {
return query.Include(entity => entity.Content.Select(content => content.Language));
}
You aren't propagating the TEntity generic parameter into the signature correctly.
public static IQueryable<ILocalizable<TEntity>> WithLocalization<TEntity>(this IQueryable<ILocalizable<TEntity>> query)
where TEntity : ILocalized
{
return query.Include (entity => entity.Content.Select (content => content.Language));
}
public class Test
{
public Test()
{
IQueryable<Entity> query;
query.WithLocalization ();
}
}
compiles correctly and gives correct intellisense.
I have to produce an output from 3 separate tables(with a couple of fields from each table) into 1 output. I have a class that represents that output. The data is pulled from linq query of EF 6.1.x ObjectContext(Im stuck with using ObjectContext due to the nature of my clients needs....) entities (the 3 classes properly joined in the query) to a list of the new class (List<>). I populate a grid and all is fine. However the user wants to edit the data in the grid and now I need to push those new changes back.
My question is this: Can I map my new class back to the entities field to field? Or am I stuck with iterating through the collection and updating the tables individually? I thought I could map but I haven't run across anything that substantiates this.
Could you not do this using the "Proxy" pattern?
I've done a 2 entity + Wrapper example pseudo example below.
EF would "Save" the SuperWrapper.DeptProxy and the SuperWrapper.EmpProxy.
public partial class DepartmentEFEntity {
public virtual Guid? DepartmentUUID { get; set; }
public virtual string DepartmentName { get; set; }
public virtual ICollection<EmployeeEFEntity> Employees { get; set; }
}
public partial class EmployeeEFEntity
{
public virtual Guid? ParentDepartmentUUID { get; set; }
public virtual Guid? EmployeeUUID { get; set; }
public virtual DepartmentEFEntity ParentDepartment { get; set; }
public virtual string SSN { get; set; }
}
public class SuperWrapper
{
internal DepartmentEFEntity DeptProxy { get; private set; }
internal EmployeeEFEntity EmpProxy { get; private set; }
public SuperWrapper(DepartmentEFEntity dept, EmployeeEFEntity emp)
{
this.DeptProxy = dept;
this.EmpProxy = emp;
}
public string DepartmentName
{
get { return null == this.DeptProxy ? string.Empty : this.DeptProxy.DepartmentName; }
set { if(null!=this.DeptProxy{this.DeptProxy.DepartmentName =value;}}
}
public string EmployeeSSN
{
get { return null == this.EmpProxy ? string.Empty : this.EmpProxy.SSN; }
set { if(null!=this.EmpProxy{this.EmpProxy.SSN =value;}}
}
}
In my repository, I get data from my TransportedMaterial table. I also 'include' TransportedMaterialPacking to retrieve all related data in this linked table. In this last table I also would like to retrieve the related MaterialPacking data. I don't know how?
On the picture above we see that the MaterialPacking is null (it has not been filled).
Here are the models:
public class TransportedMaterialPacking
{
public int TransportedMaterialPackingID { get; set; }
public MaterialPacking MaterialPacking { get; set; }
public double Quantity { get; set; }
public double? Width { get; set; }
public double? Height { get; set; }
public double? Length { get; set; }
}
public class MaterialPacking
{
public int MaterialPackingID { get; set; }
public string DescriptionFr { get; set; }
public string DescriptionNl { get; set; }
}
The relations are like this:
TransportedMaterial >> TransportedMaterialPacking >> MaterialPacking
public static class ORMExtensions
{
public static IQueryable<T> MyInclude<T, C>(this IQueryable<T> source, Expression<Func<T, C>> function)
where C : class
where T : class
{
return source.Include(function);
}
I use Entity framework.
Thanks.
You can 'cascade' include by doing something like foo.Include("NavigationProperty.SubNavigationProperty") in your query.
Edit Using property notation to include navigation subproperties is possible, see this for example.
You may be able to do an .include("nav1.nav2") to load related entities
I don't know if .include(x => x.nav1.nav2) will work