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);
Related
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);
}
I have two Models:
public class StudentModel
{
public int Student_Id {get;set;}
public string Student_Name {get;set;}
public int Class_Id {get;set;}
}
public class ClassModel
{
public int Class_Id {get;set;}
public string Class_Name {get;set;}
}
I want to return values from both the tables. What should my return value be?
public ?? GetStudentClass(StageModel model)
{
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["LO"].ConnectionString))
{
string getstudent= $"select * from student s,class c where s.classid = c.classid and s.studentid = #studentid;";
DynamicParameters parameter = new DynamicParameters();
parameter.Add("#studentid", model.Student_Id, DbType.Int64);
connection.Open();
result = connection.Execute(getstudent, parameter);
}
}
I should use the output of the SQL to print, using the model defined.
It seems that you are using Dapper for your work. But you have differences between your properties names and your database fields names. This makes the work with Dapper more difficult, so I recommend to change your properties to match the names in the database fields.
With this changes you can get your classes filled automatically by Dapper
First your StudentModel needs to have an instance of the ClassModel declared in the class
public class StudentModel
{
public int StudentId {get;set;}
public string StudentName {get;set;}
public int ClassId {get;set;}
public ClassModel StudentClass {get;set;}
}
public class ClassModel
{
public int ClassId {get;set;}
public string ClassName {get;set;}
}
now you change the method in this way
public StudentModel GetStudent(StageModel model)
{
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["LO"].ConnectionString))
{
string getstudent= #"SELECT s.*, c.ClassId, c.ClassName
FROM student s
INNER JOIN class c on s.classid = c.classid
WHERE s.studentid = #studentid;";
DynamicParameters parameter = new DynamicParameters();
parameter.Add("#studentid", model.Student_Id, DbType.Int64);
connection.Open();
result = connection.Query<StudentModel, ClassModel, StudentModel>
(getstudent, ((s, c) => { s.ClassModel = c; return s;}),
splitOn:"ClassId", parameter);
return result;
}
}
Dapper will create for you the StudentModel instance and the ClassModel instance knowing how to fill the two instances when it meets the ClassId field.
The lambda expression will receive the two instances created by Dapper and you just need to set the ClassModel inside the StudentModel received by the lambda.
This approach follows the impression from your question that between a Student and a Class there is a 1:1 relationship. Of course the matter is more complex if the relationship is 1:N and you need a List inside the StudentModel class
I have two sql database tables with a 1:n relationship. For my ASP.NET MVC-solution I have enabled EF-code-first-migration and the proper DBContext and classes established.
I would like to write an MVC-controller that joins both tables in order to select specific records for display in a view.
Here are the two classes:
public class Tbl_Group_Communities : Entity
{
public string GKZ { get; set; }
public int G_ID { get; set; }
}
public class Katastralgemeinden : Entity
{
public string KGNr { get; set; }
public string KGName { get; set; }
public string GKZ { get; set; }
public string GemeindeName { get; set; }
}
So far I have been able to come up with a working controller for the tables by themselves but not joined. Below the working controller for the first class:
public IEnumerable<Tbl_Group_Communities> Get()
{
var entities = UnitOfWork.GetAll<Tbl_Group_Communities>().ToList();
return entities;
}
I think, the join can be done with Linq but I have no idea how/where to start.
The common key for both tables is GKZ; so the join should be established via GKZ. And then I need to select specific records from the joined records where G_ID = a certain value.
If someone could give me some help, I'd be very thankful.
Manu
You can do inner join as shown below.
Assumption : Hope your table names are like Tbl_Group_Communities and Katastralgemeinden.In other words same name as the class names.
from s in db.Tbl_Group_Communities
join sa in db.Katastralgemeinden on s.GKZ equals sa.GKZ
where s.G_ID == 1
select s
You can learn more about join here : Join Operators
I figured it out - here is my controller that works:
using System.Linq;
using System.Web.Http;
using System.Web.Http.OData.Query;
using IndireKat.Data.Contracts;
using IndireKat.Data.Contracts.Entities;
using IndireKat.Shared.Framework.Security;
namespace Presentation.Host.Controllers
{
public class KatastralgemeindenController : BaseODataController
{
private readonly IIdentityStorage identityStorage;
public KatastralgemeindenController(IUnitOfWork unitOfWork, IIdentityStorage identityStorage)
{
UnitOfWork = unitOfWork;
this.identityStorage = identityStorage;
}
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Katastralgemeinden> Get()
{
IIndireKatPrincipal indireKatPrincipal = identityStorage.GetPrincipal();
var comunityIds = UnitOfWork.GetAll<UserGroup>()
.Where(group => group.Id == indireKatPrincipal.GroupId)
.SelectMany(group => group.Tbl_Group_Communities).Select(comunity => comunity.GKZ).ToList();
IQueryable<Katastralgemeinden> result = null;
if (comunityIds.Any())
{
result = UnitOfWork.GetAll<Katastralgemeinden>().Where(company => comunityIds.Contains(company.GKZ));
}
return result;
}
}
}
Regards, Manu
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Return anonymous type from LINQ query?
I use this Method:
public static ... GetList()
{
Entity conn = new Entity();
var lst = (from PS in conn.PurchaserSpecials
select new
{
PS.PurchaserID,
PS.PurchaserSerial,
PS.Purchaser.Address,
PS.Purchaser.Fax,
PS.Purchaser.NumberEconomic
}).ToList();
return lst;
}
can not use var for output Type. Please help me...
what is output the method?
It's a list of the anonymous type you created.
If you want to return a type that you would know, create a new class that has the fields you are selecting (I am guessing types below):
public class Specials
{
public int PurchaserID { get; set; }
public int PurchaserSerial { get; set; }
public string Address { get; set; }
public int Fax { get; set; }
public int NumberEconomic { get; set; }
}
public static List<Specials> GetList()
{
Entity conn = new Entity();
var lst = (from PS in conn.PurchaserSpecials
select new Specials
{
PurchaserID = PS.PurchaserID,
PurchaserSerial = PS.PurchaserSerial,
Address = PS.Purchaser.Address,
Fax = PS.Purchaser.Fax,
NumberEconomic = PS.Purchaser.NumberEconomic
}).ToList();
return lst;
}
You LINQ query generates an anonymous type, and as such can't be used as a return type of the method, and the calling method will not know anything about the return type.
A way around this is to make use of the dynamic keyword and dynamic types.
The return type of you method can be like this
public dynamic GetList() {
return ...;
The output of the query is an anonymous type which you can not return back as a parameter. To be simple, better you create a class something like : 'PurchaseDetail' with all those properties and return the code like :
public static List<PurchaseDetail> GetList()
{
Entity conn = new Entity();
var lst = (from PS in conn.PurchaserSpecials
select new PurchaseDetail
{
PurchaserID= PS.PurchaserID,
PurchaserSerial=PS.PurchaserSerial,
Address=PS.Purchaser.Address,
Fax=PS.Purchaser.Fax,
NumberEconomic =PS.Purchaser.NumberEconomic
}).ToList();
return lst;
}
The Return Type would be the List of type Products
public static List<Products> GetList()
{
Entity conn = new Entity();
var lst = (from PS in conn.PurchaserSpecials
select new
{
PS.PurchaserID,
PS.PurchaserSerial,
PS.Purchaser.Address,
PS.Purchaser.Fax,
PS.Purchaser.NumberEconomic
}).ToList();
return lst;
}
public class Products
{
//Properties you are using in the query.
}
I have this class
public class MyClass
{
public int Id { get; set;}
public string Name { get; set; }
public IList<MyClass> Classes { get; set; }
}
I am using Fluent NHibernate, and I have this AutoMappingOverride:
public void Override(AutoMapping<MyClass> mapping)
{
mapping.HasManyToMany(m => m.Classes)
.ParentKeyColumn("ClassId")
.ChildKeyColumn("SecondClassId")
.Table("ClassesRelation")
.Cascade.SaveUpdate();
}
And I have this method on my Repository:
public IList<MyClass> ClassesThatContains(MyClass #class)
{
//allClasses.Where(tempClass => tempClass.Classes.Contains(#class))
string query = string.Format(#"SELECT Class.* FROM Class WHERE Class.Id IN (SELECT ClassesRelation.ClassId FROM ClassesRelation WHERE ClassesRelation.SecondClassId = {0})", #class.Id);
var criteria = NhSession.CreateSQLQuery(query).AddEntity(typeof(MyClass));
return criteria.List<MyClass>();
}
In the method ClassesThatContains I have the comment that is equivalent to SQL Statement, but NHibernate dont know Contains Linq method....So I used SQL Statement, but how can I convert this SQL Statement to Criteria like?
Obs: ClassRelation is not a Class.
Nhibernate has a linq provider in version 3 built in and as an addon in version 2.
You could just change the method like so::
from c in session.Query<MyClass>()
from cr in session.Query<ClassRelation>()
where c.FirstId == cr.Id
where cr.SecondId = #class.Id
select c.Id;
Or something more closer to your query you actually want.