LINQ join query with relational entities - c#

I am using ef-core 2.1, I have the following simplified entities where one Account maps to zero or more Attribute objects:
public class Account
{
public int Id { get; set; }
public int LongId { get; set; }
public List<Attribute> Attributes { get; set; }
}
public class Attribute
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public Account Account { get; set; }
}
I have an initial collection of strings that resemble an Attribute.Value for a given fixed Attribute.Name, I want to find a second associated Attribute object from the same parent Account and obtain its Attribute.Value.
I want to left join the ef entities against the initial string collection so I can easily infer:
Whether no corresponding Account exists or an Account exists without the related Attribute objects (both equate to the same use case).
If an Account exists and it contains all the required Attribute objects, I want to obtain the value of the secondary Attribute.
Without LINQ and ef, I run the following SQL query which ignores the parent Account and produces the result set I want:
CREATE TABLE #Temp
(
id nvarchar(20) not null
);
INSERT INTO #Temp (id) VALUES ('cejawq'), ('issokq'), ('cqlpjq'), ('mbgzvi'), ('wqwlff'), ('iedifh');
SELECT t.[Id], attr2.[Value]
FROM #Temp t
LEFT OUTER JOIN [dbo].[Attributes] attr1
ON t.[Id]=attr1.[Value]
AND attr1.[Name]='uid'
LEFT OUTER JOIN [dbo].[Attributes] attr2
ON attr1.[AccountId]=attr2.[AccountId]
AND attr2.[Name]='objType';
I get the following result set:
id|objType
-----------
cejawq|ext
issokq|ext
cqlpjq|int
mbgzvi|int
wqwlff|ext
iedifh|null
I am struggling with mapping this to efficient LINQ such that the SQL generated produces the result set remotely and ships back data that I can project to an equivalent anonymous type. Do I need to care about the parent objects in the LINQ case? I don't have an index on the Attribute.Value column.
The Attributes table contains the following data:
Id|Name |Value |AccountId
1 |uid |cejawq|1
2 |objType|ext |1
3 |uid |issokq|2
4 |objType|ext |2
5 |uid |cqlpjq|3
6 |objType|int |3
7 |uid |mbgzvi|4
8 |objType|int |4
9 |uid |wqwlff|5
10|objType|ext |5

Since the EF Core does not support joins with in memory sequences (yet), you can split the query in two parts - one which takes the data server side ([Attributes to [Attributes join) using in memory collection as filter (SQL IN through LINQ Contains method), and second which performs left join in memory with the result of the db query:
DbContext db = ...;
var uids = new [] { "cejawq", "issokq", "cqlpjq", "mbgzvi", "wqwlff", "iedifh" };
var dbQuery =
from attr1 in db.Set<Attribute>()
where attr1.Name == "uid" && uids.Contains(attr1.Value)
join attr2 in db.Set<Attribute>()
on new { AccountId = attr1.Account.Id, Name = "objType" }
equals new { AccountId = attr2.Account.Id, attr2.Name }
into attr2Group from attr2 in attr2Group.DefaultIfEmpty() // left outer join
select new { uid = attr1.Value, objType = attr2.Value };
var query =
from uid in uids
join dbResult in dbQuery on uid equals dbResult.uid
into dbResultGroup from dbResult in dbResultGroup.DefaultIfEmpty() // left outer join
select new { uid, dbResult?.objType };
var result = query.ToList();
It translates to a single db query like this:
SELECT [attr1].[Value] AS [uid], [attr2].[Value] AS [objType]
FROM [Attributes] AS [attr1]
LEFT JOIN [Attributes] AS [attr2] ON ([attr1].[AccountId] = [attr2].[AccountId]) AND (N'objType' = [attr2].[Name])
WHERE ([attr1].[Name] = N'uid') AND [attr1].[Value] IN (N'cejawq', N'issokq', N'cqlpjq', N'mbgzvi', N'wqwlff', N'iedifh')

Related

Linq output multiply results

I have a simple SQL query like this :
SELECT table1.[idGK] , table2.FullName , table2.LgotName
from table2
join table1 on table2.C_LGT = table1.[idGK]
where table1.mcod = 41003
And i have right output of this :
idGK | FullName| LgotName
------------------------
1 |One |Ball
2 |Two |Wog
3 |Three |Aks
5 |Four |Mqi
7 |Five |Thel
9 |Six |Imx
But when i make LINQ query of this :
IEnumerable<FinalDoc> fidn = from post in repository.table1
join thir in repository.table2 on post.idGK equals thir.C_LGT
where post.mcod.Trim().Contains("41003")
orderby post.idGK
select new FinalDoc
{
mcod = post.mcod,
FullName= thir.FullName,
idGK = post.idGK
};
i have this result:
FullName | LgotName
------------------------
Five |Thel
Five |Thel
Five |Thel
Five |Thel
Five |Thel
Five |Thel
I try to change table1 and table 2 to make right join but i have same result.
What linq query i need to do to make same result that in SQl ?
P.S EF , Linq , Asp.net , Web Forms
The LINQ equivalent of your SQL query is:
from thir in repository.table2
join post in repository.table1 on thir.C_LGT equals post.[idGK]
where post.mcod == 41003
So assuming your SQL is correct (i.e. table1.mcod is a numeric type and not a string) then it should work.
EDIT:
You could try this to see what SQL EF is generating from your LINQ. It may help diagnose the problem.
var query = from post in repository.table1
join thir in repository.table2 on post.idGK equals thir.C_LGT
where post.mcod.Trim().Contains("41003")
var sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
I solve this, but answer is very simple :
My problem was that i didn't add to my class definition primary key row , because of that i have this result .
In my table i didn't have primary key , and i add id row, because EF needs primary key , and if you didn't add them , EF will do this work for you(and take first line ) . In my case i add idG row:
[Key]
public int idG {get;set;}
public int idGK { get; set; }
public string mcod { get; set; }

Creating a gridview or listview from different sql tables in asp.net

What i aim to achive with this is to have a grid or listview where the colums get data from different sql tabels.
Here are the different tabels
TradeItemIdentification
id GTIN
TradeItemDescriptionInformation
id brandName tradeItemFunctionalName
ClassificationCategory
id additionalClassificationCategoryCode
I don know if it is possible to show a grid with all of these attributes except the id's. What is the smartest way to do achive this, if possible?
They are all linked to this table. With these "many ot many" tabels.
TradeItemBasic
id
TradeItemIdentificationOnTradeItem
tradeItemId identificationId
TradeItemDescriptionInformationsOnTradeItem
tradeItemId descriptionId
And the same for the last table. So they have a connection.
Here is some joins of the tabels that i have made so far.
public List<string> GetAllProductsInfo()
{
var gtins = (from gtinss in _db.TradeItemIdentificationOnTradeItems
join gtin in _db.TradeItemIdentifications on gtinss.tradeItemIdentificationId equals gtin.id
select gtin.gtin);
var brandNames = (from descriptions in _db.TradeItemDescriptionInformationsOnTradeItems
join description in _db.TradeItemDescriptionInformations on descriptions.tradeItemDescriptionInformationId equals description.id
select description.brandName);
var article = (from articleNumbers in _db.ClassificationCategoryOnGDSNTradeItemClassifications
join articleNumber in _db.ClassificationCategories on articleNumbers.gDSNTradeItemClassificationId equals articleNumber.id
select articleNumber.additionalClassificationCategoryCode);
var allInfo = gtins.Concat(brandNames).Concat(article).ToList();
return allInfo;
}
This is what ive got so far, i am no able to get all of the items that i wanted. But when i do it like this the results is not divided in to sections, but instead it is all just gets put out as one long list.
Just join the different tables and select the properties you want to display.
There are two different use cases. If you want to display all lines where some data is missing then use left joins. Else use inner joins (just replace the left join). That reduce your resultset.
Here is an example how to join your tables:
SELECT tib.id, tii.GTIN, tidi.brandName, tidi.tradeItemFunctionalName
FROM TradeItemBasic AS tib
LEFT JOIN TradeItemIdentificationOnTradeItem AS tii2ti ON tib.id = tii2ti.tradeItemId
LEFT JOIN TradeItemIdentification AS tii ON tii.id = tii2ti.identificationId
LEFT JOIN TradeItemDescriptionInformationsOnTradeItem AS tidi2ti ON tib.id = tidi2ti.tradeItemId
LEFT JOIN TradeItemDescriptionInformation AS tidi ON tidi.id = tidi2ti.descriptionId
For the ClassificationCategory you don't post a mapping table, so i remove the selected property.
Hope that helps.
I solved it.
I Made a new C# class. Called ProductInfo that looks like this
public class ProductInfo
{
public ProductInfo()
{
}
public List<string> Gtin { get; set; }
public List<string> BrandName { get; set; }
public List<string> ArticleNr { get; set; }
}
this class takes three types of lists.
And here is the method for getting the specific information from the different classes.
public ProductInfo GetAllProductsInfo()
{
var gtins = (from gtinss in _db.TradeItemIdentificationOnTradeItems
join gtin in _db.TradeItemIdentifications on gtinss.tradeItemIdentificationId equals gtin.id
select gtin.gtin).ToList();
var brandNames = (from descriptions in _db.TradeItemDescriptionInformationsOnTradeItems
join description in _db.TradeItemDescriptionInformations on descriptions.tradeItemDescriptionInformationId equals description.id
select description.brandName).ToList();
var article = (from articleNumbers in _db.ClassificationCategoryOnGDSNTradeItemClassifications
join articleNumber in _db.ClassificationCategories on articleNumbers.gDSNTradeItemClassificationId equals articleNumber.id
select articleNumber.additionalClassificationCategoryCode).ToList();
ProductInfo pr = new ProductInfo { Gtin = gtins, BrandName = brandNames, ArticleNr = article };
return pr;
}
with this method i get three lists from the tabels that i wanted. And after i made the joins i just create a new ProductInfo and add the results to the right lists in the new class.

How can I LINQ outer join two collections?

I have the following query:
var rowData = companies.Select(
t => new CompanyDetail
{
CompanyID = t.Title,
Subjects = subjects.Count ( u => t.RowKey == "0000" + u.PartitionKey)
}).ToList();
public class CompanyDetail
{
[DisplayName("Company")]
public string CompanyID { get; set; }
[DisplayName("Subjects")]
public Int32 Subjects { get; set; }
}
The query output looks like this:
CompanyID Subjects
1 2
2 4
3 1
However I have a problem if the company has no subjects. I would like to see:
CompanyID Subjects
1 2
2 4
3 1
4 0
Is there a way that I can convert this LINQ query into an outer join so it always reports every company and then gives a count of how many subjects are connected through the row and partitionkey connector?
It's hard to answer this without knowing what LINQ provider you are using - your query would work as you expect in LINQ to Objects, for example.
Perhaps it would be worth a try to use an explicit GroupJoin to convince the provider to give you empty groups:
var rowData = from company in companies
join subject in subjects
on company.RowKey equals "0000" + subject.PartitionKey
into companySubjectGroup
select new
{
CompanyID = company.Title,
Subjects = companySubjectGroup.Count()
};

Lambda expressions to obtain data through navigation properties, LINQToEntities

I'm having a problem getting some data since I'm just starting to use LINQToEntities, Entity Framework and Lambda Expressions.
Let me explain my case:
I have a database with 4 tables as shown here:
when I generate the model from database in Visual Studio (2010) the result is this:
I searched for some info and turns out that since the table t_user_role only has the ids from its two parent tables, it gets abstracted in the model, and you have to use the navigation properties instead.
I had some problem getting the role info for a user on a given system (as the next function does)
public t_role GetRoleForUser(string userId, string sysId)
{
entities = new secfinEntities(); //context from the model
t_role userRole = entities.t_role.Where(r => r.t_user.Any(u => u.uid == userId) & r.sys_id == sysId).First();
return userRole;
}
Now I have to implement a simple search function that will look users that contain the provided string and return the user's id and name (uid, user_name) and their role's info (role_id, role_name) on a given system (the system info i have beforehand), so basically I wanna turn this next SQL Query into Lambda Expressions (keeping in mind that in the model the table t_user_role has been abstracted)
SELECT U.uid, U.user_name, R.role_id, R.role_name
FROM t_user U
INNER JOIN t_user_role UR ON U.uid = UR.uid
INNER JOIN t_role R ON UR.role_id = R.role_id
WHERE R.sys_id = #p0 -- first parameter
AND U.user_name LIKE '%' + #p1 + '%' -- second parameter
Also, I would like to store the results of that in a List of a type I have defined as follows:
public class UserRole
{
public string UserId { get; set; }
public string UserName { get; set; }
public string RoleId { get; set; }
public string RoleName { get; set; }
public UserRole(string uid, string uname, string rid, string rname)
{
UserId = uid;
UserName = uname;
RoleId = rid;
RoleName = rname;
}
}
So after explaining what I have done and what I'm trying to do first question is: how can that be done?, second: can the same be accomplished through the verbose form instead of Lambda expressions? if yes, how?
Thanks a lot in advance for your time.
This T-SQL:
SELECT U.uid, U.user_name, R.role_id, R.role_name
FROM t_user U
INNER JOIN t_user_role UR ON U.uid = UR.uid
INNER JOIN t_role R ON UR.role_id = R.role_id
WHERE R.sys_id = #p0 -- first parameter
AND U.user_name LIKE '%' + #p1 + '%' -- second parameter
given your model translates to this verbose syntax (including the requirement to use your new model):
var results = (from u in entities.t_user
from r in u.t_role
where r.sys_id == sysIdVariable && u.user_name.Contains(userNameVariable)
select new UserRole(u.uid, u.user_name, r.role_id, r.role_name))
I know you didn't ask for it, but a lambda version might look like:
var results = entities.t_user.Join(entities.t_role,
x => x.t_role_id,
x => x.role_id,
(u, r) => new UserRole(u.uid,
u.user_name,
r.role_id,
r.role_name))

Correct use of multimapping in Dapper

I'm trying to use the multimapping feature of Dapper to return a list of ProductItems and associated Customers.
[Table("Product")]
public class ProductItem
{
public decimal ProductID { get; set; }
public string ProductName { get; set; }
public string AccountOpened { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public decimal CustomerId { get; set; }
public string CustomerName { get; set; }
}
My Dapper code:
var sql = #"select * from Product p
inner join Customer c on p.CustomerId = c.CustomerId
order by p.ProductName";
var data = con.Query<ProductItem, Customer, ProductItem>(
sql,
(productItem, customer) => {
productItem.Customer = customer;
return productItem;
},
splitOn: "CustomerId,CustomerName"
);
This works fine, but I seem to have to add the complete column list to the "splitOn" parameter to return all the customers' properties. If I don't add "CustomerName", it returns null. Am I misunderstanding the core functionality of the multimapping feature? I don't want to have to add a complete list of column names each time.
I just ran a test that works fine:
var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql,
(p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
item.Customer.CustomerId.IsEqualTo(1);
The splitOn param needs to be specified as the split point, it defaults to Id. If there are multiple split points, you will need to add them in a comma delimited list.
Say your recordset looks like this:
ProductID | ProductName | AccountOpened | CustomerId | CustomerName
--------------------------------------- -------------------------
Dapper needs to know how to split the columns in this order into 2 objects. A cursory look shows that the Customer starts at the column CustomerId, hence splitOn: CustomerId.
There is a big caveat here, if the column ordering in the underlying table is flipped for some reason:
ProductID | ProductName | AccountOpened | CustomerName | CustomerId
--------------------------------------- -------------------------
splitOn: CustomerId will result in a null customer name.
If you specify CustomerId,CustomerName as split points, dapper assumes you are trying to split up the result set into 3 objects. First starts at the beginning, second starts at CustomerId, third at CustomerName.
Our tables are named similarly to yours, where something like "CustomerID" might be returned twice using a 'select *' operation. Therefore, Dapper is doing its job but just splitting too early (possibly), because the columns would be:
(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.
This makes the splitOn: parameter not so useful, especially when you're not sure what order the columns are returned in. Of course you could manually specify columns... but it's 2017 and we just rarely do that anymore for basic object gets.
What we do, and it's worked great for thousands of queries for many many years, is simply use an alias for Id, and never specify splitOn (using Dapper's default 'Id').
select
p.*,
c.CustomerID AS Id,
c.*
...voila! Dapper will only split on Id by default, and that Id occurs before all the Customer columns. Of course it will add an extra column to your return resultset, but that is extremely minimal overhead for the added utility of knowing exactly which columns belong to what object. And you can easily expand this. Need address and country information?
select
p.*,
c.CustomerID AS Id,
c.*,
address.AddressID AS Id,
address.*,
country.CountryID AS Id,
country.*
Best of all, you're clearly showing in a minimal amount of SQL which columns are associated with which object. Dapper does the rest.
Assuming the following structure where '|' is the point of splitting and Ts are the entities to which the mapping should be applied.
TFirst TSecond TThird TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------
Following is the Dapper query that you will have to write.
Query<TFirst, TSecond, TThird, TFourth, TResut> (
sql : query,
map: Func<TFirst, TSecond, TThird, TFourth, TResut> func,
parma: optional,
splitOn: "col_3, col_n, col_A, col_9")
So we want for TFirst to map to col_1 col_2 col_3, for TSecond to col_n col_m ...
The splitOn expression translates to:
Start mapping of all columns into TFirst till you find a column named or aliased as 'col_3', and also include 'col_3' into the mapping result.
Then start mapping into TSecond all columns starting from 'col_n' and continue mapping till new separator is found, which in this case is 'col_A', and mark the start of TThird mapping and so on.
The columns of the SQL query and the props of the mapping object are in a 1:1 relation (meaning that they should be named the same). If the column names resulting from the SQL query are different, you can alias them using the 'AS [Some_Alias_Name]' expression.
If you need to map a large entity write each field must be a hard task.
I tried #BlackjacketMack answer, but one of my tables has an Id Column other ones not (I know it's a DB design problem, but ...) then this insert an extra split on dapper, that's why
select
p.*,
c.CustomerID AS Id,
c.*,
address.AddressID AS Id,
address.*,
country.CountryID AS Id,
country.*
Doesn't work for me. Then I ended with a little change to this, just insert an split point with a name that doesn't match with any field on tables, In may case changed as Id by as _SplitPoint_, the final sql script looks like this:
select
p.*,
c.CustomerID AS _SplitPoint_,
c.*,
address.AddressID AS _SplitPoint_,
address.*,
country.CountryID AS _SplitPoint_,
country.*
Then in dapper add just one splitOn as this
cmd =
"SELECT Materials.*, " +
" Product.ItemtId as _SplitPoint_," +
" Product.*, " +
" MeasureUnit.IntIdUM as _SplitPoint_, " +
" MeasureUnit.* " +
"FROM Materials INNER JOIN " +
" Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
" MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
cmd,
new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
(objects) =>
{
Materials mat = (Materials)objects[0];
mat.Product = (Product)objects[1];
mat.MeasureUnit = (MeasureUnit)objects[2];
return mat;
},
splitOn: "_SplitPoint_"
)).ToList();
There is one more caveat. If CustomerId field is null (typically in queries with left join) Dapper creates ProductItem with Customer = null. In the example above:
var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
Debug.Assert(item.Customer == null);
And even one more caveat/trap. If you don't map the field specified in splitOn and that field contains null Dapper creates and fills the related object (Customer in this case). To demonstrate use this class with previous sql:
public class Customer
{
//public decimal CustomerId { get; set; }
public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");
I do this generically in my repo, works good for my use case. I thought I'd share. Maybe someone will extend this further.
Some drawbacks are:
This assumes your foreign key properties are the name of your child object + "Id", e.g. UnitId.
I have it only mapping 1 child object to the parent.
The code:
public IEnumerable<TParent> GetParentChild<TParent, TChild>()
{
var sql = string.Format(#"select * from {0} p
inner join {1} c on p.{1}Id = c.Id",
typeof(TParent).Name, typeof(TChild).Name);
Debug.WriteLine(sql);
var data = _con.Query<TParent, TChild, TParent>(
sql,
(p, c) =>
{
p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c);
return p;
},
splitOn: typeof(TChild).Name + "Id");
return data;
}
I would like to note a very important aspect: the property name within the Entity must match the select statement. Another aspect of splitOn is how it looks for Id by default, so you don't have to specify it unless your naming is something like CustomerId, instead of Id. Let's look at these 2 approaches:
Approach 1
Entity Customer : Id Name
Your query should be something like:
SELECT c.Id as nameof{Customer.Id}, c.Foo As nameof{Customer.Name}.
Then your mapping understands the relationship between the Entity and the table.
Approach 2
Entity Customer: CustomerId, FancyName
Select c.Id as nameof{Customer.CustomerId}, c.WeirdAssName As nameof{Customer.FancyName}
and at the end of the mapping, you have to specify that the Id is the CustomerId by using the SplitOn.
I had an issue where I was not getting my values even though the mapping was correct technically because of a mismatch with the SQL statement.

Categories