LinqToEntities Generic Classes and Interface - c#

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;
}

Related

How to select certain fields from class with IQueryable?

I have an IQueryable<MainObjectTest> in which there are several fields, but I only need to pull out certain ones (Title and NestedObject).
How can I do this with dynamic select expression?
public class TestIQueryable
{
public IQueryable GetObjectData(IQueryable<MainObjectTest> data)
{
IQueryable requredDataFields = data.Select("new(Title, NestedObject)");
return requredDataFields;
}
public class MainObjectTest
{
public string Title { get; set; }
public DateTime Date { get; set; }
public NestedClassTest NestedObject { get; set; }
}
public class NestedClassTest
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
}
What about making an interface:
interface Interface1
{
public string Title { get; set; }
public NestedClassTest NestedObject { get; set; }
}
Implement the interface in you class and let GetObjectData return the interface
public IQueryable GetObjectData(IQueryable<Interface1> data)
{
IQueryable requredDataFields = data.Select();
return requredDataFields;
}
I solved my problem using model, here steps:
Map all data from database into model if you have nested object => use ToList().
Use DynamicLinq library to select certain fields from model as IQueryable.
Hope it helps someone!
Use an anonymous object like this.
IQueryable requredDataFields = data.Select(x => new { x.Title, x.NestedObject });

Type 'sp' is mapped as a complex type. The Set method, DbSet objects, and DbEntityEntry objects can only be used with entity types, not complex types

I have a stored procedure as:
public partial class StoredProcedureReport_Result
{
public string Contract_QuoteNo { get; set; }
public System.DateTime Contract_StartDate { get; set; }
public System.DateTime Contract_EndDate { get; set; }
public string Contract_AgencyName { get; set; }
public int ContractService_Id { get; set; }
public string Description { get; set; }
public Nullable<System.DateTime> OrderStartDate { get; set; }
public Nullable<System.DateTime> OrderEndDate { get; set; }
public Nullable<int> OrderTermMonths { get; set; }
public Nullable<decimal> MonthlyUnitPrice { get; set; }
public Nullable<decimal> Quantity { get; set; }
public Nullable<decimal> TotalPrice { get; set; }
public System.Guid ContractId { get; set; }
}
I am using the repository pattern to execute the Entity Framework and here is the screen short of stored procedure result from SQL Server
Here is the code for the Repository to call the generic repo
public class ReportService : IReportService
{
private readonly IGenericRepository<StoredProcedureReport_Result> _iGenericReportProcRepository;
public ReportService(IGenericRepository<StoredProcedureReport_Result> iGenericReportProcRepository)
{
_iGenericReportProcRepository = iGenericReportProcRepository;
}
public List<DataExtractionViewModel> GetReportResultByFilter(ReportFilterViewModel filter)
{
List<DataExtractionViewModel> list = new List<DataExtractionViewModel>();
var reportFilterDb =
_iGenericReportProcRepository.ExecuteStoredProcedureFunction("StoredProcedureReport #QuotationNo, #AgencyName, #ContractStartDate, #ContractEndDate, #contractTerm",
new SqlParameter("QuotationNo", SqlDbType.VarChar) { Value = filter.QuotationNo },
new SqlParameter("AgencyName", SqlDbType.VarChar) { Value = filter.AgencyName },
new SqlParameter("ContractStartDate", SqlDbType.DateTime) { Value = filter.ContractStartDate },
new SqlParameter("ContractEndDate", SqlDbType.DateTime) { Value = filter.ContractEndDate },
new SqlParameter("contractTerm", SqlDbType.Int) { Value = filter.Term }
).ToList();
reportFilterDb.ForEach(item =>
{
});
return list;
}
}
Here is the generic code
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly DATAEXTRACTION_DEVEntities _entities;
public GenericRepository(DATAEXTRACTION_DEVEntities dbContext)
{
_entities = dbContext;
}
public virtual IEnumerable<T> ExecuteStoredProcedureFunction(string query, params object[] parameters)
{
return _entities.Set<T>().SqlQuery(query, parameters).ToList();
}
}
I am getting an error
Type 'sp' is mapped as a complex type. The Set method, DbSet objects, and DbEntityEntry objects can only be used with entity types, not complex types
Is this is the best way to exec the stored procedure in generic repo I found the solution from Using Generic Repository and Stored Procedures
But is is not working for me.
You didn't followed the article your mentioned.
Your method
public virtual IEnumerable<T> ExecuteStoredProcedureFunction(string query, params object[] parameters)
{
return _entities.Set<T>().SqlQuery(query, parameters).ToList();
}
should be rewritten as as:
return _entities.Database.SqlQuery<T>(query, parameters).ToList();
Having Set<T>() means that you are working on an entity class defined in your database context but it is not true for StoredProcedureReport_Result. That is what the error message said.

How to handle different entities with same structure - linq queries essentially the same

I have two different entities (database first) that have the exact same structure but different naming conventions. Is there a way I can simplify the seemingly duplicated code that comes from querying them (ie in DoWorkWithSimilar)?
I have tried doing something with generics but am having troubles getting the linq queries to work. Also I try to shy away from generics when possible because I like to be more explicit. Nevertheless, the DoWorkWithSimilar methods in the two respective classes are extremely similar. I am trying to think of a better way to streamline these two classes (DoWorkSimilarOne and DoWorkSimilarTwo) however, due to my constraint on the structure of the underlying classes... I am struggling. Is there no better way?
public class DoWorkSimilarOne : IDoWork
{
public IUnitOfWork unitOfWork;
public DoWorkSimilarOne(IUnitOfWork _unitOfWork)
{
unitOfWork = _unitOfWork;
}
public IEnumerable<int> DoWorkWithSimilar(IEnumerable<int> otherIds)
{
IEnumerable<int> similarOneIds = unitOfWork.OtherSimilarOnes
.Where(x => otherIds.Contains(x.Other.OtherId))
.SelectMany(x => x.SimilarOnes)
.Select(x => x.SimilarOneId).ToList();
return similarOneIds;
}
}
public class DoWorkSimilarTwo : IDoWork
{
public IUnitOfWork unitOfWork;
public DoWorkSimilarTwo(IUnitOfWork _unitOfWork)
{
unitOfWork = _unitOfWork;
}
public IEnumerable<int> DoWorkWithSimilar(IEnumerable<int> otherIds)
{
IEnumerable<int> similarTwoIds = unitOfWork.OtherSimilarTwos
.Where(x => otherIds.Contains(x.Other.OtherId))
.SelectMany(x => x.SimilarTwos)
.Select(x => x.SimilarTwoId).ToList();
return similarTwoIds;
}
}
public class SimilarOne
{
public int SimilarOneId { get; set; }
public OtherSimilarOnes OtherSimilarOne { get; set; }
}
public class SimilarTwo
{
public int SimilarTwoId { get; set; }
public OtherSimilarTwos OtherSimilarTwo { get; set; }
}
public class Other
{
public int OtherId { get; set; }
}
public class OtherSimilarOnes
{
public int Id { get; set; }
public Other Other { get; set; }
public IEnumerable<SimilarOne> SimilarOnes { get; set; }
}
public class OtherSimilarTwos
{
public int Id { get; set; }
public Other Other { get; set; }
public IEnumerable<SimilarTwo> SimilarTwos { get; set; }
}
public interface IDoWork
{
IEnumerable<int> DoWorkWithSimilar(IEnumerable<int> otherIds);
}
Seems to me that you just need to make your method generic and add some base classes, right?
public abstract class ObjectWithId
{
public int Id { get; set; }
}
public class ObjectThatRelates<T> : ObjectWithId
where T : ObjectWithId
{
public int OtherId { get; set; }
public IEnumerable<T> Similar { get; set; }
}
public class Object1 : ObjectWithId
{
public ObjectThatRelates<Object1> { get; set; }
}
public class Object2 : ObjectWithId
{
public ObjectThatRelates<Object2> { get; set; }
}
Then your methods become:
public IEnumerable<int> DoWorkWithSimilar<T>(IEnumerable<int> otherIds)
{
IEnumerable<int> similarOneIds = unitOfWork.ObjectThatRelates<T>
.Where(x => otherIds.Contains(x.OtherId))
.SelectMany(x => x.Similar)
.Select(x => x.Id).ToList();
return similarOneIds;
}
Of course, your definition of IUnitOfWork will have to change so that it can return either ObjectThatRelates<Object1> or ObjectThatRelates<Object2> but this should allow you to avoid duplication of effort.

Extension method with nested generics

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.

Universal data fetcher from similar entities

I have many similar EF5 entities for reference data. For example:
ConsultationType entity
public class ConsultationType
{
public ConsultationType()
{
this.Appeals = new HashSet<Appeal>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Appeal> Appeals { get; set; }
}
LawBranch entity
public class LawBranch
{
public LawBranch()
{
this.Appeals = new HashSet<Appeal>();
}
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Appeal> Appeals { get; set; }
}
DbSet's in DB context
public DbSet<LawBranch> LawBranches { get; set; }
public DbSet<ConsultationType> ConsultationTypes { get; set; }
As you see these entities have similar properties Id and Title.
The actual problem
I need a function that fetches data from database and puts it into list. Then the function inserts predefined object as a first element of this list.
My predefined object:
private class PredefinedReferenceItem
{
public int Id { get; set; }
public string Title { get; set; }
public PredefinedReferenceItem()
{
this.Id = -1;
this.Title = "some text";
}
}
My wrong solution:
Firstly, I created interface IReference that describes reference entity
public interface IReference
{
int Id { get; set; }
string Title { get; set; }
}
Secondly, my reference entities realize this interface
public class ConsultationType : IReference { ... }
public class LawBranch: IReference { ... }
Thirdly, I created the function
public IList<IReference> GetReferenceWithPredefinedItem<T>(DbSet<IReference> dbset)
{
var data = from a in dbset
orderby a.Title
select a;
var list = data.ToList();
list.Insert(0, new PredefinedReferenceItem());
return list;
}
But the function doesn't work in my viewmodel:
return GetReferenceWithPredefinedItem(dbContext.ConsultationTypes);
Error message in VS2012:
The type arguments for method 'Mjc.Client.ViewModels.ViewModelBase.GetReferenceWithPredefinedItem<T>(System.Data.Entity.DbSet<Mjc.Foundation.IReference>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Please help me to find an error or specify the right direction.
You new to change the GetReferenceWithPredefinedItem to use generics with IReference constraint on generic type T, the method should look like:
public IList<T> GetReferenceWithPredefinedItem<T>(DbSet<T> dbset) where T:IReference
{
var data = from a in dbset
orderby a.Title
select a;
var list = data.ToList();
list.Insert(0, new PredefinedReferenceItem());
return list;
}
alexandr-mihalciuc provided right solution. My addition to resolve type reference error:
Final solution:
public List<TEntity> GetReferenceWithPredefinedItem<TEntity>(DbSet<TEntity> dbset) where TEntity : class, new(), IReference
{
var data = from a in dbset
orderby a.Title
select a;
var list = data.ToList();
list.Insert(0, new TEntity() { Id = -1, Title ="aefaef" });
return list;
}

Categories