How to LeftJoin to the same table twice using ServiceStack OrmLite? - servicestack

I have table structures that look like below:
table Tenant: Id[PK], etc
table Contact: Id[PK], FirstName, LastName etc
table Sale: Id[PK], TenantId[FK], SellerId[FK], BuyerId[FK], etc
SellerId is a FK to Contact.Id
BuyerId is a FK to Contact.Id
TenantId is a FK to Tenant.Id
I want to use OrmLite to generate SQL similar to below:
select sale.*
, buyer.FirstName 'BuyerFirstName'
, buyer.LastName 'BuyerLastName'
, seller.FirstName 'SellerFirstName'
, seller.LastName 'SellerLastName'
from sale
left join
contact seller
on sale.SellerId = seller.Id
left join
contact buyer
on sale.BuyerId = buyer.Id
where tenantId = 'guid' -- this is being filtered at a global level
Because I want to have a strongly typed global filter to filter out result by tenantId (on database side) I have code looks like below
public List<TOut> Exec<TIn, TOut>(SqlExpression<TIn> exp) where TIn : IHaveTenantId
{
exp.Where(x => x.TenantId == _tenantId);
return _conn.Select<TOut>(exp);
}
The poco of Sale looks like below:
public class Sale : IHaveTenantId
{
public Guid Id { get; set; }
[ForeignKey(typeof(Contact), OnDelete = "CASCADE")]
public Guid BuyerId { get; set; }
[ForeignKey(typeof(Contact), OnDelete = "CASCADE")]
public Guid SellerId { get; set; }
//etc
}
And I'm trying to use strongly typed LeftJoin syntax like below:
public class SaleView
{
public Guid Id { get; set; }
public string BuyerFirstName { get; set; }
public string SellerLastName { get; set; }
//etc
}
var result = Exec<SaleView, Sale>(_conn
.From<Sale>()
.LeftJoin<Contact>((sale, seller) => sale.SellerId == seller.Id)
.LeftJoin<Contact>((sale, buyer) => sale.BuyerId == buyer.Id));
I couldn't figure out how to join the same table multiple times and have an alias per join (e.g. left join contact as 'seller', hence I can select seller.FirstName, buyer.FirstName) and I don't want to use parameterised raw sql.
Is this possible at all with OrmLite?

Support for typed JOIN aliases were added in v4.0.62, e.g:
var q = db.From<Sale>()
.LeftJoin<ContactIssue>((s,c) => s.SellerId == c.Id, db.JoinAlias("seller"))
.LeftJoin<ContactIssue>((s,c) => s.BuyerId == c.Id, db.JoinAlias("buyer"))
.Select<Sale, ContactIssue>((s,c) => new {
s,
BuyerFirstName = Sql.JoinAlias(c.FirstName, "buyer"),
BuyerLastName = Sql.JoinAlias(c.LastName, "buyer"),
SellerFirstName = Sql.JoinAlias(c.FirstName, "seller"),
SellerLastName = Sql.JoinAlias(c.LastName, "seller"),
});
Prior to v4.0.62 you can continue to use a Typed SqlExpression with custom SQL for this, e.g:
var q = db.From<Sale>()
.CustomJoin("LEFT JOIN Contact seller ON (Sale.SellerId = seller.Id)")
.CustomJoin("LEFT JOIN Contact buyer ON (Sale.BuyerId = buyer.Id)")
.Select(#"Sale.*
, buyer.FirstName AS BuyerFirstName
, buyer.LastName AS BuyerLastName
, seller.FirstName AS SellerFirstName
, seller.LastName AS SellerLastName");
The benefit of which is that it still leaves a Typed API where you can add additional filters like a global TenantId filter, e.g:
q.Where(x => x.TenantId == tenantId);
And then project it into your Custom POCO with:
var sales = db.Select<SaleView>(q);
The new CustomJoin API is available from v4.0.37+ that's now available on MyGet.

Related

"Traditional" one-to-many Query with RavenDB

I know the include-feature of RavenDB. It allows me to fetch a referenced document right away in one roundtrip to the database. But my problem is: The document i fetch in the first place is not including a reference to the "other" documents. But the "other" documents have references to the current document.
Imagine a setup where we have sites across the world. Each site may trigger various alarms. Each alarm has a reference to the site via siteId.
Now i would like to get a list of all the sites including all alarms. But it looks like, this is not possible with RavenDB? Since include only accepts a "path" in the site-Document which holds an id (or an array of ids) to the referenced document.
This could be solved by providing an array of alarmIds within the site'-document and referencing this array in include. But in contrast to a lot of examples featuring stuff like an orderwithlineItemswhere the order is a self contained thing, mysite` will be running for years, collecting alarms anywhere between 0 and a million. Which seems to be a bad idea to me.
Of course i could go the other way round: Query all alarms and include the sites via sitesId. But this would not return a site that has zero alarms.
So is this just a design error on my side? To i misunderstand something? Or is it just not possible to do this in one query and prevent a "n+1 query"?
public class A
{
public string Id { get; set; }
}
public class B
{
public string Id { get; set; }
public string A { get; set; }
}
public class MultiMapIndex : AbstractMultiMapIndexCreationTask<MultiMapIndex.Result>
{
public class Result
{
public string Id { get; set; }
public IEnumerable<string> Bs { get; set; }
}
public MultiMapIndex()
{
AddMap<A>(items => from a in items
select new Result {Id = a.Id, Bs = new string[0]});
AddMap<B>(items => from b in items
select new Result {Id = b.A, Bs = new[] {b.Id}});
Reduce = results => from result in results
group result by result.Id
into g
select new Result {Id = g.Key, Bs = g.SelectMany(r => r.Bs)};
}
}
[Fact]
public async Task TestCase()
{
using var store = GetDocumentStore();
await new MultiMapIndex().ExecuteAsync(store);
using (var session = store.OpenAsyncSession())
{
await session.StoreAsync(new B {A = "a/1"}, "b/0");
await session.StoreAsync(new A(), "a/1");
await session.StoreAsync(new A(), "a/2");
await session.SaveChangesAsync();
}
WaitForIndexing(store);
using (var session = store.OpenAsyncSession())
{
var results = await session.Query<MultiMapIndex.Result, MultiMapIndex>()
.Include(r => r.Bs)
.ToArrayAsync();
var before = session.Advanced.NumberOfRequests;
var bs = session.LoadAsync<B>(results[0].Bs);
Assert.Equal(before, session.Advanced.NumberOfRequests);
}
}
If you do choose to query all Alarms, as you mention,
then you can create a Map-Reduce index on the Alarms collection which will group-by the Sites.
Then you can query this Map-Reduce index and know per Site the count of Alarms it has or doesn't have...
https://demo.ravendb.net/demos/csharp/static-indexes/map-reduce-index

Where to place calculated property of HasChildren for Domain Model?

We have an Department model (domain-driven design). Each department has its child departments, so domain model looks like
public class Department
{
int Id { get; set; }
...
ICollection<Department> Children { get; set; }
}
At the API domain models of the same hierarchy path, coming from repository, it will transforms to DTO trough AutoMapper and does not include children by default.
public class DepartmentDto
{
int Id { get; set; }
...
ICollection<DepartmentDto> Children { get; set; } // Empty set.
}
Does it a good way to add [NotMapped] bool HasChildren property to the Department domain model to show or hide expand arrows at the client? For lazy load.
This field smells strange: can be filled or can be not (depends on query).
Repository returns a collection of departments, belongs to parent Id (may become Null to root nodes):
ICollection<Department> GetDepartments(int? parentId = null);
So, based on Lucian Bargaoanu comments, I've found the solution:
IDepartmentRepository.cs
IQueryable<Department> GetDepartmentsQuery(int? parentId = null);
DepartmentsController.cs (API):
[HttpGet]
public async Task<ActionResult<ICollection<DepartmentDto>>> GetRootDepartments()
{
var dtoItems = await _repository.GetDepartmentsQuery()
.ProjectTo<DepartmentDto>(_mapper.ConfigurationProvider)
.ToListAsync();
return Ok(dtoItems);
}
AutoMapper configuration:
CreateMap<Department, DepartmentDto>()
.ForMember(x => x.HasChildren,
opts => opts.MapFrom(x => x.Children.Any()))
.ForMember(x => x.Children,
opts => opts.Ignore());

ArangoDB update action in .Net

I am a .Net developer and is currently exploring on ArangoDB. I have played around with the arangod web user interface and arangod and like this NoSql very much until I delve into the detail of coding. I could not find the .Net driver working properly. Even for simple CRUD operation. Here's the problem.
ArangoClient.AddConnection("127.0.0.1", 8529, false, "Sample", "Sample");
var db = new ArangoDatabase("Sample");
string collectionName = "MyTestCollection";
var collection = new ArangoCollection();
collection.Name = collectionName;
collection.Type = ArangoCollectionType.Document;
if (db.Collection.Get(collectionName) == null)
{
db.Collection.Create(collection);
}
var employee = new Employee();
employee.Id = "1234";
employee.Name = "My Name";
employee.Salary = 33333;
employee.DateOfBirth = new DateTime(1979, 7, 22);
db.Document.Create<Employee>("MyTestCollection", employee);
employee.Name = "Tan";
db.Document.Update(employee);
It thrown the error for db.Document.Update(employee). Here's the error message: Field '_id' does not exist.
Then I tried to add the field _id though I think it is weird, it prompted me another error message.
Arango.Client.ArangoException : ArangoDB responded with error code BadRequest:
expecting PATCH /_api/document/<document-handle> [error number 400]
at Arango.Client.Protocol.DocumentOperation.Patch(Document document, Boolean waitForSync, String revision)
at Arango.Client.ArangoDocumentOperation.Update[T](T genericObject, Boolean waitForSync, String revision) ...
I have no clues at all and do not know how to to proceed further. Any help will be much appreciated. Thanks.
This is likely due to the definition of the Employee class, which is not contained in the above snippet.
To identify a document in a collection, documents have special system attributes, such as _id, _key and _rev. These attributes should be mapped to properties in .NET classes, even if not used explicitly. So one property in the class should be tagged with "Identity", one with "Key", and one with "Revision". Here is an example class definition that should work:
public class Employee
{
/* this will map the _id attribute from the database to ThisIsId property */
[ArangoProperty(Identity = true)]
public string ThisIsId { get; set; }
/* this will map the _key attribute from the database to the Id property */
[ArangoProperty(Key = true)]
public string Id { get; set; }
/* here is _rev */
[ArangoProperty(Revision = true)]
public string ThisIsRevision { get; set; }
public DateTime DateOfBirth { get; set; }
public string Name { get; set; }
public int Salary { get; set; }
public Employee()
{
}
}
The ThisIsId property will contain the automatically assigned _id value, and can also be used to retrieve the document easily later:
var employeeFromDatabase = db.Document.Get<Employee>(employee.ThisIsId);
You can of course rename the properties to your like.

Map data on partial model using entity framework not working as expected

I have model generated by ADO Entity Data Model like:
public partial class Category
{
public Category()
{
}
public int CategoryId { get; set; }
public string Name { get; set; }
}
now I have added one property
public partial class Category
{
public int EventsCount { get; set; }
}
Now I am trying to map by: //this is working Query
List<Category> retVal = db.Database.SqlQuery<Category>(
//retVal = db.Categories.SqlQuery(
#"SELECT c2.CategoryId,c2.Name,c1.EventsCount AS EventsCount FROM (
SELECT c.CategoryId, COUNT(c.CategoryId) AS EventsCount FROM Category c
JOIN EventCategory ec ON ec.CategoryId = c.CategoryId
JOIN (SELECT * FROM EVENT WHERE EventDateTime > DATEADD(D, 0, DATEDIFF(D, 0, GETDATE()))) e ON e.EventId = ec.EventId
WHERE c.ImportedFrom IS NULL
GROUP BY c.CategoryId) c1
join Category c2 ON c1.CategoryId = c2.CategoryId").ToList();
Actual Data I get executing raw sql on Management Studio
but EventCounts is always 0 with Entity Framework mapping ,
but If I map model with different Model having EventsCount , then it is mapped.
like:
public partial class Category
{
public Category()
{
}
public int CategoryId { get; set; }
public string Name { get; set; }
public int EventsCount { get; set; }
}
now there are mapped all columns,, Any idea here, why Entity framework in not mapping partial model?
I have not got exact answer for this,
But I got solution by inheriting model than making partial class.
As EntityFramework mapp data with help of reflection and reflection cannot deal with property with partial class
check this
.NET reflection: how to get properties defined on partial class
so what I did is:
first make anther class with inheriting class generated by EF, and add property that we want.
public partial class CategoryEx:Category
{
public int EventsCount { get; set; }
}
and definitely,
List<CategoryEx> retVal = db.Database.SqlQuery<Category>(
//retVal = db.Categories.SqlQuery(
#"SELECT c2.CategoryId,c2.Name,c1.EventsCount AS EventsCount FROM (
SELECT c.CategoryId, COUNT(c.CategoryId) AS EventsCount FROM Category c
JOIN EventCategory ec ON ec.CategoryId = c.CategoryId
JOIN (SELECT * FROM EVENT WHERE EventDateTime > DATEADD(D, 0, DATEDIFF(D, 0, GETDATE()))) e ON e.EventId = ec.EventId
WHERE c.ImportedFrom IS NULL
GROUP BY c.CategoryId) c1
join Category c2 ON c1.CategoryId = c2.CategoryId").ToList();
now EventsCount is there.
I have no idea but it may be helpful for somebody.
As far as my interpretation is concerned you are trying to bind more data then it is there in the class. You can bind only event count rather then trying to bind c2.CategoryId, c2.Name using select query.
I am not sure but i think that this should work.
I came across the same issue . It seems to be a bug in EF. For me the solution was the excellent Dapper library. It's just a bunch of extension methods so it can work side by side with EF.

Order the Columns, while creating DB by Entity Framework 4.1. with fluent API

I want have an order of my database table columns after creating DB by entity framework 4.1. in the following way:
1) PK
2) all foreign key columns
3) all complex types columns
4) all Other Columns
The problem is that it is no possibility to set the Order for foreign key's by fluent API, like for example HasColumnOrder for primitive properties.(all foreign key columns are the last columns)
Are there some ideas?
Thanks
Chris
I know this is an old thread, but I thought I would actually answer it. I agree, the column order in the DB makes no difference. Except for when you are in the DB searching on data, then it can be useful to have the logical/useful fields first.
The answer is with the .HasColumnOrder(x) method. Just put in a zero-based number of what order you want the field to be in. See an example here: http://hanksnh.wordpress.com/2011/04/08/inheritance-with-entity-framework-4-1/
If you want to set order for foreign key columns you must expose them as properties (use foreign key association instead of independent association). Btw. order of columns in database table doesn't matter. Only order of columns in a key matters and it is why there is no brother support for this.
I have solved the problem with foreign key columns order in the database while using independent association with the help of EF migrations.
Disable automatic migrations and create initial migration for your data model.
For model class:
public class Session
{
public int? Id { get; set; }
public Track Track { get; set; }
public Car Car { get; set; }
public int Event { get; set; }
public DateTime Date { get; set; }
public string Name { get; set; }
}
the migration code will be generated:
CreateTable("dbo.Sessions", c => new
{
Id = c.Int(nullable: false, identity: true),
Event = c.Int(nullable: false),
Date = c.DateTime(nullable: false),
Name = c.String(nullable: false, maxLength: 64),
Car = c.Int(nullable: false),
Track = c.Int(nullable: false),
}) ...
Now just reorder the foreign key columns (Car, Track) by moving them up. When you create new database and open the table, the column order will be like expected.
If you are looking for column order, I think its pretty easy. In your DbContext class, override OnModelCreating. Then grab modelBuilder, and from it pull out EntityTypeConfiguration. Then using it configure the order as follows.
public class AppDbContext : IdentityDbContext<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppDbContext() : base("AvbhHis")
{
}
public DbSet<PatientCategory> Product { get; set; }
public DbSet<LookupBase> LookupBase { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<PatientCategoryLookup>()
.Map<PatientCategoryLookup>(m =>
{
m.ToTable("LookupPatientCategory");
m.MapInheritedProperties();
});
EntityTypeConfiguration<PatientCategoryLookup> config = modelBuilder.Entity<PatientCategoryLookup>();
config.Property(e => e.Id).HasColumnOrder(0);
config.Property(e => e.PatientCatgCode).HasColumnOrder(1);
config.Property(e => e.PatientCatgName).HasColumnOrder(2);
config.Property(e => e.Description).HasColumnOrder(3);
config.Property(e => e.ModifiedTime).HasColumnOrder(4);
config.Property(e => e.History).HasColumnOrder(5);
base.OnModelCreating(modelBuilder);
}
}
And then finally you need to add migration and then update database.

Resources