I have the following class:
public class ProcessInstance
{
[AutoIncrement]
public int Id { get; set; }
[Reference]
public ProcessDefinition ProcessDefinition { get; set; }
public int ProcessDefinitionId { get; set; }
// and more...
}
Then running the following, which looks fine to me:
var q = db.From<ProcessInstance>().Where(inst => inst.ProcessDefinition.Id == id
&& Sql.In(inst.Status, enProcessStatus.READY, enProcessStatus.ACTIVE));
return db.Exists(q);
When I inspect the last command text SQL from the "db" object, it's wrong:
SELECT 'exists'
FROM "ProcessInstance"
WHERE (("Id" = #0) AND "Status" IN (#1,#2))
LIMIT 1
Note that it's filtering on Id instead of ProcessDefinition.Id, which of course is wrong. Don't know why it's doing that -- at least I'd appreciate getting an error instead of just a wrong result.
However, I've found how to fix it: Use ProcessDefinitionId: Where(inst => inst.ProcessDefinitionId == id gives the correct SLQ:
SELECT 'exists'
FROM "ProcessInstance"
WHERE (("ProcessDefinitionId" = #0) AND "Status" IN (#1,#2))
LIMIT 1
Why didn't the first one work? Why is there no error?
OrmLite is designed for providing a typed api around an SQL Expression so that it should be intuitive to determine the SQL generated from a typed Expression. It doesn’t support magic behavior such as querying any nested objects as attempted with the reference complex type property, I.e. you can only query direct column properties as done in your 2nd query.
Related
I have a table called PODetail with a primary Key of POno and ItemCode and I have the following:
[Route("/podetail/{POno}/{ItemCode}")]
public class UpdatePODetail : IReturn<PODetail> {
public string POno { get; set; }
public string ItemCode { get; set; }
public int ? QtyPend { get; set; }
public decimal ? NewPrice { get; set; }
public bool ? BackOrder { get; set; }
public string ActionCode { get; set; }
public bool ? OpenOrder { get; set; }
}
public class PODetailService : Service {
public object Any(UpdatePODetail request) {
var podetail = Db.SingleFmt<PODetail>("ItemCode = {0} AND POno = {1}", request.ItemCode, request.POno);
// var cap = new CaptureSqlFilter();
try {
Db.Update(podetail);
} catch {
// var sql = string.Join(";\n\n", cap.SqlStatements.ToArray());
}
:
:
try {
Db.Update(podetail);
} catch (Exception ex) {
string error = ex.Message;
}
return podetail;
}
}
I added the Db.Update call at the top just to check to see if there was some issue changing a column, but I get
Violation of PRIMARY KEY constraint 'aaaaaPoDetail_PK'. Cannot insert
duplicate key in object 'dbo.PODetail'.
So then I added the cap = line to see the SQL code which returns
UPDATE "PODetail" SET "NewItemCode"=#NewItemCode, "POno"=#POno, "Vendor"=#Vendor, "ActionCode"=#ActionCode, "Price"=#Price, "NewPrice"=#NewPrice, "CostPrice"=#CostPrice, "QtyOrd"=#QtyOrd, "QtyRcv"=#QtyRcv, "QtySPO"=#QtySPO, "QtyPend"=#QtyPend, "BackOrder"=#BackOrder, "OpenOrder"=#OpenOrder, "OrderDate"=#OrderDate, "InvoiceNo"=#InvoiceNo, "InvoiceVendor"=#InvoiceVendor, "InvoiceDate"=#InvoiceDate, "InvoiceDiscount"=#InvoiceDiscount, "QtyCancel"=#QtyCancel, "Qtylabels"=#Qtylabels, "REOVendor"=#REOVendor, "CurrentRcvQty"=#CurrentRcvQty, "SOPickQty"=#SOPickQty, "SOItem"=#SOItem, "QtyOther"=#QtyOther, "BackOrderCode"=#BackOrderCode WHERE "ItemCode"=#ItemCode
And then it runs fine uncommented -- no exceptions .. if I remove it it gets the Primary Key error
What is the deal -- why do I need that CaptureSqlFilter call -- or what I do I need to change so that it knows both PoNo and ItemCode are primary Keys or the update needs to say WHERE "ItemCode"=#ItemCode AND "POno"=#PONo? It almost seems as if it is trying to do an INSERT vs an UPDATE without the CaptureSqlFilter
Update 1
The documentation said :
Limitations For simplicity, and to be able to have the same POCO class
persisted in db4o, memcached, redis or on the filesystem (i.e.
providers included in ServiceStack), each model must have a single
primary key, by convention OrmLite expects it to be Id although you
use [Alias("DbFieldName")] attribute it map it to a column with a
different name or use the [PrimaryKey] attribute to tell OrmLite to
use a different property for the primary key.
You can still SELECT from these tables, you will just be unable to
make use of APIs that rely on it, e.g. Update or Delete where the
filter is implied (i.e. not specified), all the APIs that end with
ById, etc.
Workaround single Primary Key limitation
A potential workaround to support tables with multiple primary keys is
to create an auto generated Id property that returns a unique value
based on all the primary key fields,
So I tried to add this
public class PODetail {
public string Id { get { return this.ItemCode + "/" + this.POno; } }
public string ItemCode { get; set; }
public string NewItemCode { get; set; }
public string POno { get; set; }
:
}
But when it went to execute :
Db.SingleFmt<PODetail>
It error out with ID not a valid column or column not found or something like that
So I then tried
public class PODetail {
//public string Id { get { return this.ItemCode + "/" + this.POno; } }
[PrimaryKey]
public string ItemCode { get; set; }
public string NewItemCode { get; set; }
[PrimaryKey]
public string POno { get; set; }
:
}
and it worked on the Db.SingleFmt ... and the Db.Update
So then I added back in the CaptureSqlFilter to see what the query looked like and I got
UPDATE "PODetail" SET "NewItemCode"=#NewItemCode, "Vendor"=#Vendor, "ActionCode"=#ActionCode, "Price"=#Price, "NewPrice"=#NewPrice, "CostPrice"=#CostPrice, "QtyOrd"=#QtyOrd, "QtyRcv"=#QtyRcv, "QtySPO"=#QtySPO, "QtyPend"=#QtyPend, "BackOrder"=#BackOrder, "OpenOrder"=#OpenOrder, "OrderDate"=#OrderDate, "InvoiceNo"=#InvoiceNo, "InvoiceVendor"=#InvoiceVendor, "InvoiceDate"=#InvoiceDate, "InvoiceDiscount"=#InvoiceDiscount, "QtyCancel"=#QtyCancel, "Qtylabels"=#Qtylabels, "REOVendor"=#REOVendor, "CurrentRcvQty"=#CurrentRcvQty, "SOPickQty"=#SOPickQty, "SOItem"=#SOItem, "QtyOther"=#QtyOther, "BackOrderCode"=#BackOrderCode WHERE "ItemCode"=#ItemCode AND "POno"=#POno
Which is what I wanted in the first place.
It works but what is the deal can you have the [PrimaryKey] attribute multiple times (it appears so) and also then why didn't the autogenerated Id work? Just wondering if I am missing something or not understanding the documentation correctly.
Oh and sorry for posting in the comments!
what I do I need to change so that it knows both PoNo and ItemCode are
primary Keys
OrmLite's primary limitation is that each Table has a single primary Key.
Also you can use the built-in Profiling or debug logging to view the generated SQL without needing to change code to use CaptureSqlFilter.
I'd also recommend that you don't use the Request DTO for anything other than defining your Service with. You can use the built-in AutoMapping to easily use it to populate your data model.
It seems sorting in Dapper Extensions can be achieved with Predicates:
Predicates.Sort<Person>(p => p.LastName)
My question is, how do I implement random sorting (i.e. RAND() in sql) to predicates?
Predicates.Sort actually produces an ISort-compatible interface which is defined as follows:
public interface ISort
{
string PropertyName { get; set; }
bool Ascending { get; set; }
}
It looks like we have a chance of setting property name to "RAND()" or something, right?... But, sadly, this interface is used in this way:
if (sort != null && sort.Any())
{
sql.Append(" ORDER BY ")
.Append(sort.Select(s => GetColumnName(classMap, s.PropertyName, false) + (s.Ascending ? " ASC" : " DESC")).AppendStrings());
}
So Dapper Extensions in fact check that the passed name is a column name. And the GetColumnName is defined as follows:
public virtual string GetColumnName(IClassMapper map, string propertyName, bool includeAlias)
{
IPropertyMap propertyMap = map.Properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase));
if (propertyMap == null)
{
throw new ArgumentException(string.Format("Could not find '{0}' in Mapping.", propertyName));
}
return GetColumnName(map, propertyMap, includeAlias);
}
Therefore, the string actually should be not a column name, but a property name (they have named the field in ISort interface for a purpose, right?).
So, to implement something like the thing you want you need to customize their SQL generator.
For further details, the best way is to refer to sources:
Predicates: https://github.com/tmsmith/Dapper-Extensions/blob/master/DapperExtensions/Predicates.cs
SqlGenerator: https://github.com/tmsmith/Dapper-Extensions/blob/master/DapperExtensions/Sql/SqlGenerator.cs
PS: I am unable to post link to the repo root due to beginner's rep, hope you can guess it ;)
PPS: The same is true for whole predicates system in Dapper Extensions. I believe it should be greatly refactored to allow more than plain-forward column-based restrictions.
What I'm trying to achieve here is to save the current user instance in my ApiConfigurationRecord table. I already dig around the internet, and most of the example is using UserPartRecord. But the troble I encounter is to get the UserPartRecord object itself.
This is my Entity class look like:
public class ApiConfigurationRecord
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual UserPartRecord RegisterBy { get; set; }
}
This is my Migration.cs code look like:
public int Create()
{
SchemaBuilder.CreateTable("ApiConfigurationRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("RegisterBy_id")
.Column<string>("Name", column => column.NotNull())
);
return 1;
}
This is my Action Controller codes:
public ActionResult Test()
{
var userId = this._orchardServices.WorkContext.CurrentUser.Id;
// below code got error: The non-generic method IContentManager.Query() cannot be used with type arguments
this._orchardServices.ContentManager.Query<UserPart, UserPartRecord>().Where(u => u.Id == userId);
return null;
}
For hours I stuck in this problem. Need to know how to save this User relationship object, and most importantly, get the object itself. Please guide me.
Or you could just do
_orchardServices.WorkContext.CurrentUser.As<UserPart>().Record;
Though you will probably want to check user is not null there too. And as Bertrand Le Roy says, you will also need
using Orchard.ContentManagement;
to make use of the .As extension method.
My super-powers tell me that you are missing the following on top of your controller file:
using Orchard.ContentManagement;
The generic version of the Query method is an extension method that is in this namespace.
here are my entities:
public abstract class ResourceBase
{
[Key]
int Id { get; set; }
[ForeignKey("Resource")]
public Guid ResourceId { get; set; }
public virtual Resource Resource { get; set; }
}
public class Resource
{
[Key]
public Guid Id { get; set; }
public string Type { get; set; }
}
public class Message : ResourceBase
{
[MaxLength(300)]
public string Text { get; set; }
}
And then my query is something like this:
var msgs = messages.Where(x=>x.Id == someRangeOfIds).Include(m=>m.Resource).Select(x => new
{
message = x,
replyCount = msgs.Count(msg => msg.Id = magicNumber)
});
I am running this with proxy creation disabled, and the result is all the messages BUT with all the Resource properties as NULL. I checked the database and the Resources with matching Guids are there.
I drastically simplified my real life scenario for illustration purposes, but I think you'll find you can reproduce the issue with just this.
Entity Framework 5 handles inherited properties well (by flattening the inheritence tree and including all the properties as columns for the entity table).
The reason this query didn't work was due to the projection after the include. Unfortunately, the include statement only really works when you are returning entities. Although, I did see mention of a solution which is tricky and involves invoking the "include" after the shape of the return data is specified... If anyone has more information on this please reply.
The solution I came up with was to just rephrase the query so I get all messages in one query, and then in another trip to the database another query that gets all the reply counts.
2 round trips when it really should only be 1.
I'm using Fluent NHibernate (1.2.0.712) and Nhibernate.Linq (version 1) with SQLite (1.0.79)
Here is my model:
public class QueueItem
{
public virtual long ID { get; set; }
public virtual DateTime AddedToQueue { get; set; }
public virtual DateTime DontProcessUntil { get; set; }
public virtual DataQueueItemState State { get; set; }
}
Note that the ID is a long.
I also have this bit of LINQ:
var nextID =
from i in _repository
where i.State == DataQueueItemState.GetDataQueue && i.DontProcessUntil < DateTime.UtcNow
group i by i.State into g
select new { ID = g.Min(i => i.ID) };
_repository is a data layer repository implementing IQueryable.
This query works fine. However, when I looked at the generated SQL, I saw this:
NHibernate: select cast(min(queueitem0_.ID) as INTEGER) as col_0_0_ from "QueueItem"
queueitem0_ where queueitem0_.State=#p0 and queueitem0_.DontProcessUntil<#p1 group by
queueitem0_.State;#p0 = 'GetDataQueue' [Type: String (0)], #p1 = 28/03/2012 08:21:10
[Type: DateTime (0)]
The question is; why is the ID getting cast to an INTEGER?
In fact, why is it casting at all?
On the code side, the g.Min(i => i.ID) knows that it is returning a long.
A new anonymous type is being generated to hold the result and if I do a .elementAt(0).ID on it then it gives me a long as well so that all seems fine.
You are seeing the conversion because long is not a sql data type. I understand that your SQLite columns can be typeless with exception to the ID but NHibernate converts .NET data types to their sql equivalent. I would suggest using Int64 instead of long just to be safe but this is expected behavior.