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.
Related
I have the following function that I am trying to run that joins a product table and a productcategory table and return the results. The debugger, when exception is thrown, displays the query string and I have tried running the query string in my test mysql database and it works.
The CategorizedProduct is a model. I have checked and made sure that all the fields in the select list match the fields in the model.
I cannot find where the error is.
public static List<CategorizedProduct> GetAllCategorizedProducts(string category)
{
using (var conn = Sql.GetConnection())
{
string queryString = "select ProductID,ProductName,ProductType,ProductCategory,CategoryName,Unit,PricePerUnit,IsAvailable,NumberOfUnits,RemainingUnits,WeightPerUnit" +
" from product inner join productcategory where product.ProductCategory = productcategory.CategoryID and CategoryName = \'" + category + "\' order by ProductType;";
return conn.Query<CategorizedProduct>(queryString).ToList();
}
}
Try this version instead:
select ProductID, ProductName, ProductType, ProductCategory,
CategoryName, Unit, PricePerUnit, IsAvailable, NumberOfUnits,
RemainingUnits, WeightPerUnit
from product inner join
productcategory
on product.ProductCategory = productcategory.CategoryID
where CategoryName = \'" + category + "\'
order by ProductType;
I do think that the on is optional in MySQL after join, but this might fix your problem. The on is required by any other database (and I think if you have certain ANSI settings on in MySQL).
Try to do a single database call to get an entity, as well as the count of related child entities.
I know I can retrieve the count using
var count = Context.MyChildEntitySet.Where(....).Count();
or even MyEntity.ListNavigationProperty.Count()
But That means getting the entity first, followed by another call in order to get the count or use an Include which would retrieve the whole list of related entities.
I am wondering is it possible to add a "Computed" column in SQL Server to return the Count of related rows in another table?
If not how do I ask EF to retrieve the related count for each entity in once call?
I am thinking of possibly using Join with GroupBy, but this seems an Ugly solution/hack.
public class MyEntity
{
public uint NumberOfVotes{ get; private set; }
}
which ideally woudl generate SQL Similar to:
SELECT
*,
(SELECT Count(*) FROM ChildTable c WHERE c.ParentId = p.Id) NumberOfVotes
FROM ParentTable p
UPDATED
You can always drop down to using actual SQL in the following way...
string query = "SELECT *,
(SELECT Count(*) FROM ChildTable c
WHERE c.ParentId = p.Id) as NumberOfVotes
FROM ParentTable p";
RowShape[] rows = ctx.Database.SqlQuery<RowShape>(query, new object[] { }).ToArray();
I realize this is not ideal because then you are taking a dependency on the SQL dialect of your target database. If you moved form SQL Server to something else then you would need to check and maybe modify your string.
The RowShape class is defined with the properties that match the ParentTable along with an extra property called NumberOfVotes that has the calculated count.
Still, a possible workaround.
I have a DB (in SQL Server 2008 SP3) and need all Schema names, Table names and Column Names in related hierarchy in C# Code, I have the SQLElement Class as Following:
public class SQLElement
{
public string SchemaName { get; set; }
public string TableName { get; set; }
public string ColumnName { get; set; }
}
And have a List Like:
List<SQLElement> SQLElementCollection = new List<SQLElement>();
So how can I read Names from DB and add them to this List (SQLElementCollection)?
for example assume we create a table like this:
Create Table [General].[City] (
[Id] BIGINT NOT NULL IDENTITY(1, 1),
[Title] NVARCHAR(30) NOT NULL DEFAULT (N''),
[Province_Id] BIGINT NOT NULL
)
and I need the list like:
[0]={SchemaName="General", TableName="City", ColumnName="Id"}
[1]={SchemaName="General", TableName="City", ColumnName="Title"}
[2]={SchemaName="General", TableName="City", ColumnName="Province_Id"}
Does any one have any idea about this?
Edit:
In next step how we can get the type of each column or related properties?
My suggestion is to include another member DataType in SQLElement if you have change permission or create another class with a property name DataTypeand then inherit from SQLElement and then save data type name into it for later use and use below query for all information, thanks
SELECT t.name AS TableName,
SCHEMA_NAME(t.schema_id) AS SchemaName,
c.name AS ColumnName,
tp.name as DataType
FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
INNER JOIN sys.types tp ON c.system_type_id = tp.system_type_id
ORDER BY TableName;
Connect to your database and execute the following statement:
select *
from INFORMATION_SCHEMA.COLUMNS
order by TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION;
Check the results and then peek and choose what you need.
This query will give you all column names and schema name
SELECT t.name AS tblName,
SCHEMA_NAME(schema_id) AS [schemaName],
c.name AS colName
FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
where SCHEMA_NAME(schema_id) = 'dbo' // you can include this where clause if you want to add additional filter to the result set, like query only tables that belong in particular db schema, or query only tables that starts with particular name (maybe prefix or sufix), etc.
ORDER BY tblName;
you have to execute the above query and take results in list
Have a look at the Information_Schema Views in your DB. These have all the stuff you want already in them.
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))
Previously, I asked a question about getting data from two tables where I take one row in a table and join it with several rows in another table. Here's the link to that discussion: SQL Select data from two tables (one row -> multiple rows)
Here is my SQL code:
SELECT
customer.fName, customer.lName, phone.phoneNumber
FROM
Customers customer
INNER JOIN phoneNumbers phone ON
customer.customerId = phone.customerId
What I would like to know now is: what is the best way to get this data organized in .net?
Let's suppose I have a C# class as following:
public class CustomerDetails
{
int customerId;
string fname;
string lName;
List<string> phoneNumbers;
}
For the sake of discussion, let's suppose that the above SQL query returns the following result:
fname, lname, phoneNumber
"John", "Smith", "111-111"
"Jane", "Doe", "222-1111"
"Jane", "Doe", "222-2222"
At a glance, I see that I have two customers; one has one phone number and the other has two phone numbers. How can I write code to efficiently parse this in C#?
One option is to use LINQ to create a instance of the CustomerDetails class.
Let me know if you would like an example.
Example 1:
List<CustomerDetails> customers = db.Customers.Select(c => new CustomerDetails(){
customerId = c.customerID,
fname = c.fName,
lName = c.lName,
phoneNumbers = (from p in db.PhoneNumbers where p.customerID == c.customerID select p.phoneNumber1).ToList<String>()});
Example 2:
List<CustomerDetails> custs = (from c in db.Customers
select new CustomerDetails()
{
customerId = c.customerID,
fname = c.fName,
lName = c.lName,
phoneNumbers = (from p in db.PhoneNumbers where p.customerID == c.customerID select p.phoneNumber1).ToList<String>()
}).ToList<CustomerDetails>();
I understand that you are looking for ORM object-relational mapping. In .NET I can recommend IBatis.net or LINQ.
Solution 1:
I assume that the records are sorted by name in the SQL query's results and that you also select the customerID. Appending "ORDER BY customer.fName, customer.lName" to your original query will do the trick.
I'll assume that you get your result in a DataReader, so you can do the following:
// Lets start by declaring a collection for our records
List<CustomerDetails> myRecords = new List<CustomerDetails>();
// Iterate all records from the results and fill our collection
while (yourReader.Read())
{
int customerID = int.Parse(yourReader["customerID"]);
int nrRecords = myRecords.Count;
if (nrRecords > 0 && myRecords[nrRecords - 1].customerId == customerID)
{
myRecords[nrRecords - 1].phoneNumbers.Add(yourReader["phoneNumber"]);
}
else
{
CustomerDetails newCustomerDetails = new CustomerDetails();
newCustomerDetails.customerId = customerID;
newCustomerDetails.fName = yourReader["fName"];
newCustomerDetails.lName = yourReader["lName"];
List<string> phoneNumberList = new List<string>();
phoneNumberList.Add(yourReader["phoneNumber"]);
newCustomerDetails.phoneNumbers = phoneNumberList;
myRecords.Add(newCustomerDetails);
}
}
P.S. If ordering the list is not an option, then you can't just check the latest added records customerid - instead you'll need to iterate through the myRecords list and search for the existance of it. This can be done in many ways including with myRecords.Contains() or with a foreach.
Solution 2:
Do the telephone number grouping directly from SQL. Create a function for selecting a comma separated string with all the telephone numbers of a particular customer:
CREATE FUNCTION [dbo].[GetCommaSeparatedPhoneNumbers]
(
#customerID int
)
RETURNS varchar(max)
AS
BEGIN
declare #output varchar(max)
select #output = COALESCE(#output + ', ', '') + phoneNumbers
from phoneNumbers
where customerId = #customerID
return #output
END
GO
Then you can nicely select the list of all customer you want:
SELECT customerId, dbo.GetCommaSeparatedPhoneNumbers(customerId)
FROM Customers
GROUP BY customerId
This will return:
"John", "Smith", "111-111"
"Jane", "Doe", "222-1111,222-2222"
Now it's all a question of parsing the results with a foreach or while loop, but no checking for existance is needed. Just split the string at ',' and insert the values into the List. If there is a chance, that there will be no phone numbers for some customers, then you can filter that field for null.
PS. Will not work if there is a comma as pointed out by BQ in his comment.
Iterate over the result, for each row check if it is in your list of costumers, if not, add a new one, if yes, add the phone number to the existing one.
Of course, you shouldn't parse the string use appropiate classes to access the database, like ado.net.