i've got many ef core entities with a possibility to join a shared child table ( without FK-s). I created a generic join extension method, but got a bit stuck returning the main parent entity with the child mapped to it.
Heres what i've got :
public interface IBaseEntity
{
int Id { get; set; }
}
public interface IBaseAttachmentEntity:IBaseEntity
{
ICollection<ResourceAttachment> ResourceAttachment { get; set; }
}
public class ResourceAttachment:BaseEntity
{
//PK- Id of the parent table
public long ParentId { get; set; }
// type or enum of a parent table. Should point to which table it points to
public string ResourceType { get; set; }
public string AttachmentType { get; set; }
public string Value { get; set; }
}
public static class EFCoreExtension
{
//this must be a generic method, want to use it for ~30 tables where the entity inherits IBaseAttachmentEntity
public static IQueryable<TEntity> IncludeResourceAttachment<TEntity>
(this IQueryable<TEntity> queryable,IServiceProvider serviceProvider) where TEntity : class,IBaseAttachmentEntity
{
var className = queryable.ElementType.Name;
var attachmentRepository = serviceProvider.GetRequiredService<IResourceAttachmentRepository>();
var attachments = attachmentRepository
.FindAllQueriable(x => x.ResourceType == className); //this part is fine
var joined = (from q in queryable
join a in attachments on q.Id equals a.ParentId into qa
from a in qa.DefaultIfEmpty()
select new {Parent=q,Attachments = qa}); // joining the child table, ending up with a tuple like result
return joined.Select(x => x.Parent); // need to return the Parent together with Parent.ResourceAttachment which id defined in the
}
}
want to use the extension like this:
var result = _deviceServiceService.FindAllQueriable(CurrentUserId(), matchFilter)
.IncludeResourceAttachment(_serviceProvider).ToList();
EDIT:
i have also created a minimal sample project to run. Uses In memory Db with data seeding
https://github.com/rauntska/EFGenericChildJoin
Thanks!
THis might not be what you want, but you might have to settle for it:
public static IEnumerable<TEntity> IncludeResourceAttachment<TEntity>
(this IQueryable<TEntity> queryable, IServiceProvider serviceProvider) where TEntity : class, IBaseAttachmentEntity
{
var className = queryable.ElementType.Name;
var dataContext = serviceProvider.GetRequiredService<DataContext>();
var attachments = dataContext.Set<ResourceAttachment>().Where(x => x.ResourceType == className);
var joined =
from q in queryable
join a in attachments on q.Id equals a.ParentId into qa
select new { Parent = q, Attachments = qa };
foreach (var x in joined)
foreach (var y in x.Attachments)
x.Parent.ResourceAttachment.Add(y);
return joined.Select(x => x.Parent);
}
Related
I need to write a generic Join() function to perform a query between two DBSets, entities of type TEntity and parentEntities of type TParent. What this will do is get me an IQueryable of cObjectNames, each object with the PK of the entity and the name of the parent entity. both types have an IBaseEntity interface so the Id column is available but I need a way to generically specify the foreign key column in entities (fkCol in the example) and the parentEntities name column (parentNameCol).
public static IQueryable<cObjectNames> Join<TEntity, TParent>(IQueryable<TEntity> entities, IQueryable<TParent> parenEntities,
string fkCol, string parentNameCol)
where TEntity : class, IBaseEntity where TParent : class, IBaseEntity
{
IQueryable<cObjectNames> qNames = entities.Join(parenEntities, e => e.fkCol, p => p.Id, (e, p) =>
new cObjectNames() { name = p.parentNameCol, eId = e.Id });
return qNames;
}
I know it is possible to use EF to get the parent object, but I need a generic solution for several such fk relationships, where even the parent name column is not constant. And please save me the Dynamic LINQ suggestions - LINQ generic expressions are so much cooler...
The definition of cObjectNames is
public class cObjectNames
{
public int eId{ get; set; }
public string name{ get; set; }
}
and the IBaseEntity interface is:
public interface IBaseEntity
{
int Id { get; set; }
DateTimeOffset Created { get; set; }
DateTimeOffset? Lastupdated { get; set; }
DateTimeOffset? Deleted { get; set; }
}
Thanks!
Here is implementation. I hope inline comments are useful.
public static class JoinExtensions
{
public static IQueryable<cObjectNames> Join<TEntity, TParent>(this IQueryable<TEntity> entities, IQueryable<TParent> parentEntities,
string fkCol, string parentNameCol)
where TEntity : class, IBaseEntity where TParent : class, IBaseEntity
{
// we can reuse this lambda and force compiler to do that
Expression<Func<TEntity, int>> entityKeySelector = e => e.Id;
Expression<Func<TParent, int>> parentKeySelector = p => p.Id;
var entityParam = entityKeySelector.Parameters[0];
var parentParam = parentKeySelector.Parameters[0];
// Ensure types are correct
var fkColExpression = (Expression)Expression.Property(entityParam, fkCol);
if (fkColExpression.Type != typeof(int))
fkColExpression = Expression.Convert(fkColExpression, typeof(int));
// e => e.fkCol
var fkColSelector = Expression.Lambda(fkColExpression, entityParam);
// (e, p) => new cObjectNames { name = p.parentNameCol, eId = e.Id }
var resultSelector = Expression.Lambda(Expression.MemberInit(Expression.New(cObjectNamesConstrtuctor),
Expression.Bind(cObjectNamesNameProp,
Expression.Property(parentParam, parentNameCol)),
Expression.Bind(cObjectNamesIdProp,
entityKeySelector.Body)),
entityParam, parentParam);
// full Join call
var queryExpr = Expression.Call(typeof(Queryable), nameof(Queryable.Join),
new Type[] { typeof(TEntity), typeof(TParent), typeof(int), typeof(cObjectNames) },
entities.Expression,
parentEntities.Expression,
Expression.Quote(fkColSelector),
Expression.Quote(parentKeySelector),
Expression.Quote(resultSelector)
);
var qNames = entities.Provider.CreateQuery<cObjectNames>(queryExpr);
return qNames;
}
static ConstructorInfo cObjectNamesConstrtuctor = typeof(cObjectNames).GetConstructor(Type.EmptyTypes) ??
throw new InvalidOperationException();
static MemberInfo cObjectNamesNameProp = typeof(cObjectNames).GetProperty(nameof(cObjectNames.name)) ??
throw new InvalidOperationException();
static MemberInfo cObjectNamesIdProp = typeof(cObjectNames).GetProperty(nameof(cObjectNames.eId)) ??
throw new InvalidOperationException();
}
You can retrieve meta-data from your EF-Core context:
IEntityType entityType = context.Model
.FindEntityTypes(typeof(TEntity))
.FirstOrDefault();
From here you can get the navigation properties:
foreach (IReadOnlyNavigation nav in entityType.GetNavigations()) {
if (nav.IsOnDependent) {
var parentProp = nav.Inverse?.PropertyInfo;
var childProp = nav.PropertyInfo;
Type parentType = nav.TargetEntityType.ClrType;
var foreignKeyProps = nav.ForeignKey.Properties;
... etc., etc.
}
}
At least, this is a starting point. Then you will have to create expressions through Reflection. See: How do I dynamically create an Expression<Func<MyClass, bool>> predicate from Expression<Func<MyClass, string>>?.
Hey I have two following classes:
public class Project
{
public int Id { get; protected set; }
public string ProjectName { get; protected set; }
public List<Task> TaskList{ get; protected set; }
}
public class Task
{
public int Id { get; protected set; }
public int ProjectId { get; protected set; }
public string Name { get; protected set; }
}
If I am using Entity Framework is it possible to get Project object from database (eg. by id) and in the same time join tasks list into [Project] TaskList property, or I should first get a project, and then tasks?
How it should be implemented properly?
If you described relationship between tables Project and Task as explained here
than you can directly just call your Project entity; it will come with a list of related Tasks; if you didn't described relationships than you have to create a join query by linq to etnity or use .Inlcude for relate entites to each other.
var tasks = (from items in db.Task
join projects in db.Project on items.ProejctId equals projects.Id
where 1==1 // you can add some other confitions to here
select new SelectItemsList() { //Items you want to select })
Do you require the Project class object like below code? without using .include<>
var res = (from p in db.ProjectTable
select new Project
{
Id = p.Id,
ProjectName = p.ProjectName,
TaskList = (from q in db.TaskTable
where q.ProjectId = p.Id
select q
).ToList()
}).ToList();
return res; //you will get List<Project> with their List<TaskList>
I have 2 tables in the database :
Table: Order (item_id)
Table: Item ( item_id)
When I'm doing the inner join in entity framework, as you can see below, I need to return in one list the result to manipulate this. Usually when I do the select in one single table , I return a LIST from the entity with the tables name, but I dont know how can I return a LIST when I have 2 or more entity , I mean, using inner join, I would like to return a List that I can manipulate in other class. When I use for only one entity, it is perfect and easy.
public List<????????> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new
{
a.order_numer,
a.total,
b.item_code,
b.item_qty
});
return _result;
}
I don't know how to return it !! I tried to use the .TOLIST(), but still coming "anonymous".
Thank you
First you need to create a custom type like
public class OrderItems
{
public int Order_numer { get; set; }
public int Total { get; set; }
public string Item_code { get; set; }
public int Item_qty { get; set; }
}
After then modify your function like
public List<OrderItems> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new OrderItems()
{
Order_numer= a.order_numer,
Total= a.total,
Item_code=b.item_code,
Item_qty=b.item_qty
}).ToList();
return _result;
}
I hope it will work for you.
You can create a compound model that has a property representing each entity.
public class CompoundModel
{
public Entities.Order { get; set; }
public Entities.Item { get; set; }
}
public List<CompoundModel> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new CompoundModel
{
Order = a
Item = b
});
return _result;
}
Alternatively, if you want to flatten your structure, you can create a class that only has four properties.
public class CompoundModel
{
public string OrderNumber { get; set; }
public int Total { get; set; }
public string ItemCode { get; set; }
public int ItemQuantity { get; set }
}
public List<CompoundModel> getTransdataByStatus(string status)
{
contenxt = new Finance_ManagementEntity();
var _result = (from a in contenxt.Orders
join b in contenxt.Items on a.item_id equals b.item_id
select new CompoundModel
{
OrderNumber = a.order_number,
Total = a.total,
ItemCode = b.item_code,
ItemQuantity = b.item_qty
});
return _result;
}
The problem with your code is this part:
select new // This will create an anonymous type
{
a.order_numer,
a.total,
b.item_code,
b.item_qty
}
As the select generates an anonymous type you will get a list of theses anonymous types as a result of the query. In order to get a list typed results, you need to specify the type in the select-clause:
select new TypeYouWantToReturn() // This will create an real type
{
PropA = a.order_numer, // You also need to specify the properties
PropB = a.total, // of the class that you want to assign
PropC = b.item_code, // the resulting values of the query.
PropD = b.item_qty
}
Now the result of the query will return a list of real types. You need to finally call .ToList() so you get a list instead of the IEnumerable that the select statement will return.
I am working on data warehouse application and we have 4 tables where schema is identical. Only difference between those tables is just Table Name.
Table Example:
ps_Contractor
ps_Employee
ps_Union
ps_NonUnion
Schema
id
hourly
benefit
total
Now i need to generate 4 reports based on these tables. Instead of writing 4 separate LINQ queries i would like to write single query where i can pass the table name dynamically.
The question How do i pass the table name dynamically in following LINQ query ?
var data = ( from q in _dbcontext.ps_Contractor
join _l in _dbcontext.log on q.id equals l.tablelogid
where q.hourly = 8
select new{
hourly=q.hourly,
benefit=q.benefit,
total=q.total,
log = l.message
}.ToList();
I have looked at all similar questions suggested by stack overflow. I do not want to use ExecuteStoreQuery.
what options do i have ?
If all the tables have the same columns, then I'd extract an interface out of those tables and create partial entity classes just to implement that interface, finally use that interface to query.
For example:
//entities
public partial class ps_Contractor: ICommonInterface{}
public partial class Table2 : ICommonInterface{}
in the search method I'd pass IEnumerable<ICommonInterface> or IQueryable<ICommonInterface> and apply that query on that. All you'd need to do is to pass different tables to that search method.
Or you can even have kind of generic class of type ICommonInterface and use that to do the query.
public void Example(IQueryable<ICommonInterface>dataSource)
{
var data = ( from q in dataSource
join _l in _dbcontext.log on q.id equals l.tablelogid
where q.hourly = 8
select new{
hourly=q.hourly,
benefit=q.benefit,
total=q.total,
log = l.message
}.ToList();
}
Example(_dbcontext.ps_Contractor.AsQueryable())
This is just a sample that I tested now:
public class Repository
{
private List<string> GetData(IQueryable<IContractor> data)
{
return (from d in data select d.Name).ToList();
}
public List<string> GetFullTime()
{
using (var context = new TestDbEntities())
{
return GetData(context.FTContractors.AsQueryable());
}
}
public List<string> GetPartTime()
{
using (var context = new TestDbEntities())
{
return GetData(context.PTContractors.AsQueryable());
}
}
}
Entities:
public interface IContractor
{
int Id { get; set; }
string Name { get; set; }
}
public partial class FTContractor : IContractor
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class PTContractor : IContractor
{
public int Id { get; set; }
public string Name { get; set; }
}
Test:
[TestMethod]
public void Temp()
{
var tester = new Repository();
var ft = tester.GetFullTime();
var pt = tester.GetPartTime();
Assert.AreEqual(3, ft.Count);
Assert.AreEqual(4, pt.Count);
}
In the database there are two tables containing just Id and Name columns
EF Core no longer has a non generic .set method but This extension class makes it easy to query your table based on a string using dynamic Linq
public static class DbContextExtensions
{
public static IQueryable<Object> Set(this DbContext _context, Type t)
{
return (IQueryable<Object>)_context.GetType().GetMethod("Set").MakeGenericMethod(t).Invoke(_context, null);
}
public static IQueryable<Object> Set(this DbContext _context, String table)
{
Type TableType = _context.GetType().Assembly.GetExportedTypes().FirstOrDefault(t => t.Name == table);
IQueryable<Object> ObjectContext = _context.Set(TableTypeDictionary[table]);
return ObjectContext;
}
}
}
usage:
IQueryable<Object> query = db.Set("TableName");
// Filter against "query" variable below...
List<Object> result = query.ToList();
// or use further dynamic Linq
IQueryable<Object> query = db.Set("TableName").Where("t => t.TableFilter == \"MyFilter\"");
Here's a way to do a dynamic function that accepts a DbSet<T> (type of database class that you want to pass as a parameter) and a specific expression to build a query on that table:
private IQueryable<T> BuildQueriedCollection<T>(Expression<Func<T, bool>> exp, DbSet<T> dbTable) where T : class
{
var appliedQueryCollection = dbTable.AsExpandable().Where(exp);
return appliedQueryCollection;
}
and you could call the function like so:
Expression<Func<MyClass, bool>> myExp = myList => myList... // some condition...;
var dbset = dbContext.MyTable;
var query = BuildQueriedCollection(myExp, dbset);
I have 3 Entities (MasterDoc, Folder, User) that i link together in another Entity (Folder_User).
public class Folder_User
{
public int Id { get; set; }
public MasterDoc MasterDoc { get; set; }
public User User { get; set; }
public Folder Folder { get; set; }
}
After ive inserted an object of type Folder_User into the database, i can (on the same DbContext) query it and retreive the child objects.
public static List<Folder_User> GetAllFolder_USer(User user, DataModel dbContext)
{
List<Folder_User> list = null;
var query = from f in dbContext.Folder_User
where f.User.Id == user.Id
select f;
list = new List<Folder_User>(query);
return list;
}
But after a page-refresh (new dbcontext) when i run the same query the objects reference to MasterDoc is null.
*I have tried turn of lazy loading but nothing seems to fix it.
*Have also checked the database and the table is correctly containing a row with a MasterDoc Id.
You need to Include the MasterDoc in the query:
public static List<Folder_User> GetAllFolder_USer(User user, DataModel dbContext)
{
var query = dbContext.Folder_User.
Include(f => f.MasterDoc).
Where(f => f.User.Id == user.Id);
return query.ToList();
}