I don't understand Dapper's mapping, multimapping and QueryMultiple - c#

Title says it all, I'm trying to use it but I don't understand it. It's possible that the problem is a lack of knowledge due to that I'm an amateur, but I've read a dozen questions about this thing and googled for three days, and I still don't understand it.
I have SO many questions that I'm not sure that I should write it all in only one Question, or even if someone would read it all. If someone have other solution or think I should split it in different questions... well, I'm open to suggestions.
I was going to write an example, but again I read dozen of examples for days and didn't help me.
I just can't make my mind to understand how work something like the example at github:
var sql =
#"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id";
var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});
So, Post have a property of type User and that property is called Owner, right? Something like:
public class Post
{
...
public User Owner { get; set;}
}
Therefore Query<Post, User, Post> will return a Post instance with all the properties and what not, AND will create a User instance and assign it to Post.Owner property? How would simple parameters be added to that query, for example if someone wanted to pass the id as a int parameter like ...WHERE Id = #Id", new {Id = id}, where should the parameter be added given that the parameter right now is (post, user) => { post.Owner = user; return post;}? The parameter always refer to the types given, you can only use the simple typical parameters for the dynamic query, both can be used simultaneously? How?
Also, how does it to differentiate what DB field goes to what object? It makes something like class name=DB table name? What happens if the classes don't have the same name as the DB table and I want to use the [Table] attribte, will it work or the attribute is only for Dapper.Contrib.Extensionsmethods? Would it work with objects that share the same DB table?
Regarding same table for different objects question, f.i. lets say I have a Person object that have a BankAccount object:
public class Person
{
...
public BankAccount Account {get; set;}
...
}
public class BankAccount
{
private string _Account;
public string Account
{
get { return _Account; }
set
{
if(!CheckIfIBANIsCorrect(value))
throw new Exception();
_Account = value;
}
}
private bool CheckIfIBANIsCorrect(string IBAN)
{
//...
//Check it
}
}
I could store the string account at the same table than Person, since every person would have a single account referred by the person's Id. How should I map something like that? Is there even a way, should I simply load the result in a dynamic object and then create all the objects, will Query create the rest of the Person object and I should bother to create the nested object myself?
And by the way, how is splitOnsupposedly be used in all this? I understand that it should split the result into various "groups" so you can split the results by Ids f.i. and take what you need, but I don't understand how should I retrieve the info from the different "groups", and how it return the different "groups", lists, enumerables, what?.
QueryMultiple is other thing that is FAR beyond my understanding regardles how much questions and answers I read.
You know... how the * does that .Read thing work? All I read here or googling assumes that Read is some sort of automagic thing that can miracly discern between objects. Again, do it divide results by class names so I just have to be sure every object have the correct table name? And again what happens with [Table] attribute in this case?
I think the problem I'm having is that I can't find(I suppose it doesn't exist) a single web page that describes it all(the examples at GitHub are very scarce), and I only still finding answers to concrete cases that doesn't answer exactly what I'm trying to understand but only that concrete cases, which are confusing me more and more while I read them, since everyone seems to use a bunch of different methods without explaining WHY or HOW.

I think that your main problem with the Dapper querying of joined table queries is thinking that the second argument in the list is always the "param" argument. Consider the following code:
var productsWithoutCategories = conn.Query<Product>(
"SELECT * FROM Products WHERE ProductName LIKE #nameStartsWith + '%'",
new { nameStartsWith = "a" }
);
Here, there are two arguments "sql" and "param" - if we used named arguments then the code would look like this:
var productsWithoutCategories = conn.Query<Product>(
sql: "SELECT * FROM Products WHERE ProductName LIKE #nameStartsWith + '%'",
param: new { nameStartsWith = "a" }
);
In your example, you have
var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});
The second argument there is actually an argument called "map" which tells Dapper how to combine entities for cases where you've joined two tables in your SQL query. If we used named arguments then it would look like this:
var data = connection.Query<Post, User, Post>(
sql: sql,
map: (post, user) => { post.Owner = user; return post;}
);
I'm going to use the class NORTHWND database in a complete example. Say we have the classes
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public bool Discontinued { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
and we want to build a list of Products, with the nested Category type populated, we'd do the following:
using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
var productsWithCategories = conn.Query<Product, Category, Product>(
"SELECT * FROM Products INNER JOIN Categories ON Categories.CategoryID = Products.CategoryID,
map: (product, category) =>
{
product.Category = category;
return product;
},
splitOn: "CategoryID"
);
}
This goes through all the rows of JOIN'd Product and Category data and generates a list of unique Products but can't be sure how to combine the Category data with it, so it requires a "map" function which takes a Product instance and a Category instance and which must return a Product instance which has the Category data combined with it. In this example, it's easy - we just need to set the Category property on the Product instance to the Category instance.
Note that I've had to specify a "splitOn" value. Dapper presumes that the key columns of tables will simply be called "Id" and, if they are, then it can deal with joins on those columns automatically. However, in this case, we're joining on a column called "CategoryID" and so we have to tell Dapper to split the data back up (into Products and into Categories) according to that column name.
If we also wanted to specify "param" object to filter down the results, then we could do something like the following:
var productsWithCategories = conn.Query<Product, Category, Product>(
"SELECT * FROM Products INNER JOIN Categories ON Categories.CategoryID = Products.CategoryID WHERE ProductName LIKE #nameStartsWith + '%'",
map: (product, category) =>
{
product.Category = category;
return product;
},
param: new { nameStartsWith = "a" },
splitOn: "CategoryID"
);
To answer your final question, QueryMultiple simply executes multiple queries in one go and then allows you to read them back separately. For example, instead of doing this (with two separate queries):
using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
var categories = conn.Query("SELECT * FROM Categories");
var products = conn.Query("SELECT * FROM Products");
}
You could specify a single SQL statement that includes both queries in one batch, but you would then need to read them separately out of the combined result set that is returned from QueryMultiple:
using (var conn = new SqlConnection("Server=.;Database=NORTHWND;Trusted_Connection=True;"))
{
var combinedResults = conn.QueryMultiple("SELECT * FROM Categories; SELECT * FROM Products");
var categories = combinedResults.Read<Category>();
var products = combinedResults.Read<Product>();
}
I think that the other examples I've seen of QueryMultiple are a little confusing as they are often returning single values from each query, rather than full sets of rows (which is what is more often seen in simple Query calls). So hopefully the above clears that up for you.
Note: I haven't covered your question about the [Table] attribute - if you're still having problems after you've tried this out then I would suggest creating a new question for it. Dapper uses the "splitOn" value to decide when the columns for one entity end and the next start (in the JOIN example above there were fields for Product and then fields for Category). If you renamed the Category class to something else then the query will still work, Dapper doesn't rely upon the table name in this case - so hopefully you won't need the [Table] at all.

Related

LinqToSql: How do you read data when you don't know the DB Table yet?

I have already been working with linq in the past and I know how to access a database with SqlConnection and SqlCommand. Today I wanted to work with LinqToSql and see if and how I can make reading from and writing to a database easier. I did this Walkthrough.
Here is the code for the Customer Class (I changed it a bit but it still works perfectly fine):
[Table(Name = "Customers")]
class Customer
{
[Column(IsPrimaryKey = true)]
public string CustomerID { get; set; }
[Column]
public string City { get; set; }
}
And the code from Main:
class Program
{
static void Main(string[] args)
{
DataContext db = new DataContext(#"Data Source=(local)\SQLEXPRESS;Initial Catalog=Northwind;User ID=sa;Password=xxx");
Table<Customer> customers = db.GetTable<Customer>();
IQueryable query = from cust in customers
//where cust.City == "London"
select cust;
foreach (Customer customer in query)
Console.WriteLine("ID:{0}; City={1}", customer.CustomerID, customer.City);
Console.ReadKey();
}
}
It worked and I'm happy since this makes accessing a database so much easier for me. But there are still a couple of things that concern me:
It seems like for every column I want to include I need to create a new property in the Customer class and add a [Column] above it.
I need to create a new Class for every table that I want to read from (for example Customer, Orders and Suppliers in the Northwind DB).
First of all this seems to be quite a lot of unnecessary and repetitive work. Am I doing something wrong here?
Also I want the user to type in the name of a database and a table. This means I don't know yet which database he will pick and I also don't know the structure of the table yet. I can't create the class yet that is supposed to represent the table.
This means I will need to:
Create a type / class / object dynamically. I can't use Table<Customer> customers = db.GetTable<Customer>() because I don't know the type yet. The type will be the dynamically created class.
Mark the type / class / object as a table with [Table(Name="xxx")].
Read the columns from the SqlTable and create for every column a property and mark it with [Column].
After I'm done with this I can get the table with Table<T> table = db.GetTable<T>(), execute the query and display the datarows.
My second (and more important) question is: How can I do this? Does anyone have code examples or links to share? Or is this approach wrong?
You can store the result in dynamic object just like this.
dynamic table = db.GetTable<T>()
and use reflection to get type of the object.

Entity Framework Core Linq query returning ids which do not exist in database

I wonder if there is an easy way using Linq to SQL with Entity Framework Core to query check if a given list of ids exist in the database and which returns the list of ids that do not exist.
The use case I come across this is if the user can do something with a list of object (represented through the list of their ids) I want to check if these ids exist or not.
Of course I could query all objects/object ids that exist in the database and cross check in a second step.
Just wondering if it would be possible in one step.
What I mean in code:
public class MyDbObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public IActionResult DoSomethingWithObjects([FromQuery]List<int> ids}
{
List<int> idsThatDoNotExistInTheDb = DbContext.MyDbObject.Where(???)
return NotFound("those ids do not exist: " + string.Join(", ", idsThatDoNotExist));
}
You can obtain the list of IDs that match, then remove them from the original list, like this:
var validIds = DbContext
.MyDbObject
.Where(obj => ids.Contains(obj.Id))
.Select(obj => obj.Id);
var idsThatDoNotExistInTheDb = ids.Except(validIds);
This approach may be slow, though, so you may be better off doing it in a stored procedure that takes a table-valued parameter (how?)
Note: Pre-checks of this kind are not bullet-proof, because a change may happen between the moment when you validate IDs and the moment when you start the operation. It is better to structure your APIs in a way that it validates and then does whatever it needs to do right away. If validation fails, the API returns a list of errors.

Use generic method to return list<T> where T could be one of three types that share the same structure

I have some sql views where different source tables are used, but each view returns exactly the same structure.
So very simplistically:
view1
select tblA.Name as custName,
tblA.DOB as DateOfBirth,
tblA.accountBalance as AccountBalance
from myFirstTable tblA
view2
select tblB.AccountName as custName,
tblB.BirthDate as DateOfBirth,
tblB.Balance as AccountBalance
from mySecondTable tblB
view3
select tblC.CustomerName as custName,
tblC.DateOfBirth as DateOfBirth,
tblC.accBal as AccountBalance
from myThirdTable tblC
So even though the fieldnames in the source tables are different, the different views return identically-named (and typed) fields. The actual views are complex and massive, running to hundreds of lines each.
I then drag these views into my Linq-to-sql designer and try and use them in a generic class.
public static List<T> MainSearch<T>(ReportParams RP)
{
MyDataContext dc = new MyDataContext();
if (string.IsNullOrEmpty(RP.appType)) { return null; }
var searchQuery = new List<T>();
switch (RP.appType)
{
case "type1":
searchQuery = (from t in dc.view1s select t);
break;
case "type2":
searchQuery = (from t in dc.view2s select t);
break;
case "type3":
searchQuery = (from t in dc.view3s select t);
break;
}
//do other stuff with search query depending on params
DateTime dtFrom = Convert.ToDateTime(RP.fromDate);
searchQuery = searchQuery.Where(q => convert.ToDateTime(q.DateOfBirth) >= dtFrom);
//and so on...
The idea being that a generic front end can be used that passes in parameters depending on user selection and then these parameters are used to narrow down the search results for the specific source data being searched for (it will never be a combination returned, it's always one or other of the three views).
The code above does not work. In my switch statement, the intellisense complains that it cannot implicitly convert from a specific type (of view1, for example) to a generic list of T. This then stops me using the strong types in lambda expressions further down.
Is there any way to achieve what I'm trying to do or am I barking up completely the wrong tree here?
I haven't fully tested this but I think you could do it by creating an interface with properties custName, DOB and AccountBalance. Then have each of your generated LINQ-to-SQL classes (tblA, tblB, tblC) implement this interface in a separate partial class file. This should be okay as long as the property names and types match.
interface IAccountTable
{
string custName { get; set; }
DateTime DOB { get; set; }
Decimal AccountBalance { get; set; }
}
and
partial class tblA : IAccountTable
{
}
Make your searchQuery a list of that interface.
var searchQuery = new List<IAccountTable>();
The cast the results of your actual queries to the interface.
searchQuery = tblA.Where(t => t.custName == "Uday").Select(t => (IAccountTable)t).ToList();
If you have a class that has the same set of properties as returned by the views, then may be you can try something like this:
case "type1":
searchquery = (from t in dc.view1s select new commonClass() { CustName = t.CustName, DateOfBirth = t.DateOfBirth, AccountBalance = t.AccountBalance });
break;
and so on for other cases...
In Entity Framework, the right approach would be to create a separate TBaseclass, that T1, T2, T3 would derive from. It would contain all the common properties you need, and therefore there would be no need for the switch statement: you would just have TBase someObj; and then be able to use someObj.ACommonProperty directly.

DTO and ViewBag issue MVC C#

Hi i have a problem getting data from two sources and combining into one DTO and then sending to the View
I have a list of Users in my Database and i have a list of Jobs in my WCF Service. What i am trying to do is make an Add Job page where i get all of the Users current Jobs from the Database and make a new DropDown list in the View that display all the Jobs a user doesnt have. So i intersect the two lists and create a new list with all obtained job removed.
I have tried doing it but cannot see where im going wrong.
public ActionResult AddJob(String usrCode)
{
var jobs = jobClient.getAllJobs();
var allJobCodes = (from s in jobs select s.jobCode).ToList();
var thisJobCode = (from s in db.UserJobs
where s.usrCode == usrCode
select s.jobCode).ToList();
var notObtained = allJobCodes.Except(thisJobCode);
IEnumerable<String> list1 = allJobCodes.AsEnumerable();
IEnumerable<String> list2 = notObtained.AsEnumerable();
IEnumerable<String> list3 = list2.Select(x => new UserJobsDTO()
{ jobCode = x });
IEnumerable<UserJobsDTO> list = list3(jobs, notObtained);
ViewBag.jobCode = new SelectList(list, "jobCode", "Description");
var model = new UserJobsDTO { usrCode = usrCode, jobCode = list};
return View("AddJob", model);
}
public class UserJobsDTO
{
public string usrCode { get; set; }
public IEnumerable<String> jobCode { get; set; }
public String Description { get; set; }
}
Can anyone help me out? The main problems at the moment are with jobCode = x complaining about....
Error 1 Cannot implicitly convert type
'string' to 'System.Collections.Generic.IEnumerable<string>'
And list3 variable complaining about this....
Error 2 'list3' is a 'variable' but is used like a 'method'
The errors state exactly what the problems are...
Cannot implicitly convert type
'string' to 'System.Collections.Generic.IEnumerable'
Job code is a collection:
public IEnumerable<String> jobCode { get; set; }
But you're trying to assign a non-collection value to it:
list2.Select(x => new UserJobsDTO()
{ jobCode = x })
So either you need to make jobCode a string (such that any given instance of the DTO has one value) or set the entire collection to it (such that any given instance of the DTO has the list of values).
'list3' is a 'variable' but is used like a 'method'
I literally have no idea what you're even trying to do here:
IEnumerable<UserJobsDTO> list = list3(jobs, notObtained);
But, as the error states, list3 is a variable not a method. You can't invoke a variable like a method.
It's very confusing what you're trying to accomplish in this code. And, honestly, the best advice at this time would just be to step through in a debugger, examine the runtime values that you have, and really think about how to structure those values for your view.
In particular, it will be very helpful to semantically discern between singular values and plural values. For example, terms like UserJobsDTO or jobCode imply certain levels of singularity and plurality, but their types and structures don't agree with that. (How can a collection of strings be a single "code"?)
Just the names and types of the structures you're using are very important in being able to logically understand and express what you're trying to do.

SQL query shows good database values, but LINQ to Entity framework brings a null value from nowhere

I'm having a really strange problem here, and i dont have any clue why.
I'm supposed to make small localdb console app in C#. The goal is to enter persons (teachers, actually) in the DB, with a certain amount of information.
I have a few classes, but 2 of them are important here: Certification and Notation.
Certifications are, well, certifications of the professors.
The code for these classes is this:
class Certification
{
public int CertificationID { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Course Course { get; set; }
public string CertificationName { get; set; }
public virtual Notation Notation { get; set; }
}
class Notation
{
public int NotationID {get;set;}
public string Note {get;set;}
}
Nothing too dangerous. Through migrations i made my database, and they look like they should:
Certification:
CertificationID (PK)
CertificationName
Course_CourseID (FK to another class, course)
Notation_NotationID (FK to notations)
Teacher_TeacherID (FK to the teachers)
Notations:
NotationID (PK)
Note
My program allows me to add teachers, with all the informations i need, and for example, their certifications. Here, i made some dummy teacher, with a dummy certification.
If i call SELECT * FROM Certification , i get exactly what i should get, a single line like this:
CertificationID = 6
CertificationName = placeholder
Course_CourseID = 13
Notation_NotationID = 12
Teacher_TeacherID = 5
Everything is correct in this. CourseID links to an actual course in the database, NotationID in an actual note, and Teacher to an actual teacher too. Everything is fine!
Now, i just want to show the certifications of our teacher:
var certifs = from c in db.Certifications where c.Teacher.TeacherID == item.TeacherID select c;
foreach(var v in certifs )
{
var course = (from c in db.Courses where c.CourseID == v.Course.CourseID select c).First();
var note = (from n in db.Notations where n.NotationID == v.Notation.NotationID select n.NotationID).First();
Console.WriteLine("Name: " + v.CertificationName + ", related to the " + course.CourseName + " course, with a note of " + note);
Console.WriteLine("");
}
And it doesn't work. When my foreach loop starts, my first item in the loop doesn't have any reference to a notation. Everything else is fine: the foreign keys for the course and the teachers are here, and valid, but for the notation, i only get a null value. So my certification item looks more like:
CertificationID = 6
CertificationName = placeholder
Course_CourseID = 13
Notation_NotationID = null
Teacher_TeacherID = 5
Basically, if i do a SQL Query, my row in the database is perfectly fine, but calling it through the entity framework (and LINQ) returns a null value for the notation. (which throws an exception when calling var note etc....
Does anybody have an idea about this? I'm really stuck on this.
I'm sorry if my English isn't good enough. If you guys need more information, just ask.
Anwsered by JC:
Lazy loading isnt working properly. Eager loading solves the problem.
The problem is you aren't populating your navigation properties when you retrieve the Certification entities. Then you try to access them and they're null.
You either need to make sure lazy loading is turned on:
Configuration.LazyLoadingEnabled = true; //In your DbContext's constructor
in which case just accessing the Course and Notification references should cause them to be populated in separate database transactions...
...or you need to employ eager loading when querying against the DbSet:
var certifs = from c in db.Certifications.Include(c=>c.Course).Include(c=>c.Notation) where ...
Which will cause Course and Notation to be loaded at the same time Certifications is loaded all in one database transaction.
In your line
var note = (from n in db.Notations
where n.NotationID == v.Notation.NotationID
select n.NotationID).First();
you are selecting n.NotationID only which would return an integer only. Trying changing the select to select n

Categories

Resources