Extension method with nested generics - c#

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.

Related

Json property coming back null despite having value

I am making a call to a mongodb to get a list of values but one of the properties is coming back null, even though it has a value. I will try to give as much info as possible.
call to to get shipment info
var mobs = new List<ShipmentInformation>();
mobs = await GetAll();
Definition of GetAll() method:
public abstract class GenericDocumentRepository<TEntity> : IGenericDocumentRepository<TEntity>, IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable where TEntity : IEntity
{
public virtual Task<List<TEntity>> GetAll();
}
Shipmentinformation class:
public class ShipmentInformation : ResourceObject<string, ShipmentInformationAttributes, ShipmentInformationRelationships>
{
public override string type { get; set; }
public override ShipmentInformationAttributes attributes { get; set;
}
public override ShipmentInformationRelationships
relationships { get; set; }
}
public class ShipmentInformationRelationships : RelationshipsObject
{
public JobInfo Job { get; set; }
}
public class JobInfo
{
public List<JobData> data { get; set; }
}
Result:
}
here is image of data from mongo
As you can see job in mongo is not null but result says its null.
public class JobInfo
{
public List<JobData> Data { get; set; }
}
Should be named Data, so that the casing matches that of your Mongo Object.
However it would also be possible to add a BsonElement Attribute like this
public class JobInfo
{
[BsonElement("Data")]
public List<JobData> data { get; set; }
}

Create generic Function to filter many Objects that have the same Base Class

I'm trying to create a generic Function to pass a filter that I intend to use for many Objects in my project. All of these objects will have a Base Class called BaseObject.
For example:
public class Artist : BaseObject
{
public string Name { get; set; }
//...more properties
}
public class Album : BaseObject
{
public string Title { get; set; }
//...more properties
}
BaseObject has the following:
public class BaseObject
{
public BaseObject()
{
Oid = Guid.NewGuid();
}
[Key]
public Guid Oid { get; set; }
[ScaffoldColumn(false)]
public DateTime? DateCreated { get; set; }
[ScaffoldColumn(false)]
public DateTime? DateUpdated { get; set; }
[ScaffoldColumn(false)]
public DateTime? DateDeleted { get; set; }
[ScaffoldColumn(false)]
public bool IsDeleted { get; set; }
}
I would like to create a Method that could be used for Artist and Album, I have the following, but isn't working, I'm not understanding <T>.
public static IQueryable<Entities.Core.BaseObject> WhereActive<T>(this IQueryable<Entities.Core.BaseObject> query)
{
return query.Where<Entities.Core.BaseObject>(b => !b.IsDeleted);
}
Above, I tried to replace Entities.Core.BaseObject with T, but wasn't working.
When I call db.Artist with the Where clause:
Artist artist = db.Artist
.WhereActive<Artist>()
.Where(a => string.IsNullOrEmpty(a.Name.Trim()))
.FirstOrDefault();
I get an error:
'BaseObject' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'BaseObject' could be found (are you missing a using directive or an assembly reference?)
I am sure this is a simple fix, but I am new to LINQ and whatnot.
You need to return IQueryable<T>, and have a generic constraint on T to specify it is a Entities.Core.BaseObject (or a class that derives from it)
public static IQueryable<T> WhereActive<T>(this IQueryable<T> query)
where T : Entities.Core.BaseObject
{
// no need to specify the generic <T> here as well...
return query.Where(b => !b.IsDeleted);
}
You need to constrain the generic parameter to inherit from BaseObject and use it as the input and output of your extension method.
public static IQueryable<T> WhereActive<T>(this IQueryable<T> query)
where T: BaseObject
{
return query.Where(b => !b.IsDeleted);
}

Base<T1, T2> where T1:class where T2:class 'The type mus be a reference...'

I'm not sure where I'm messing this up. Been looking for an answer but none solves my actual problem.
My code is as follows:
namespace BusinessLogic
{
public abstract class CRUDBaseEntity<TDTO, TEntity>
where TDTO : class
where TEntity : class
{
public virtual TDTO GetSingle(Expression<Func<TDTO, bool>> where)
{
TDTO item = null;
using (var context = new MyEntities())
{
Func<TDTO, bool> delegated = where.Compile();
Func<TEntity, bool> destWere = d => delegated(MapDTOFromEntity(d));
IQueryable<TEntity> dbQuery = context.Set<TEntity>();
var fetch = dbQuery
.AsNoTracking()
.FirstOrDefault(destWere);
if (fetch == null) return null;
item = MapDTOFromEntity(fetch);
}
return item;
}
}
public class Specific : CRUDBaseEntity<DTO.DTOSpecific, DataAccess.SpecificEntity>
{
}
}
namespace Client
{
public class ClientClass
{
public void Caller()
{
var fetch = new BusinessLogic.Specific().GetSingle(f => f.SomeProperty.Equals("someValue"));
}
}
}
As a result, I'm getting:
"The type 'DataAccess.SpecificEntity' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'BusinessLogic.CRUDBaseEntity<TDTO,TEntity>'
Evidently, there's no issue with the DTO as it is referenced at the client.
Notice that BusinessLogic, DataAccess and Client are 3 diferent projects of the same solution.
EDIT:
SpecificEntity is an EntityFramework autogenerated class representing an Entity from my database. Quite plain:
namespace DataAccess
{
using System;
using System.Collections.Generic;
public partial class SpecificEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public SpecificEntity()
{
this.SpecificEntityPriceList = new HashSet<SpecificEntityPriceList>();
}
public string SpecificEntityId { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public virtual SpecificEntityCategory SpecificEntityCategory { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<SpecificEntityPriceList> SpecificEntityPriceList { get; set; }
}
}

Entity Framework storing collection size in 1-* relationships

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");

LinqToEntities Generic Classes and Interface

public class CoreEntity
{
public int Id { get; set; }
//some additional common fields like insertedBy, insertedAt etc
}
public interface IUniqueObject
{
Guid Uid { get; set; }
}
public class Tag : CoreEntity
{
public string TagName { get; set; }
}
public class ItemTags : CoreEntity
{
public Guid TaggedItemUid { get; set; }
public int TagId { get; set; }
}
public class Repository<T>
where T : CoreEntity
{
public DbSet<T> TheEntitySet { get; set; }
public DbSet<ItemTags> ItemTagSet { get; set; }
public IQueryable<T> AddTagsFilter(IQueryable<T> query, List<int> tags)
{
var ts = ItemTagSet;
if (typeof (T) is IUniqueObject)
{
query = query.Where(f => ts.Any(e => e.TaggedItemUid == ((IUniqueObject)f).Uid && tags.Contains(e.TagId));
}
return query;
}
}
//usage
public class Departman : CoreEntity, IUniqueObject
{
public Guid Uid { get; set; }
//some other fields
}
class Test
{
void xx()
{
var r = new Repository<Departman>();
}
}
The classes are simplified for the purpose of the problem they do much more than shown here. The thing is that Linq to Entities does not allow casting ((IUniqueObject)f).Uid. So how can apply the Tags Filter knowing that generic type T implements IUniqueObject interface. I tried using some GenericMethod with "T2 where : CoreEntity , IUniqueObject" but I could not cast type T to T2, anyways
any help is appreciated.
The Where statement in LinqToEntities will be converted to SQL syntax. Every instruction must be translatable into SQL - which means no casting. Still thinking about a more helpful answer. I think you'll need to have distinct functions for the different query types, e.g. add a method like:
public IQueryable<T> AddTagsFilterToUniqueObject(IQueryable<T> query,
List<int> tags) where T : IUniqueObject
{
var ts = ItemTagSet;
query = query.Where(f => ts.Any(e => e.TaggedItemUid == (f.Uid && tags.Contains(e.TagId));
return query;
}

Categories