i have a rather difficult mapping problem.
EDIT: reformulated descritpion
for historical reasons texts are not stored in a column as text, instead they are saved in tables. i have several tables with following structure:
TABLE SomeEntityTexts
(
id serial NOT NULL,
key character varying(10), // \
linenumber integer, // / unique constraint
type smallint,
length smallint, // actual length of content string
content character varying(80),
PRIMARY KEY (id)
)
text is saved as lines with arbitrary length, different für each entity and sometimes even for different texttypes in one table.
i would like to map them to classes which handle these quirks inside and in mappings.
my solution so far:
a hidden collection and a dummy object which should be readonly. For loading there are always valid Text-objects because persisting the inner collection creates them.
internal class Textline
{
public virtual int Id { get; set; }
public virtual TextType Type { get; set; } // Enum
public virtual int Linenumber { get; set; }
public virtual string Text { get; set; }
}
public class Textmodule
{
public virtual int Id { get; set; }
public virtual string Key { get; set; } // Unique
public virtual TextType Type { get; set; } // Enum
protected internal virtual IList<Textline> Textlines { get; set; }
public virtual string Text
{
get { Textlines.select(t => t.Text).Aggregate(/* ...*/); }
set { /* split text to lines with max 80 chars and feed to Textlines*/}
}
}
public TextmoduleMap()
{
Table("textmodules");
ReadOnly(); // problem: shouldnt insert and update at all, but does insert
Where("linenumber = 1"); // starts with 1
// doesnt matter because it shouldnt be saved
Id(text => text.Id, "id").GeneratedBy.Custom<SimpleGenerator>();
Map(text => text.Key);
HasMany(text => text.Textzeilen)
.Table("textmodules")
.PropertyRef("Key")
.KeyColumn("key")
.Component(c =>
{
c.Map(line => line.Text)
.Columns.Add("content", "length")
.CustomType<StringWithLengthUserType>();
c.Map(line => line.Linenumber, "linenumber");
})
.Cascade.AllDeleteOrphan()
.Not.LazyLoad();
;
}
My problem is, that Readonly doesnt prevent nhibernate from inserting it on save. Is there anything i can do to get it work or does someone has a better idea for a more sane domain object?
Edit2: I fiddled with SQLInsert("SELECT 1"); but i get exception "unexpected rowcount -1, expect 1"
thanks for your time
i found a rather ugly way which is probably not very portable
public TextmoduleMap()
{
...
ReadOnly();
SqlInsert("DROP TABLE IF EXISTS temp; CREATE TEMP TABLE temp(id int); INSERT INTO temp (id) VALUES (1);");
SqlDelete("DROP TABLE IF EXISTS temp; CREATE TEMP TABLE temp(id int); INSERT INTO temp (id) VALUES (1);");
...
}
Better ways are still welcome
Related
I would like to know if someone knows how to make a table with a primary key composed of two columns, where the first column is sent by me, and the second is generated from the first
public class Person
{
public int idPerson { get; set; }
public string name { get; set; }
}
public class PersonAdress
{
public int idPerson { get; set; }
public int DireccionId { get; set; }
public string reference { get; set; }
}
I am looking for the incremental of the second column to be if the first column changes
how to make a table with a primary key composed of two columns
You can add the following code by fluent api in dbContext's OnModelCreating method :
modelBuilder.Entity<PersonAdress>().HasKey(sc => new { sc.idPerson , sc.DireccionId });
You can also have a reference for this.
It's me again, the guy working with SQLite-net. I had my code working when I did not have AutoIncrement on my Primary Keys of the tables. I wanted to AutoIncrement the keys so I reconstructed the Tables like this:
using SQLite;
namespace VehicleTracks.Models
{
public class vehicles
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string VehID { get; set; }
public string VehYear { get; set; }
public string VehMake { get; set; }
public string VehModel { get; set; }
public string VehColor { get; set; }
public string EngineID { get; set; }
public System.DateTime PurchaseDate { get; set; }
public string SellerName { get; set; }
public string SellerStreet { get; set; }
public string SellerCityStateZip { get; set; }
public string VehOptions { get; set; }
public string OdomInitial { get; set; }
public string VehBodyStyle { get; set; }
public float PurchaseCost { get; set; }
public byte[] VehPhoto { get; set; }
public string VehType { get; set; }
public string Sold { get; set; }
}
}
Now when an attempt is made to create the Tables, I get "Syntax Error near AutoIncrement". I tried leaving off AutoIncrement and it does not appear to increment the ID without it.
I'm probably missing something stupid.
Nothing stupid about your code; matches the code samples on https://github.com/praeclarum/sqlite-net alright. But apparently the code samples are wrong, considering this similar problem:
Android table creation Failure (near "autoincrement": syntax error)?
The problem was solved by removing AutoIncrement. Or to quote http://www.sqlite.org/faq.html#q1 :
Short answer: A column declared INTEGER PRIMARY KEY will autoincrement.
(Please double-check whether column ID actually has type INTEGER PRIMARY KEY once the table has been created.)
Longer answer: If you declare a column of a table to be INTEGER PRIMARY KEY, then whenever you insert a NULL into that column of the table, the NULL is automatically converted into an integer which is one greater than the largest value of that column over all other rows in the table, or 1 if the table is empty.
Make sure your INSERT statements do not contain an explicit value (other than NULL) for column ID, otherwise the column will not auto-increment. If that is not possible in SQLite-net (you may need a debugger here), then that may well be a bug. Though it would be surprising that nobody else has ever ran into this.
Maybe you need to make property ID nullable (i.e. use type int?; yes, with the question mark). Mind you, I'm only guessing here; you may need to experiment a bit.
sqlite> CREATE TABLE app (a INTEGER PRIMARY KEY NOT NULL, B VARCHAR);
sqlite> insert into app (b) values ('');
sqlite> insert into app (b) values ('a');
sqlite>
sqlite> insert into app (b) values ('b');
sqlite> insert into app (b) values ('c');
sqlite> insert into app (b) values ('d');
sqlite> select * from app;
1|
2|a
3|b
4|c
5|d
sqlite> exit;
NOTE: IN SQLite AUTOINCREMENT keyword is recommended not to be used. You need to use INTEGER PRIMARY KEY NOT NULL. It will automatically insert the incremented value for this attribute.
I´m new to sqlite-net ( https://github.com/praeclarum/sqlite-net ) and was having issues with an autoincrement PK.
Although I´ve created the table with INTEGER PRIMARY KEY or INTEGER PRIMARY KEY AUTOINCREMENT I got an exception on inserts because the PK was set in 0 in the first row and then I got exception because it tried to insert the same value, so the autoincrement was not working. What I have done is edit the DataAccess.cs and in the table class I´ve replaced the [PrimaryKey] public Int64 id { get; set; } for [PrimaryKey] [AutoIncrement] public Int32 id { get; set; }. I don´t know why it uses bigint (Int64) instead of Int32 as I´ve specified INTEGER in table creation, but now it´s working OK. I can add a new item through LINQ and the autoincrement id (the primary key of the table) increments automatically in each row.
The only examples I can find addressing this sort of scenario are pretty old, and I'm wondering what the best way is to do this with the latest version of ORMLite...
Say I have two tables (simplified):
public class Patient
{
[Alias("PatientId")]
[Autoincrement]
public int Id { get; set; }
public string Name { get; set; }
}
public class Insurance
{
[Alias("InsuranceId")]
[Autoincrement]
public int Id { get; set; }
[ForeignKey(typeof("Patient"))]
public int PatientId { get; set; }
public string Policy { get; set; }
public string Level { get; set; }
}
Patients can have multiple Insurance policies at different "levels" (primary, secondary, etc). I understand the concept of blobbing the insurance information as a Dictionary type object and adding it directly to the [Patient] POCO like this:
public class Patient
{
public Patient() {
this.Insurances = new Dictionary<string, Insurance>(); // "string" would be the Level, could be set as an Enum...
}
[Alias("PatientId")]
[Autoincrement]
public int Id { get; set; }
public string Name { get; set; }
public Dictionary<string, Insurance> Insurances { get; set; }
}
public class Insurance
{
public string Policy { get; set; }
}
...but I need the insurance information to exist in the database as a separate table for use in reporting later.
I know I can join those tables in ORMLite, or create a joined View/Stored Proc in SQL to return the data, but it will obviously return multiple rows for the same Patient.
SELECT Pat.Name, Ins.Policy, Ins.Level
FROM Patient AS Pat JOIN
Insurance AS Ins ON Pat.PatientId = Ins.PatientId
(Result)
"Johnny","ABC123","Primary"
"Johnny","987CBA","Secondary"
How can I map that into a single JSON response object?
I'd like to be able to map a GET request to "/patients/1234" to return a JSON object like:
[{
"PatientId":"1234",
"Name":"Johnny",
"Insurances":[
{"Policy":"ABC123","Level":"Primary"},
{"Policy":"987CBA","Level":"Secondary"}
]
}]
I don't have a lot of hope in this being do-able in a single query. Can it be done in two (one on the Patient table, and a second on the Insurance table)? How would the results of each query be added to the same response object in this nested fashion?
Thanks a ton for any help on this!
Update - 4/29/14
Here's where I'm at...In the "Patient" POCO, I have added the following:
public class Patient
{
[Alias("PatientId")]
[Autoincrement]
public int Id { get; set; }
public string Name { get; set; }
[Ignore]
public List<Insurance> Insurances { get; set; } // ADDED
}
Then, when I want to return a patient with multiple Insurances, I do two queries:
var patientResult = dbConn.Select<Patient>("PatientId = " + request.PatientId);
List<Insurance> insurances = new List<Insurance>();
var insuranceResults = dbConn.Select<Insurance>("PatientId = " + patientResult[0].PatientId);
foreach (patientInsurance pi in insuranceResults)
{
insurances.Add(pi);
}
patientResult[0].Insurances = insurances;
patientResult[0].Message = "Success";
return patientResult;
This works! I get nice JSON with nested items for Insurances while maintaining separate related tables in the db.
What I don't like is that this object cannot be passed back and forth to the database. That is, I can't use the same nested object to automatically insert/update both the Patient and InsurancePolicy tables at the same time. If I remove the "[Ignore]" decorator, I get a field in the Patient table called "Insurances" of type varchar(max). No good, right?
I guess I'm going to need to write some additional code for my PUT/POST methods to extract the "Insurances" node from the JSON, iterate over it, and use each Insurance object to update the database? I'm just hoping I'm not re-inventing the wheel here or doing a ton more work than is necessary.
Comments would still be appreciated! Is Mythz on? :-) Thanks...
An alternate more succinct example:
public void Put(CreatePatient request)
{
var patient = new Patient
{
Name = request.Name,
Insurances = request.Insurances.Map(x =>
new Insurance { Policy = i.Policy, Level = i.Level })
};
db.Save<Patient>(patient, references:true);
}
References are here to save the day!
public class Patient
{
[Alias("PatientId")]
[Autoincrement]
public int Id { get; set; }
public string Name { get; set; }
[Reference]
public List<Insurance> Insurances { get; set; }
}
public class Insurance
{
[Alias("InsuranceId")]
[Autoincrement]
public int Id { get; set; }
[ForeignKey(typeof("Patient"))]
public int PatientId { get; set; }
public string Policy { get; set; }
public string Level { get; set; }
}
I can then take a JSON request with a nested "Insurance" array like this:
{
"Name":"Johnny",
"Insurances":[
{"Policy":"ABC123","Level":"Primary"},
{"Policy":"987CBA","Level":"Secondary"}
]
}
...to create a new record and save it like this:
public bool Put(CreatePatient request)
{
List<Insurance> insurances = new List<Insurance>();
foreach (Insurance i in request.Insurances)
{
insurances.Add(new Insurance
{
Policy = i.Policy,
Level = i.Level
});
}
var patient = new Patient
{
Name = request.Name,
Insurances = insurances
};
db.Save<Patient>(patient, references:true);
return true;
}
Bingo! I get the new Patient record, plus 2 new records in the Insurance table with correct foreign key references back to the PatientId that was just created. This is amazing!
First you should define a foreign collection in Patient class. (with get and set methods)
#ForeignCollectionField
private Collection<Insurance> insurances;
When you query for a patient, you can get its insurances by calling getInsurances method.
To convert all into a single json object with arrays inside you can use a json processor. I use Jackson (https://github.com/FasterXML/jackson) and it works very well. Below will give you json object as a string.
new ObjectMapper().writeValueAsString(patientObject);
To correctly map foreign fields you should define jackson references. In your patient class add a managed reference.
#ForeignCollectionField
#JsonManagedReference("InsurancePatient")
private Collection<Insurance> insurances;
In your insurance class add a back reference.
#JsonBackReference("InsurancePatient")
private Patient patient;
Update:
You can use Jackson to generate objects from json string then iterate and update/create database rows.
objectMapper.readValue(jsonString, Patient.class);
I am using flunet nhibernate and in a many to many relation I need to have key column for the table between these two entities
HasManyToMany(p => p.Signers)
//.Cascade.AllDeleteOrphan()
.Table("PersonnelDocumentSigner")
.Schema("personnel");
public partial class PersonnelDocument
{
private IList<Position> _signers;
virtual public IList<Position> Signers
{
get
{
if (_signers == null)
_signers = new List<Position>();
return _signers;
}
set
{
_signers = value;
}
}
}
the created table just consist of these two columns:PersonnelDocumentId,PositionId
but I need a column Id for this connector table "PersonnelDocumentSigner"
how exactly I can assign it?
link-tables like your PersonnelDocumentSigner normally dont need an Id column because PersonnelDocumentId and PositionId together are the Id/Primary key. If you still want to have additional Data in the linking Table you should Create a new Entity.
class PersonnelDocumentSigner
{
public virtual PersonnelDocument Document { get; set; }
public virtual Position Signer { get; set; }
public virtual int Id { get; set; }
// additional Properties
}
class PersonnelDocumentSignerMap : ClassMap<PersonnelDocumentSigner>
{
Table("PersonnelDocumentSigner");
CompositeId()
.KeyReference(pds => pds.Document)
.KeyReference(pds => pds.Signer);
Map(pds => pds.Id);
// additional Mapps
}
I have to model this relationship in NHibernate (simplified the code a bit to stay on-topic) - an employee can be an accountmanager (so, it's optional):
table Employee (Id, Number, Name)
table EmployeeIsAccountManager (Number, MaxAllowedDiscount)
instead of having an Id foreign key in table EmployeeIsAccountManager pointing to table Employee, I have a Number column in the Employee table which points to the Number column in table EmployeeIsAccountManager.
How do I map this in NHibernate? I've tried using the foreign generator on the EmployeeIsAccountManager class mapping, but if I use Number as foreign generated value, it maps to the ID of Employee, which is Id instead of Number. I modeled my class to use composition:
public class Employee
{
public virtual int Id { get; set; }
public virtual short Number {get; set; }
public virtual string Name {get; set; }
public virtual AccountManager AccountManager { get; set; }
}
public class AccountManager
{
public virtual short Number { get; set; } /*needed because of ID only?*/
public virtual decimal MaxAllowedDiscount { get; set }
}
I've tried a lot (one-to-one, many-to-one, foreign generator) but I can't figure out if this can be done with NHibernate. btw: I can change the classes, mappings, etc but I CANNOT change the table structure because of it's brownfield status (old application with 2+ million lines of code, nearly 1000 forms).
Any help is appreciated, thanks!
Ted
Your question make me think about class inheritance, you could map your class AccountManager as a subclass of Employee and then you will able to do what you want to do, I'v etested a mapping for you but as you designed tables that way does not resolve your needs because there are two points you have to notice in your mapping:
The number property in the Employee table should be a unique kind of foreing key to AccountManager in order to be used as a foreign key but even so it does'mt work because NHibernate, when you try to insert a new Account Manager will insert a record in the person table and then assign the id of person to the number column of AccountManager an that break you needs.
Mapping that relation as many-to-one doesn't work for the same reason. The Number property of AccountManager is primary key? is unique? NHibernate cannot work without primary keys, so in order to make that relation to work you have to specify the Number propety of AccountManager as an Id column
The last option which comes in my mind is to use a property in the Employee class mapped
to the AccountManager table with a formula where you can specify a custom select to obtain the value you need I assume the property MaxAllowedDiscount, but this too has some limitation, when you map a property with formula, this property cannot be inserted nor updated.
Hope this helps
lat me konw if there are questions.
public class Employee
{
public virtual short Number
{
get { return (AccountManager == null) ? 0 : AccountManager.Number; }
set
{
if (AccountManager == null)
AccountManager = new AccountManager();
AccountManager.Number = value;
}
}
public virtual AccountManager AccountManager { get; set; }
}
or with GeneratedBy.Assinged()
public class Employee
{
public Employee()
{
AccountManager = new AccountManager();
}
public virtual AccountManager AccountManager
{
get;
set { value.Parent = this; _accountManager = value; }
}
public class AccountManager
{
Internal protected virtual Employee Parent { get; set; }
protected virtual short Number { get { return Parent.Number; } set { } } /*needed because of ID only?*/
public virtual decimal MaxAllowedDiscount { get; set }
}
}