OrmLite query to select some of the columns from each of 2 joined tables - ormlite-servicestack

Following on from this comment, how can I do a ServiceStack OrmLite query that joins two or more tables and returns some of the columns from each of them?
Using the OrmLite Does_only_populate_Select_fields_wildcard unit test as example, I'd like to do something like this:
public class DeptEmployee
{
[PrimaryKey]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[References(typeof(Department2))]
public int DepartmentId { get; set; }
[Reference]
public Department2 Department { get; set; }
}
public class Department2
{
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
}
var q = db.From<DeptEmployee>()
.Join<Department2>()
.Select<DeptEmployee, Department2>((de, d2) => new[] { de.FirstName, de.LastName, d2.Name });
var results = db.Select(q);
The above does not return a list of anonymous types containing FirstName, LastName and Name, as I'd expect. It still returns a list of DeptEmployee objects (but with only FirstName and LastName populated).

An important thing to note in OrmLite is how the query is constructed and executed is independent to how the results are mapped. It doesn't matter whether the query is raw custom SQL or a Typed SQL Expression, OrmLite only looks at the dataset returned to workout how the results should be mapped.
So when use the Select<T>(SqlExpression<T>) API, OrmLite will always try to map the results into the primary SqlExpression Type in db.From<DeptEmployee>() which isn't what you want since the custom columns you've selected don't match the shape of DeptEmployee POCO.
There are a few different ways to read a custom schema which all work off the same query (as it's independent to how you chose to map the results):
var q = db.From<DeptEmployee>()
.Join<Department2>()
.Select<DeptEmployee, Department2>(
(de, d2) => new { de.FirstName, de.LastName, d2.Name });
Our recommendation, esp. for a typed code-first ORM like OrmLite is to create a Typed Custom POCO and select that, e.g:
class Custom
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name { get; set; }
}
db.Select<Custom>(q).PrintDump();
Which will print out a nice:
[
{
FirstName: First 1,
LastName: Last 1,
Name: Dept 1
},
]
The primary benefit is that you get Typed access to your custom results in a List<Custom>.
If you don't want to create a custom Type you can Select OrmLite's Dynamic Result APIs, e.g:
If you're happy knowing the positions of the different fields you can select a List<object> which will return the selected fields in the order they were selected, e.g:
db.Select<List<object>>(q).PrintDump();
Prints:
[
[
First 1,
Last 1,
Dept 1
],
]
Otherwise if you also want the names returned you can select a string object dictionary, e.g:
db.Select<Dictionary<string,object>>(q).PrintDump();
Which prints results similar to the Custom POCO, but the names and corresponding values are maintained in a loose-typed object Dictionary:
[
{
FirstName: First 1,
LastName: Last 1,
Name: Dept 1
},
]
If you were instead only selecting 2 columns, e.g:
var q = db.From<DeptEmployee>()
.Join<Department2>()
.Select<DeptEmployee, Department2>(
(de, d2) => new { de.LastName, d2.Name });
You can make of OrmLite's convenient data access APIs which will let you select 2 columns into a Dictionary<string,string>, e.g:
db.Dictionary<string,string>(q).PrintDump();
Which prints:
{
Last 1: Dept 1,
Last 2: Dept 2,
Last 3: Dept 3
}
Notice this is very different to the string object dictionary above as it returns results in a single Dictionary<string,string> for all rows instead of List<Dictionary<string,object>>, which has a Dictionary for each row.
Likewise if you were only selecting 1 field, e.g:
var q = db.From<DeptEmployee>()
.Join<Department2>()
.Select(x => x.LastName);
Then you can select a singular column of results in a List<string>, e.g:
db.Column<string>(q).PrintDump();
Which prints:
[
Last 1,
Last 2,
Last 3
]
If you instead wanted distinct results you can return them in a HashSet<string> with:
db.ColumnDistinct<string>(q).PrintDump();
To return to the original important point, it doesn't matter how the query was constructed (which just controls the SQL that's generated), OrmLite only looks at the returned resultset to Map the results, which it tries to map to the target API that you've specified you want the results mapped into, so executing custom SQL:
db.Column<string>("SELECT LastName FROM DeptEmployee").PrintDump();
Or if you executed a Stored Procedure:
db.Column<string>("EXEC GetLastNamesFromDeptEmployees").PrintDump();
Is mapped exactly the same way if you used a typed SQL Expression, i.e. OrmLite only looks at the resultset which it maps to how you want the results returned.

Related

How to diagnose slow Entity Framework stored procedure call?

Problem: I'm calling a stored procedure through EF Core. When I run the stored procedure directly (via 'debug procedure'), it runs quickly, but it runs VERY slowly when called by EF's FromSqlRaw. So the problem appears to be when converting the returned data-table to a list of objects.
Setup:
Simple application with a list of blog posts. The stored procedure gets a hierarchical list of posts and associated users from a TPH table of posts, plus a table of users.
// Code is simplified, actually 8 parameters
SqlParameter depth_p = new SqlParameter("#depth", depth);
SqlParameter authorizedUserID_p = new SqlParameter("#authorizedUserID", authorizedUser.ID);
IQueryable<PostUser> query = postContext.PostUsers
.FromSqlRaw("Post.USP_ReadDebate #depth, #authorizedUserID",
parameters: new[] { depth_p, authorizedUserID_p });
List<PostUser> postUsers = query.ToList(); // This hangs.
26 columns are returned and converted by EF into the PostUser class.
PostUser holds 26 "ordinary" properties. No navigation properties, custom classes or any getters or setters that do any work.
public class PostUser
{
// Post fields
public Int32? ID { get; set; } // Primary Key
public String Text { get; set; }
public Guid OwnerID { get; set; }
public int? ParentID { get; set; } // nullable
public bool IsDisabled { get; set; }
public DateTime TimeCreated { get; set; }
public bool User_IsBanned { get; set; } = false;
// some others...
public PostUser() { }
}
Note: the stored procedure is very complex. It calls another stored procedure which fills a #spid table, then inserts the contents of that #SPID table into a table variable and returns that.
But again when debugged directly it returns quickly, so I think the problem is when EF Core is converting the returned data to the PostUser object.
Bottom Line: is there any way to get visibility into what EF Core is doing on the conversion to PostUser to find the problem?
Thank you!

MongoDB is identifying field with ObjectID as null even thought the value exists

I have created a collection and added an index key by grouping 4 fields as unique.
Here are the example of field and value.
"username":string,
"companyID": ObjectID,
"areaCode": string,
"lotNum":string
The companyID's ObjectID, I'm getting it from another collection's document ID as it's ID.
I tried to insert through MongoDB .Net driver and encountered some problem.
Company company = new Company()
{
CompanyName = "xyz"
};
await _company.InsertOneAsync(company); //_company is IMongoCollection object
SomeClass someClass = new SomeClass()
{
UserName = "James",
CompanyID = company.ID, //This is the ObjectID generated by MongoDB
AreaCode = "ABC",
LotNum = "1234a"
};
await _someClass.InsertOneAsync(company); //_someClass is IMongoCollection object
So, the document object in MongoDB will look like below. I can actually view the document using Compass.
_id:ObjectID("5b062d5be75ed035f057bf06")
username:"James",
companyID: ObjectID("5b062d5be75ed035f057bf05"),
areaCode: "ABC",
lotNum:"1234a"
Now the problem is when I tried to find the document in SomeClass collection with {companyID:ObjectID("5b062d5be75ed035f057bf05")}, I'm unable to find it.
But if I use {companyID:null}. It's returning the document.
And I'm not able to add any new document with same username,areaCode and lotNum with a different companyID, "as duplicate key error collection" occurs eventhou the ID is from a newly created Company object have brand new ID. MongoDB is still saying it is NULL.
Am I doing something wrong? How can I fixed this problem.
Here are my data object for Company and SomeClass
public class Company
{
public ObjectId Id { get; set; }
public string CompanyName{ get; set;}
}
public class SomeClass
{
public ObjectId Id { get; set; }
public string UserName { get; set;}
[BsonRepresentation(BsonType.ObjectId)]
public string CompanyID { get; set; }
public string AreaCode{ get; set; }
public string LotNum{ get; set; }
}
when you retrieve a document from collection and get its ObjectId its a string.
You need to convert that to ObjectId type object.
new ObjectId("string objectid")
This is what we do in nodejs. Please check C# mongodb driver docs
As mentioned by Alex Blex in the comments under my question, it is due to case sensitivity.
Here's his answer:
It's case sensitive. "companyID" != "CompanyID". Basically companyID is undefined in any of your documents so all of them match criteria companyID:null

Inject or Bind "Alias" in an ServiceStack entity

I have 3 tables which contains same set of columns. Do i need to create 3 entities for all the DB tables? Is there a way to avoid creating 3 entities and have only one in ServiceStack?
Yes there is one way of doing it like below
List<EntityA> list = db.SqlList<EntityA>("SELECT COL_A,COL_B FROM TableA");
Entity without Alias on Class
public class EntityA
{
[Alias("COL_A")]
public string ColumnA { get; set; }
[Alias("COL_B")]
public string ColumnB { get; set; }
}
in this way i can change the table name(TableA/TableB/TableC) provided in the Query
but I want something like injecting / passing the alias while retrieving the results from the database. I am not sure if this is possible with service stack
Edited
Let me rephrase the question Instead of returning differenct objects like EntityTableA/EntityTableB/EntityTableC as Result i want
return db.Select<GenericEntity>(w => w.OrderBy(o => o.ColumnA));
the GenericEntity can be any tables result
You can just use inheritance to reduce boilerplate:
public class EntityBase
{
[Alias("COL_A")]
public string ColumnA { get; set; }
[Alias("COL_B")]
public string ColumnB { get; set; }
}
Then inherit properties from the shared entity, e.g:
public class TableA : EntityBase {}
public class TableB : EntityBase {}
Then query it as normal:
var results = db.Select<TableA>(q => ColumnA == "A");
Otherwise yeah the using any of the raw SQL API's will work as well.
Modifying SqlExpression
You can also override the SqlExpression FromExpression to include your own table, e.g:
var q = db.From<GenericEntity>().OrderBy(o => o.ColumnA);
q.From("TableA");
List<GenericEntity> results = db.Select(q);
This will change the SQL to SELECT from TableA instead.

Casting to objects having properties with same names

I have a problem when i try to cast to class Foo having Bar property. Properties of the class Bar have the same names as the properties of the class Foo:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
}
Database schema looks like this:
CREATE TABLE dbo.TB_Foo
(
foo_Id INT PRIMARY KEY
,foo_Name NVARCHAR(20)
)
GO
CREATE TABLE dbo.TB_Bar
(
bar_Id INT PRIMARY KEY
,bar_Name NVARCHAR(20)
,bar_FooId INT
,FOREIGN KEY(bar_FooId) REFERENCES dbo.TB_Foo(foo_Id)
)
GO
Sample data:
INSERT INTO dbo.TB_Foo(foo_Id, foo_Name)
VALUES (1, 'Foo1'), (2, 'Foo2'), (3, 'Foo3')
INSERT INTO dbo.TB_Bar(bar_Id, bar_Name, bar_FooId)
VALUES (1, 'Bar1', 1), (2, 'Bar2', 2), (3, 'Bar3', 3)
When i try to use Simple.Data casting to object i get exception:
"System.ArgumentException: An item with the same key has already been adde"
dynamic barAlias;
List<Foo> list = db.TB_Foo
.All()
.With(db.TB_Foo.TB_Bar.As("Bar"), out barAlias)
.Select(
// Columns for properties of class Foo
db.TB_Foo.foo_Id.As("Id"),
db.TB_Foo.foo_Name.As("Name"),
// Columns for properties of class Bar
barAlias.bar_Id.As("Id"),
barAlias.bar_Name.As("Name")
)
.ToList<Foo>();
Is there a way to achieve this? (sorry for my bad english).
The problem here is that Simple.Data uses its own internal aliases to handle the With clause. That's something I can take a look at in the next iteration, but the obvious answer for now is to rename your columns so they're not prefixed with the name of the table they're in. If the table columns have the same name as the properties, then you don't need the alias and everything will work.
What's the thinking behind the column prefixes anyway?

Code first, working with relationships, seeding

I would like to simply create a relationship between two entities that already exists..
In my case I have an Album entity which might have multiple Genres(which also is a bunch of entities)
My Album model looks like this:
public class AlbumModel : IModel
{
public List<GenreModel> Genres { get; set; }
public ArtistModel Artist { get; set; }
public Guid Id { get; set; }
public string Title { get; set; }
public float Price { get; set; }
public string ArtUrl { get; set; }
}
I then want to create a relation ship between a Album model-object and a bunch of GenreModel-objects/entities..
In the "normal" entity framework I would just assign a new EntityKey.. but Im not quite sure of how to do this using code first..
I also noticed that some people has an extra property called xxxxId for the entity they want to create a reference between.. and then simply assign a value to the xxxxId property which some how magically creates the reference between the entities.. and I guess this works fine for a one to one or a one to many relationship.. but I guess that doesnt work for a many to many relationship..or?
Anyway.. this is my GenresModel:
public class GenreModel : IModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<AlbumModel> Albums { get; set; }
}
And this is what I tried before.. but this wont create the relationship..it will only simply create an additional entity/row in my database..
var artistRepository = new ArtistRepository();
var genresRepository = new GenreRepository();
#region Lordi
context.Albums.Add
(
new AlbumModel
{
Artist = artistRepository.GetByName("Lordi"),
ArtUrl = "http://upload.wikimedia.org/wikipedia/en/8/8f/The_Monster_Show.jpg",
Genres = new List<GenreModel> { genresRepository.GetByName("Rock"), genresRepository.GetByName("Hard rock") },
Id = Guid.NewGuid(),
Price = 3,
Title = "The Monster Show",
}
);
context.Albums.Add
(
new AlbumModel
{
Artist = artistRepository.GetByName("Lordi"),
ArtUrl = "http://www.nuclearblast.de/static/articles/157/157892.jpg/1000x1000.jpg",
Genres = new List<GenreModel> { genresRepository.GetByName("Rock"), genresRepository.GetByName("Hard rock") },
Id = Guid.NewGuid(),
Price = 10,
Title = "Zombilation - The Greatest Cuts",
}
);
#endregion
...and for the record... no I dont listen to Lordi :).. just dummy data.. and for some wierd spooky reason Lordi was the first band that came in mind..
Thanks in advance!
So basically you're trying to set up a many-to-many relationship in EF. I assume there's a table to link Albums and Genres in the database? If so you need to let EF know about the relationship and the table it's stored as I've explained here. You can find a lot of extra information by googling something like "set up many-to-many in Entity Framework".
On a side note you might also want to change your collection properties to virtual so instead of
public List<GenreModel> Genres { get; set; }
you would have
public virtual ICollection<GenreModel> Genres { get; set; }
This makes it faster by allowing EF to perform lazy loading on those properties.
Specifying those collection properties as you have done will automatically create a many-to-many linking table called GenreModelAlbumModels.
However, I suspect that your repository is preventing EF from knowing that there is a relationship. If you're not loading the GenreModel entities in the same data context, it may not know that there is a link between them. As an example, this code works:
using (var context = new DataContext())
{
#region Lordi
context.Albums.Add
(
new AlbumModel
{
ArtUrl = "http://upload.wikimedia.org/wikipedia/en/8/8f/The_Monster_Show.jpg",
Genres = new List<GenreModel> { context.Genres.First(i => i.Name == "Rock"), context.Genres.First(i => i.Name == "Hard Rock") },
Id = Guid.NewGuid(),
Price = 3,
Title = "The Monster Show",
}
);
context.SaveChanges();
#endregion
}
The GenreModels are connected to the same context that we're adding the AlbumModels to, so EF knows that there is a relationship between the entities.
To make sure it's working properly, look for that linking table in your DB. It will be hidden from you in the code.

Resources