I have a situation where I need data from multiple database tables.
Table 1 - has list of columns which needs to be displayed on front end html, angular kendo grid - which is configurable from separate Admin configuration.
Table 2 (joining of some other tables)- has the data which needs to be displayed on the angular front end.
My linq here which I am using currently is as below.
Query 1: to get list of columns to be displayed on Grid
var columns = from cols in _context.columns
select cols.colNames;
Query 2: Get the actual data for list
var data = from cust in _context.customer
join details in _context.custDetails on cust.id equals details.custid
join o in _context.orders on cust.id equals o.custid
where cust.id == XXXX
select new Customer
{
Id = cust.Id,
Name = cust.Name,
Address = details.Address,
City = details.City,
State = details.State,
OrderDate = o.OrderDate,
Amount = o.Amount
//15 other properties similarly
};
returns IQueryable type to Kendo DataSourceRequest
Currently, From my ui I have been make two api calls one for columns and one for getting the actual data, and show/hide the columns which are configured in the columns table.
But the problem is if anyone looks at the api calls on the network or on browser tools they could see the data being returned for the columns that are to be hidden which is a security problem.
I am looking for a single query for my api which returns the data using second query which should be smart enough to send the data only for configured columns (there could be 30 different columns) and set the other properties to null or doesn't select them at all. there are some properties which needs to be returned always as they are being used for some other purpose.
I searched many resources on how could I generate dynamic linq select using the configured columns.
Please some one help me in resolving this problem
you can do something like this. Assuming you columns tables a Boolean column Display and when it is true Column will be displayed and when it is false it wont be displayed.
var columns = (from cols in _context.columns
select cols).ToList(); // note I am getting everything not just column names here...
var data = from cust in _context.customer
join details in _context.custDetails on cust.id equals details.custid
join o in _context.orders on cust.id equals o.custid
where cust.id == XXXX
select new Customer
{
Id = cust.Id,
Name = cust.Name,
Address = details.Address,
City = details.City,
State = details.State,
OrderDate = o.OrderDate,
Amount = o.Amount
//15 other properties similarly
}.ToList();
var fileterdData = from d in data
select new Customer
{
Id = DisplayColumn("ID",columns)? cust.Id:null,
Name = DisplayColumn("Name",columns)? cust.Name:null,
Address = DisplayColumn("Address",columns)? details.Address:null,
// do similarly for all other columns
}.AsQueryable(); // returns IQueryable<Customer>
private bool DisplayColumnn(string columnName,List<Columns> cols)
{
return cols.Where(x=>x.ColumnName==columnName).First().Display();
}
So now you will have this code as part of one web API call which is going to do two SQL calls one to get columns and other to get data then you will use Linq To Entity filter columns which you dont want ( or want them to null). return this filtered data back to UI.
Related
Lets suppose I have a table called Transactions
Transactions has the following columns
OrderId,
OrderType (Can be 0 = Sale or 1 = Purchase) <--- this can increase
Amount
Now I want to get the relevant data based on the OrderType
if OrderType = 0 then join from Sale Table else Join from Purchase Table.
Currently what I am doing is that doing three calls to the database to get the some other values from the other tables(which works but highly inefficient in long run as 3 Calls are bad performance wise).
My solution is using left join with SQL
SELECT ap.*,
coalesce(s.orderNo,p.orderNo) as orderNo
FROM apptransactions AS ap
LEFT JOIN sales AS s ON (ap.orderType = 0 and ap.orderId = s.id)
LEFT JOIN purchases AS p ON (ap.orderType = 1 and ap.orderId = p.id);
how can this query be converted to EF Core?
Why is your query not like this?
var results = context.Transactions.Select(t =>
new
{
/* t.column list, there's no t.* in LINQ */,
OrderNo = t.OrderType == 0 ? t.Sale.OrderNo : t.Purchase.OrderNo
});
Let EF generate any underlying joins it needs to, concern yourself with getting the results you want.
This also alludes to what #caius mentions. Your model is likely not high level enough or incorrectly mapped.
I am trying to write a query to collect the Job number, start date, customer name, Model, and completion date for jobs at my work. To get this info, I look at 3 different tables, using joins to put them together. Here are the three tables:
STAGE - (stages each job goes through during production)
ORDER - (this is where I get the customer's name)
JOBS (start date, completion date, job number, model)
So most of the info is from the JOBS table. But I'm joining onto the ORDER table by Job Number (JobNum) to obtain the customer's name. Here's what the query looks like. I created it in SQL before I tried to translate it into one of my ViewModels:
var CompletedTrucksQuery =
(from FA in context.JOBS
join ORD in context.ORDER on FA.ORGANIZATION_ID equals ORD.ORGANIZATION_ID
where FA.ORDER_NUMBER == ORD.ORDER_NUMBER
join StageF in context.STAGE on FA.JobNum equals StageF.JobNum
where StageF.StageID == 325
join TruckComp in context.STAGE on FA.JobNum equals TruckComp.JobNum
where TruckComp.StageID == 327
join INSP in context.STAGE on FA.JobNum equals INSP.JobNum
where INSP.StageID == 487
orderby StageF.CompDate descending, FA.JobNum ascending
select new {FA.JobNum, FA.StartDate, ORD.CUSTOMER_NAME, FA.MODEL_NAME, StageF.CompDate});
At this point, I'm wanting to select the
Job number (from JOBS),
the start date, (From JOBS),
the Customer's name (from ORDER),
the Model of product (from JOBS),
and the date it was completed in StageF (from STAGE)
as you can see in my select statement. I DO have an object to hold each of these called CompJob, and have tried to do a 'group by' and select a new CompJob and set the properties, but I can't seem to group it right and get 'access' to all of the properties I want to set. Here's an excerpt of what I'm talking about:
group new {FA.JobNum, O = ORD} by StageF into grp
select new CompletedTruck
{
JobNum = grp.Key.JobNum,
StartDate = grp. //???
}
As you can tell I stopped, because for some reason I couldn't 'find' the start date. I know it's something to do with my grouping. I'm very new to linq and databases, in general.
MY QUESTION: What's the best way I can select these columns of interest into my
ObservableCollection<CompJob> CompJobList;
so that I may use it in my scrollviewer in a view?
Thanks to everyone for helping me out. I solved the issue and this is what I did. I followed Gert Arnold's suggestion of selecting a new CompJob object. This would set each row of the query to a new object:
select new CompJob {JobNum = FA.JobNum, StartDate = FA.StartDate, Customer = ORD.CUSTOMER_NAME,.....}
and then said
).ToList();
At the end of the query. Then I set my ObservableCollection to the list returned by my query:
CompJobList = CompletedTrucksQuery ;
Thanks again for helping me out; I know I'm new to all of this!
I am binding ComboBox using LINQ Join query. Below is my code:
var list = (from a in context.tbl_Products
join c in context.tbl_CurrentStock on a.ProductID equals c.ProductID
where c.Qty > 0
select new
{
ProductID = a.ProductID,
ProductName = a.ProductName
}).ToList();
cmbProduct.DataSource = list;
cmbProduct.ValueMember = "ProductID";
cmbProduct.DisplayMember = "ProductName";
cmbProduct.SelectedIndex = -1;
Table Details:
tbl_Products : ProductID, Product Name
Data in table
1,ABC
2,BCA
3,CDA
tbl_CurrentStock: StockID,ProductID,Qty
Data in table:
1,1,5
2,2,10
3,3,50
I am using cmbProduct.SelectedValue like below:
int ProductID = Convert.ToInt32(cmbProduct.SelectedValue);
var Product = context.tbl_Products.Single(o => o.ProductID == ProductID);
Until here it is fine. In combobox I have selected "ABC", but I am getting cmbProduct.selectedvalue value as 2 instead of 1. Same way if I select 2nd product getting value as 3 instead of 2, it is not giving selected value, instead it is giving first value in the list. What could be the problem? It's silly and eating my head. This is working fine, when I don't use JOIN Query (if I bind data from only one table)
Thanks in Advance
Problem resolved after changing sorted property of combobox to false. Combobox is sorting the productnames but not product IDs. This was causing issue. –
I'm developing a WCF RESTful web service with Entity Framework Code First.
I have a table Users with a lot of columns. I do this to get an specific user:
context.Configuration.ProxyCreationEnabled = false;
var users = from u in context.Users
where u.UserId == userId
select u;
On this table, there is a password column, and I don't want return this column.
How can I exclude password column from that select?
Its sad to say but NO
You do not have option to directly exclude any particular column. You may go with lazy loading of columns.
The easiest and non-liking method would be to include columns which you want.
Specify each column that you do want in your select statement:
var users = from u in context.Users
where u.UserId == userId
select u.UserId, u.Watever, etc... ;
another way like this,
var users = from u in context.Users
where u.UserId == userId
select new
{
col1 = u.UserId,
col2 = u.Watever
}.ToList();
You can create more than one LINQ object per table. I'd create one with the field you need and one without. It makes CRUD operations more difficult though.
Yes, you can run LINQ with certain columns excluded:
Store list of all columns in an array of string. E.g.
var arrayOfColNames = dbContext.Model.FindEntityType(typeof(TableName))
.GetProperties().Select(x => x.Relational().ColumnName).ToArray() )
Remove the unwanted columns from the above array of string. E.g.
arrayOfColNames = arrayOfColNames .Where(w => !w.Equals("password")).ToArray();
Run your LINQ select using the above filtered array of strings : https://stackoverflow.com/a/45205267/19386398
I have a process that extracts customer info from multiple databases (MySql) based on a timestamp. I store this data into a DataTable. The data table represents updates to existing customer info as well as new customer info.
I want to delete any dupes in the destination database (SqlServer) based on one constant value, CompanyID, and the CustomerID. So, I thought a join would give me the RecordIDs of the dupes in the destination DB, pass the List<int> (or some collection mechanism) to the DELETE method.
What I have:
using (var context = new DataContext(SqlConnection))
{
var tblSource = context.GetTable<tblCustomerInfo>();
var dupeIDs = from currCust in tblSource
join newCust in myTable.AsEnumerable() on currCust.CompanyID equals newCust.Field<string>("CompanyID")
where currCust.CustomerID.Equals(newCust.Field<int>("CustomerID")
select currCust.RecordID;
}
This obviously does not work. I will update with the exact error messages in a bit, but this doesn't compile.
First, is my join syntax even correct for what I am wanting to achieve?
Second, how can I write this Linq to join between a DataTable and the destination SqlServer database?
Afterthought - is it possible to, once I have a collection of dupe RecordIDs, use Linq to DELETE records from the destination database?
Edit
To clarify the process, I have incoming data tables like so and contained in a DataSet:
Table1
CompanyID CustomerID Field1 Field2 ....
1 5 ... ...
1 15 ... ...
Table2
CompanyID CustomerID Field1 Field2 ....
10 125 ... ...
10 145 ... ...
Which will all go into a single database:
Destination DB
CompanyID CustomerID Field1 Field2 ....
1 5 ... ...
1 15 ... ...
1 27 ... ...
5 15 ... ...
10 125 ... ...
10 145 ... ...
11 100 ... ...
So, in this case I would delete from the destination table the items that match from tables 1 & 2. The destination database will be growing constantly so creating a List of CustomerID does not seem feasible. However, I expect daily imports of new and updated customer info to be relatively small (in the hundreds, maybe near 1000 records).
If I cannot write a single join what other method for completing this process would be appropriate? I am trying to figure something out since it looks like I cannot actually mix Linq-to-Sql and Linq-to-Objects.
Is it possible to somehow map my data table to the entity datamap, tbl_CustomerInfo, filling an otherwise immutable var, then perform the join?
Update
Here is what I have accomplished at this point and I get the results I expect from dupes:
using (DataContext context = new DataContext(SqlConnection)
{
var custInfo = context.GetTable<tbl_CustomerInfo>();
string compID = ImportCust.Rows[0]["CompanyID"].ToString();
var imports = from cust in ImportCust.AsEnumerable()
select cust.Field<int>("CustomerID");
var dupes = from cust in custInfo
join import in imports
on cust.CustomerID equals import
where cust.CompanyID == compID
select cust;
custInfo.DeleteOnSubmit(/* what goes here */);
context.SubmitChanges();
}
My question now is, what goes into the DeleteOnSubmit(...)? I feel like I have gotten so close only to be foiled by this.
I usually tackle all of this in a stored proc for efficiency.
Add an identity field to your destination table to uniquely identify the records, then use a query like this:
DELETE d
FROM DestinationTable d JOIN (
Select CompanyID, CustomerID, Min(UniqueID) AS FirstRecID
FROM DestinationTable
GROUP BY CompanyID, CustomerID) u on u.CompanyID=d.CompanyID AND u.CustomerID=d.CustomerID
WHERE d.UniqueID <> u.FirstRecID
Alternatively, you could create two lists of List<int>, containing id's from your two sources, then use the Intersect LINQ operator to find the common items.
List<int> a = new List<int>{1,2,3,4,5,6,8, 10};
List<int> b = new List<int>{1,2,99,5,6,8, 10};
var c= a.Intersect(b); //returns the items common to both lists
Here is what I have that works:
using (DataContext context = new DataContext(SqlConnection)
{
var custInfo = context.GetTable<tbl_CustomerInfo>();
string compID = ImportCust.Rows[0]["CompanyID"].ToString();
var imports = from cust in ImportCust.AsEnumerable()
select cust.Field<int>("CustomerID");
var dupes = from import in imports
join cust in custInfo
on import equals cust.CustomerID
where cust.CompanyID== pivnum
select cust;
var records = dupes.GetEnumerator();
while (records.MoveNext())
{ custInfo.DeleteOnSubmit(records.Current); }
context.SubmitChanges();
}
If there is a more efficient method, I'm interested in options.