Mapping 4 Tables to a single Object in Dapper - mapping

I have a quick question about Dapper. I have a query that returns 4 tables.
Three tables have just one integer column. Call them field1, field2, and field3.
The 4th table has 5 columns say:
A,B,C,D,E.
I have made an object called ResultSet that has all the fields from the 4 tables
public class ResultSet
{
int field1;
int field2;
int field3;
string A;
string B;
string C;
string D;
string E
}
How do I map the results to the ResultSet object?
Currently I am using QueryMultiple to get the desired result. But it is only mapping the 1st 3 columns. A,B,C,D,and E are all null.
I do not want to use a Union to get all the Fields in just one single table.

You should be able to achieve this by handing the connection.Query extension method an appropriate parameterised SQL statement, and pass it your object as the Type parameter.
Dapper will then map your query to the object magically, assuming you alias the items in the select list appropriately (ie, alias them with the corresponding property name of your object).
Something along these lines should work:
public class SomeObject
{
public int Field1 {get; set;}
public int Field2 {get; set;}
public int A {get; set;}
public int B {get; set;}
public int C {get; set;}
public int D {get; set;}
}
using(var connection = SomeConnectionFactory.GetConnection())
{
var yourObject =
connection.Query<SomeObject>("select tab1.someThing as Field1, " +
"tab2.someThing as Field2, " +
"tab4.onePotato as A, " +
"tab4.twoPotato as B, " +
"tab4.threePotato as C, " +
"tab4.four as D " +
"from someTable tab1 " +
"join someTable2 tab2 on tab1.Id = tab2.Id " +
"$$ etc etc for the other joins $$" +
"where tab1.Id = :ID " + ,new {ID = someId});
};
One note is that i've used the bind variable syntax for an Oracle database (:). You'll need to replace this with the equivalent for your DB.
Hope that's useful.

Related

Neo4j Adding Multiple Nodes and Edges Efficiently

I have the below example.
I was wondering what is the best and quickest way to add a list of nodes and edges in a single transaction? I use standard C# Neo4j .NET packages but open to the Neo4jClient as I've read that's faster. Anything that supports .NET and 4.5 to be honest.
I have an lists of about 60000 FooA objects that need to be added into Neo4j and it can take hours!
Firstly, FooB objects hardly change so I don't have to add them everyday. The performance issues is with adding new FooA objects twice a day.
Each FooA object has a list of FooB objects has two lists containing the relationships I need to add; RelA and RelB (see below).
public class FooA
{
public long Id {get;set;} //UniqueConstraint
public string Name {get;set;}
public long Age {get;set;}
public List<RelA> ListA {get;set;}
public List<RelB> ListB {get;set;}
}
public class FooB
{
public long Id {get;set;} //UniqueConstraint
public string Prop {get;set;}
}
public class RelA
{
public string Val1 {get;set;}
pulic NodeTypeA Node {get;set;
}
public class RelB
{
public FooB Start {get;set;}
public FooB End {get;set;}
public string ValExample {get;set;}
}
Currently, I check if Node 'A' exists by matching by Id. If it does then I completely skip and move onto the next item. If not, I create Node 'A' with its own properties. I then create the edges with their own unique properties.
That's quite a few transactions per item. Match node by Id -> add nodes -> add edges.
foreach(var ntA in FooAList)
{
//First transaction.
MATCH (FooA {Id: ntA.Id)})
if not exists
{
//2nd transaction
CREATE (n:FooA {Id: 1234, Name: "Example", Age: toInteger(24)})
//Multiple transactions.
foreach (var a in ListA)
{
MATCH (n:FooA {Id: ntA.Id}), (n2:FooB {Id: a.Id }) with n,n2 LIMIT 1
CREATE (n)-[:RelA {Prop: a.Val1}]-(n2)
}
foreach (var b in Listb)
{
MATCH (n:FooB {Id: b.Start.Id}), (n2:FooB {Id: b.End.Id }) with n,n2 LIMIT 1
CREATE (n)-[:RelA {Prop: b.ValExample}]-(n2)
}
}
How would one go about adding a list of FooA's using for example Neo4jClient and UNWIND or any other way apart from CSV import.
Hope that makes sense, and thanks!
The biggest problem is the nested lists, which mean you have to do your foreach loops, so you end up executing a minimum of 4 queries per FooA, which for 60,000 - well - that's a lot!
Quick Note RE: Indexing
First and foremost - you need an index on the Id property of your FooA and FooB nodes, this will speed up your queries dramatically.
I've played a bit with this, and have it storing 60,000 FooA entries, and creating 96,000 RelB instances in about 12-15 seconds on my aging computer.
The Solution
I've split it into 2 sections - FooA and RelB:
FooA
I've had to normalise the FooA class into something I can use in Neo4jClient - so let's introduce that:
public class CypherableFooA
{
public CypherableFooA(FooA fooA){
Id = fooA.Id;
Name = fooA.Name;
Age = fooA.Age;
}
public long Id { get; set; }
public string Name { get; set; }
public long Age { get; set; }
public string RelA_Val1 {get;set;}
public long RelA_FooBId {get;set;}
}
I've added the RelA_Val1 and RelA_FooBId properties to be able to access them in the UNWIND. I convert your FooA using a helper method:
public static IList<CypherableFooA> ConvertToCypherable(FooA fooA){
var output = new List<CypherableFooA>();
foreach (var element in fooA.ListA)
{
var cfa = new CypherableFooA(fooA);
cfa.RelA_FooBId = element.Node.Id;
cfa.RelA_Val1 = element.Val1;
output.Add(cfa);
}
return output;
}
This combined with:
var cypherable = fooAList.SelectMany(a => ConvertToCypherable(a)).ToList();
Flattens the FooA instances, so I end up with 1 CypherableFooA for each item in the ListA property of a FooA. e.g. if you had 2 items in ListA on every FooA and you have 5,000 FooA instances - you would end up with cypherable containing 10,000 items.
Now, with cypherable I call my AddFooAs method:
public static void AddFooAs(IGraphClient gc, IList<CypherableFooA> fooAs, int batchSize = 10000, int startPoint = 0)
{
var batch = fooAs.Skip(startPoint).Take(batchSize).ToList();
Console.WriteLine($"FOOA--> {startPoint} to {batchSize + startPoint} (of {fooAs.Count}) = {batch.Count}");
if (batch.Count == 0)
return;
gc.Cypher
.Unwind(batch, "faItem")
.Merge("(fa:FooA {Id: faItem.Id})")
.OnCreate().Set("fa = faItem")
.Merge("(fb:FooB {Id: faItem.RelA_FooBId})")
.Create("(fa)-[:RelA {Prop: faItem.RelA_Val1}]->(fb)")
.ExecuteWithoutResults();
AddFooAs(gc, fooAs, batchSize, startPoint + batch.Count);
}
This batches the query into batches of 10,000 (by default) - this takes about 5-6 seconds on mine - about the same as if I try all 60,000 in one go.
RelB
You store RelB in your example with FooA, but the query you're writing doesn't use the FooA at all, so what I've done is extract and flatten all the RelB instances in the ListB property:
var relBs = fooAList.SelectMany(a => a.ListB.Select(lb => lb));
Then I add them to Neo4j like so:
public static void AddRelBs(IGraphClient gc, IList<RelB> relbs, int batchSize = 10000, int startPoint = 0)
{
var batch = relbs.Select(r => new { StartId = r.Start.Id, EndId = r.End.Id, r.ValExample }).Skip(startPoint).Take(batchSize).ToList();
Console.WriteLine($"RELB--> {startPoint} to {batchSize + startPoint} (of {relbs.Count}) = {batch.Count}");
if(batch.Count == 0)
return;
var query = gc.Cypher
.Unwind(batch, "rbItem")
.Match("(fb1:FooB {Id: rbItem.StartId}),(fb2:FooB {Id: rbItem.EndId})")
.Create("(fb1)-[:RelA {Prop: rbItem.ValExample}]->(fb2)");
query.ExecuteWithoutResults();
AddRelBs(gc, relbs, batchSize, startPoint + batch.Count);
}
Again, batching defaulted to 10,000.
Obviously time will vary depending on the number of rels in ListB and ListA - My tests has one item in ListA and 2 in ListB.

Database First with Multiple Tables with Foreign Keys in a Single View

I have two tables department and teacher like this:
Department table (DeptID is the primary key)
DeptID | DeptName
1 P
2 C
3 M
Teacher table (DeptID is a foreign key)
DeptID | TeacherName
1 ABC
1 PQR
2 XYZ
I have used database first approach to create a single model out of these two tables. I want to display both details in a single view like this:
TeacherName | DeptName
ABC P
PQR P
XYZ C
I tried to create controllers using scaffolding but it would provide views and CRUD operations for a single table in the model.
Is there any method using which I can map these two tables together in a single view ? or is it possible (easily achievable) when I use different models for each table in the database ?
You have to create Viewmodel.
public class DepartmentTeacher
{
public int DeptID {get;set;}
public string DeptName {get;set;}
public int TeachID {get;set;}
public string TeachName {get;set;}
}
using (var db = new SchoolContext())
{
var query = (from tc in db.Teacher
join dp in db.Department on tc.DeptID equals dp.DeptID
//where st.STUDENT_ID == Customer_Id maybe you need
select new
{
dp.DeptName,
tc.TeachName
});
foreach (var item in query)
{
DepartmentTeacher.DeptName = item.DeptName;
DepartmentTeacher.TeachName = item.TeachName;
}
}
return View(DepartmentTeacher);
You can use every process this viewmodel.However you have to description this Viewmodel on your view page.

Unable to cast object of type 'System.Collections.Generic.List TO System.Collections.Generic.IList

I have the following code :
public class ComputeOperationData
{
public COUNTERS_DATA CounterData { get; set; }
public COMPUTES_OPERATION ComputeOperation { get; set; }
public COUNTERS_FORMULA CounterFormula { get; set; }
}
Public Function GetComputeOperationData(ByVal computeId As Integer, ByVal priodType As Integer, ByVal lstDuringTime As DuringTime) As IList(Of ComputeOperationData) Implements IComputeResultService.GetComputeOperationData
Dim computeOperations As IQueryable(Of COMPUTES_OPERATION) = _repository.GetObjectQuery(Of COMPUTES_OPERATION).Include("COMPUTES").Include("COUNTERS")
computeOperations = computeOperations.Where(Function(a) a.COMPUTE_ID = computeId)
Dim countersFormula As IQueryable(Of COUNTERS_FORMULA) = _repository.GetObjectQuery(Of COUNTERS_FORMULA)().Include("COUNTERS")
Dim countersData As IQueryable(Of DomainClasses.COUNTERS_DATA) = _repository.GetObjectQuery(Of DomainClasses.COUNTERS_DATA)().Include("COUNTERS")
countersData = countersData.Where(Function(a) a.PERIOD_TYPE = priodType And a.COUNTER_DATE = lstDuringTime.StartDate And a.COUNTER_TIME >= lstDuringTime.StartTime And a.COUNTER_TIME <= lstDuringTime.EndTime)
Dim lstComputeOperationData = From counterData In countersData
Join computeOperation In computeOperations On counterData.COUNTER_ID Equals computeOperation.COUNTER_ID
Join counterFormula In countersFormula On counterData.COUNTER_ID Equals counterFormula.COUNTER_ID
Where counterData.COUNTER_DATE >= counterFormula.FROM_DATE And counterData.COUNTER_DATE <= counterFormula.TO_DATE
Return lstComputeOperationData.ToList
End Function
And I got this error :
Unable to cast object of type 'System.Data.Objects.ObjectQuery1[VB$AnonymousType_13[Danesh.Ems.DomainClasses.COUNTERS_DATA,Danesh.Ems.DomainClasses.COMPUTES_OPERATION,Danesh.Ems.DomainClasses.COUNTERS_FORMULA]]' to type 'System.Collections.Generic.IList`1[Danesh.Ems.Models.ComputeOperationData]'
please help me
It's been a while since I used VB.NET, but in C#, you'd need something like...
Select New ComputeOperationData()
{
CounterData = counterData, ComputeOperation = computeOperation, CounterFormula = counterFormula
}
...at the end of your query to transform the three separate data sources into a single object.
You have function that return IList(Of ComputeOperationData) but inside you tru return List(Of AnonymousClass)
so in your linq query you need specify what type of object you want return, like this
Dim lstComputeOperationData = From counterData In countersData
Join computeOperation In computeOperations On counterData.COUNTER_ID Equals computeOperation.COUNTER_ID
Join counterFormula In countersFormula On counterData.COUNTER_ID Equals counterFormula.COUNTER_ID
Where counterData.COUNTER_DATE >= counterFormula.FROM_DATE And counterData.COUNTER_DATE <= counterFormula.TO_DATE
Select New ComputeOperationData With {.CounterData = counterData, .ComputeOperation = computeOperation, .CounterFormula = counterFormula}
When you see the runtime error message: Unable to cast object of type Generic.List [SomeDerivedType] to Generic.IList[SomeBaseType], you may simply need to Cast the objects to the proper type in the list before making the assignment to the IList.
For example:
Dim objectIList As IList(Of Object)
Dim stringList As New List(Of String)
objectIList = stringList.Cast(Of Object).ToList()
This is tricky because the compiler does not catch this. There are several C# based questions that relate to this issue. Here is one. Here is another.

Subsonic 3: Strongly typed return value for stored procedures that return mixed results from different tables

Say I have a stored procedure that returns dataSet from 2 different tables. Example:
SELECT Customers.FirstName, Customers.LastName, SUM(Sales.SaleAmount) AS SalesPerCustomer
FROM Customers LEFT JOIN Sales
ON Customers.CustomerID = Sales.CustomerID
GROUP BY Customers.FirstName, Customers.LastName
Is there any way to get a strongly typed list as a result from this stored procedure ? Something like this:
StoredProcedure sp = myDevDB.GetCustomerSales();
List<MyCustomType> resultSet = sp.ExecuteTypedList<MyCustomType>();
How and where do I define the MyCustomType class ? How do I map its properties to the actual table columns ?
Thanks,
Zohrab.
I just created a asp.net web page that does this. Try this:
DataSet ds = new DataSet();
SqlDataAdapter adtp = new SqlDataAdapter(command);
adtp.Fill(ds);
StringBuilder b = new StringBuilder();
b.AppendLine("class " + this.txtSP.Text + "_QueryResult");
b.AppendLine("{");
foreach ( DataColumn c in ds.Tables[0].Columns )
{
b.AppendLine(string.Format("property {0} {1} {{ get; set; }}", c.DataType, c.ColumnName));
}
b.AppendLine("}" + Environment.NewLine);
this.txtResult.Text = b.ToString();
}
catch { }

to separate comma separated values of field and add them in integer field of temp table

I have one table member_details with field "preferred_location" (varchar) that has comma separated values like "19,20,22" that come from a listbox selection ....
Now I also have another table city_master having field "city_id" (int) and "city_name" (varchar)...
Now I want to separate "preferred_location" (varchar) values and to add them in integer field of temp table so I can make an inner join between city_id(int) of the temp table and city_id(int) of city_master and then can get city name from city_name of city_master...
This is all stuff I need in MySQL - either a stored procedure or a function. I am using it with c#.net.
Frankly, this sounds like a bad design. If you need the integers values separately, then modify your database structure accordingly, and save the values separately to begin with.
I mean, you see where it leads to - because you stored the values as a list in a string, you have maneuvered yourself into a position where you need to unwind the values each time you want to join the tables.
That's like putting the horse behind the wagon.
If these integers are small, like 19,20,22 etc just use smaller 16 or 8 bit integers (as supported by your database) and it should not take much more space than a string (possibly even less).
Made up some mock up example, but this should work with LinqToMySql as well.
class user {
public string name {get;set;}
public int id {get;set;}
}
class member_detail {
public int user_id {get;set;}
public string prefered {get;set;}
}
class city_master{
public int code {get;set;}
public string name {get;set;}
}
void Main()
{
var users = new List<user>();
users.Add(new user(){name = "Mary",id = 1});
users.Add(new user(){name = "John",id=2});
var details = new List<member_detail>() ;
details.Add(new member_detail(){user_id=1,prefered="1,2,3"});
details.Add(new member_detail(){user_id=2,prefered="3,5"});
var cities = new List<city_master>();
cities.Add(new city_master(){code =1,name="Moscow"});
cities.Add(new city_master(){code =2,name="London"});
cities.Add(new city_master(){code =3,name="Paris"});
cities.Add(new city_master(){code =4,name="Rome"});
cities.Add(new city_master(){code =5,name="Madrid"});
users.Select(u=>new {u.name,cities=
details.Where(d=>d.user_id==u.id)
.SelectMany(d=>d.prefered.Split(','))
.Join(cities,c=>c,d=>d.code.ToString(),(a,b)=>new {b.name})}).Dump();
}
thanks for your suggestion but in my case it is better to store ids of preferred location cities as comma separated.
I have a procedure that makes a temporary table and then I can use inner join with city_master table to get city names.
Create Procedure parseAndStoreList(in thingId int, in i_list varchar (128),
out returnCode smallInt)
BEGIN
DECLARE v_loopIndex default 0;
DECLARE Exit Handler for SQLEXCEPTION
BEGIN
call saveAndLog(thingId, 'got exception parsing list');
set returnCode = -1;
END;
call dolog(concat_ws('got list:', i_list));
pase_loop: LOOP set v_loopIndex = v_loopIndex + 1;
call dolog(concat_wc(',', 'at loop iteration ', v_loopIndex);
LOOOP parse_loop;
set returnCode = 0;
END;

Resources