cokMedlem sedan dec. 2005664 inläggJo, givetvis så binder man upp sig på ett eller annat sätt om man vill använda någon slags mapper. Men visst skulle man lika väl kunna ha attribut som säger vilket fällt i klassen som är identifier och sedan låta fältet ha den typ som man brukar ha (int, guid etc.). Sedan i mappern hantera detta.
En annan fråga, varför bör man ha mappningsinformationen i filer istället som attribut?
GGladhMedlem sedan maj 20012 812 inlägg
cok skrev:
Men visst skulle man lika väl kunna ha attribut som säger vilket fällt i klassen som är identifier och sedan låta fältet ha den typ som man brukar ha (int, guid etc.). Sedan i mappern hantera detta.
Visst kan du det, det brukar nog vara det vanligast också, men jag valde att göra en egen klass, lite bara för att det var "häftigt" att testa på och se hur det hela skulle fungera. Det är ju knappast en fullblodig entitymapper som jag gjort, utan mer någon för att leka och lära....
cok skrev:
En annan fråga, varför bör man ha mappningsinformationen i filer istället som attribut?
Säg att du har ett fält i din databas som heter L_Name och du i din class har satt attributet [DataField("L_Name")] på en variabel. Om du nu av någon anledning vill ändra namnet i din databas kolumn till LastName, så betyder det att du måste in i ditt projekt och ändra och sedan kompilera om. Och kompilera om är något som man helst inte vill göra.
Säg att du har lagt en version 1.0 i produktion, och håller på med version 2.0, du har nu gjort en massa förändringar i din kod och helt plötsligt så ändra DB:an namnet i databasen från L_Name till LastName, då skall du in och kompilera om din kod, men måste se till så inga förändringar slår igenom. Du har alltså 2 vägar att lösa det på, vilket båda är mindre bra:
1. Skapa en vy i databasen där du skickar tillbaka fältet som L_Name, fast det heter LastName i kolumnen.
2. Plocka fram din version 1.0 från din sourcesafe och hoppas på att allt är korrekt markerat så du får ut exkat rätt version, och där efter ändra till LastName och kompilera om och lägga ner filen i produktion. Och sedan göra samma förändring i din version 2.0.
Jag skulle nog råda dig till alternativ 1, men oftast slutar det med en massa olika vyer och nödlösningar. Alternativ 2 är helst något man inte gör, där jag jobbade innan så hade man produktionssättning 2 gånger om året. Under övrig tid så fick man inte lägga något i produktion om det inte var en ren buggrättning som var allvarlig. Så allt som man kan lösa utan att behöva kompilera och lägga i produktion är att fördra, så konfigfiler är perfekta i det sammahanget. Vi hade till och med gjort en speciell settingsdatabas som vi mappade mot VS2005 settings funktion. Där värden lades i en databas som ändrades från ett webinterface under kontrollerade former. Väldigt smidigt och man slapp ner på serverns xml-filer för att ändra något till applikationen.
- M
GGladhMedlem sedan maj 20012 812 inlägg Så då fortsätter vi med DELETE. Vi vill helt enkelt ta bort en rad från databasen som motsvara vårt objekt.
Nu har vi alla attribute klara och även vår Userklass har allt den behöver så vi skall bara skapa några nya metoder i vår EntityMapper.
public void Delete<ItemType>(ItemType itemType)
{
IDbCommand command = null;
try
{
//-- Create information objects
DataSourceInformation dataSourceInformation = CreateDataSourceInformation<ItemType>();
DataIdentifierInformation dataIdentifierInformation = CreateDataIdentifierInformation<ItemType>(itemType, null);
//-- Create Command
command = CreateDeleteCommand(dataIdentifierInformation, dataSourceInformation);
int affectedRow = //Här anropar du ditt DAL med command objektet och exekverar det med metoden ExecuteNonQuery() av prestandaskäl... ;)
}
finally
{
if (command != null)
command.Dispose();
}
}
Här återanvänder vi ju lite gamla metoder så det enda som är nytt här är CreateDeleteStatement() och det är den som bygger ihop vår DELETE sats och den är inte heller speciellt komplicerad.
private IDbCommand CreateDeleteCommand(DataIdentifierInformation dataIdentifierInformation, DataSourceInformation dataSourceInformation)
{
//-- Checks to see so we have any dataIdentifierfields
if (dataIdentifierInformation == null)
throw new NoDataIdentifierInformationDefinedException();
StringBuilder sb = new StringBuilder();
string whereStatement = WHERE(dataIdentifierInformation);
if (string.IsNullOrEmpty(whereStatement))
throw new Exception();
sb.Append(DELETE(dataSourceInformation)).Append(whereStatement);
//-- Create commandobject
IDbCommand command = CreateSqlCommand();
command.CommandText = sb.ToString();
//-- Create parameterlist
foreach (IDataParameter parameter in CreateParameterList(dataIdentifierInformation))
command.Parameters.Add(parameter);
//-- Check so we really has a parameter value
if (command.Parameters.Count == 0)
throw new Exception();
//-- returnerar command object.
return command;
}
Här dök det upp en gammal välkänd metod WHERE(), den är vi lite försiktiga med, för om den skulle råka returnera en tom sträng, så kommer vi ta bort alla rader i databasen och de vill vi inte, så därför kontrollerar vi att den verkligen innehåller något innan vi fortsätter (hängslen och livrem)...
Det fanns en ny metod som vi inte stött på tidigare. DELETE och den bygger faktiskt bara upp vår DELETE sats, och eftersom denna är för en SQL Server så har den ingen * i sin DELETE sats...
private string DELETE(DataSourceInformation dataSourceInformation)
{
StringBuilder sb = new StringBuilder();
sb.Append(" DELETE ");
sb.Append(" FROM [").Append(dataSourceInformation.DataSourceName).Append("] ");
//-- return the DELETE statement
return sb.ToString();
}
Det är det som behövs för att deleta en rad från databasen utifrån ett objekt vi har.
GGladhMedlem sedan maj 20012 812 inlägg Nu är det faktiskt så att i ovanstående exempel så krävde vi att objektet fanns, vilket betyder att om vi står med identifier i handen, så måste vi antingen hämta objektet från databasen och sedan ta bort det ;) eller skapa ett nytt objekt och sätta värdet för identifier och sedan ta bort det objektet. För att göra vårt liv lite enklare så väljer vi den andra metoden men vi låter vår entitymapper lösa problem till oss.
public void Delete<ItemType>(Identifier identifier)
{
ItemType itemType = Activator.CreateInstance<ItemType>();
SetIdentifier<ItemType>(itemType, identifier);
Delete<ItemType>(itemType);
}
Enkelt va!
Visst skulle det gå att fixa det hela utan att behöv skapa objektet, men för att återanvända så mycket kod som möjligt (förenklar när man ändra saker och ting), så är det en billig kompromiss, men tanke på att de flesta Entitys inte gör något speciellt när de skall instanseras, om det är så att de skall göra en massa tunga saker, så bör man antingen ändra på det, eller se till så att man kan delete datan utan att behöva instansera objekt. Det går genom att leta fram DataIdentifierAttributet, plocka fram fältnamnet och sedan plocka fram DataFieldAttrubutet för detta fält och vips så har du löst det utan att instansera ditt objekt...
Men nu så valde jag att återanvända min kod från den andra Delete funktionen och då blir man tvungen att använda metoden SetIdentifier() som sätter identifier värdet till oss i vår nya instans, och som vanligt så spelar det ingen roll vilken typ vi skickar in, bara vi har satt DataIdentifierAttributet och DataFieldAttributet på fältet i klassen och det är av typen Identifier.
private void SetIdentifier<ItemType>(ItemType itemType, object identifier)
{
//-- Get all fieldInfors for the ItemType
FieldInfo[] fieldInfos = itemType.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
//-- Check so we have some field
if (fieldInfos == null || fieldInfos.Length == 0)
throw new Exception("No fields found");
for (int i = 0; i < fieldInfos.Length; i++)
{
if (fieldInfos[i].FieldType == typeof(Identifier))
{
fieldInfos[i].SetValue(itemType, new Identifier(identifier));
return;
}
}
//-- Couldn't find any Identifier
throw new Exception("No identifier type found");
}
Så ja nu har vi bara INSERT och UPDATE kvar, de är lite mer jobb med dem... men håll ut de kommer...
- M
CatZMedlem sedan jan. 20022 440 inläggHmm... Nu är jag ute på djupt vatten igen... sitter och kopierar och klistrar in din kod i ett tomt projekt för att testa hur det fungerar.
Tyvärr får jag inte ihop det riktigt, vet fasen inte var jag ska placera allting i för olika projekt/klassfiler... Ett projekt som ser ut på detta viset blir lite mer abstrakt än att göra som jag gjort tidigare så jag stöter på en del problem här men ska nog lyckas klura ut det :)
cokMedlem sedan dec. 2005664 inläggen fråga. Sitter på mobil just nu så det kan bli lite kryptiskt... Om jag har ett domän objekt "user" och har en beskrivande fil som innehåller info om mappning. Hur når jag denna filen utifrån itemtype, Då denna kan ligga i andra projekt etc. Har ingen hjälp installerad :(
CatZMedlem sedan jan. 20022 440 inläggÄsch jag blir tokig! Vilket blir lättast och mest överskådligt sätt att organisera allting? Delete, Select, update och insert eller?
Om jag förstått det hela rätt så är det dödssynd att ha mer än en klass i samma fil?
GGladhMedlem sedan maj 20012 812 inlägg
Catz skrev:
Äsch jag blir tokig! Vilket blir lättast och mest överskådligt sätt att organisera allting? Delete, Select, update och insert eller?
Om jag förstått det hela rätt så är det dödssynd att ha mer än en klass i samma fil?
Du lägger alla classer i egna filer i ett projekt (utom user som skall ligga i ett eget assenbly, men här i testet går det bra i samma projekt).
Du lägger sedan alla metoder som vi har skapat i EntityMapper klassen. Ditt projekt som innehåller din User skall ha en referens till projektet med EntityMappern. Sedan har du ytterligare ett projekt (din applikation) som skall ha en referens till både EntityMappern och till User projekten, du kan sedan ifrån din Applikation anropa entitymappern för att fylla din User klass.
- M
GGladhMedlem sedan maj 20012 812 inlägg
cok skrev:
Om jag har ett domän objekt "user" och har en beskrivande fil som innehåller info om mappning. Hur når jag denna filen utifrån itemtype, Då denna kan ligga i andra projekt etc. Har ingen hjälp installerad
Mest logiskt är att du har en XML-fil någonstans, och att du skickar med sökvägen till denna XML fil i konstruktorn till din EntityMapper, du låter sedan EntityMappern hämta upp XML-filen parsa igenom den efter information som pasar din typ. du kan ju ha en nod som ser ut så här
<EntityMapperMapper>
<Types>
<User>
<DataSource>
<Name>tblUser</Name>
<DataSource>
<DataField>
<ClassFieldname>_FirstName</ClassFieldName>
<DataSourceFieldName>fornamn</dataSourceFieldName>
</DataField>
<User>
</Types>
</EntityMapperMapper>
Sedan får du helt enkelt loppa igenom denna xml-nod för att bygga upp de olika informationsklasserna...
- M
Gladh, skulle det vara möjligt att använda din mapper mot Linq to sql?
Så att man kör alla select/inserts/updates/delets därigenom.
Jag tycker Linq är riktigt intressant men tycker att microsofts variant av att designa dbml-filerna inte är bra då alla klasser bakas ihop i en lång fil och man tappar kontrollen på klasserna.
Då vore det ultimata att kunna göra egna klasser som vanligt, mappa dem mot databasen genom din mapper, och sedan använda dem genom linq! :)
cokMedlem sedan dec. 2005664 inlägg
Gladh skrev:
cok skrev:
Om jag har ett domän objekt "user" och har en beskrivande fil som innehåller info om mappning. Hur når jag denna filen utifrån itemtype, Då denna kan ligga i andra projekt etc. Har ingen hjälp installerad
Mest logiskt är att du har en XML-fil någonstans, och att du skickar med sökvägen till denna XML fil i konstruktorn till din EntityMapper, du låter sedan EntityMappern hämta upp XML-filen parsa igenom den efter information som pasar din typ. du kan ju ha en nod som ser ut så här
<EntityMapperMapper>
<Types>
<User>
<DataSource>
<Name>tblUser</Name>
<DataSource>
<DataField>
<ClassFieldname>_FirstName</ClassFieldName>
<DataSourceFieldName>fornamn</dataSourceFieldName>
</DataField>
<User>
</Types>
</EntityMapperMapper>
Sedan får du helt enkelt loppa igenom denna xml-nod för att bygga upp de olika informationsklasserna...
- M
Nja. Jag skulle vilja ha det som i nhibernate, en mappfil per klass där mappfilen ligger i samma katalog som objektfilen. Mappfilen har jag koll på men inte hur man kan få tag på sökvägen till objektfilen.
CatZMedlem sedan jan. 20022 440 inläggDå har jag i alla fall fått allting att kompilera ok, då återstår att faktiskt hämta datan från db'n och binda den till tex en gridview. Jag gjorde en SQL Databas för enkelthets skull. Vad jag ska skicka för information i själva anropet?
Jag måste ju ange vilken db, tabell osv som vi ska hämta informationen eller delete:a info infrån.
cokMedlem sedan dec. 2005664 inläggdet får du se till att den informationen finns i ditt dal.
GGladhMedlem sedan maj 20012 812 inlägg Jag har lagt det så att jag tar emot en connectionstring i entitymapperns konstruktor, och när jag sedan skapar mitt command objekt så sätter jag connectionstringen där. Så här ser det ut, inte riktigt som i koden ovan eftersom den koden som jag vissat ovan är en förkortning och förenkling av hur det ser ut på riktigt hos mig....
private IDbCommand CreateSqlCommand()
{
_Log.TraceEnter();
try
{
//-- Create commandobject
IDbCommand command = new SqlCommand();
command.Connection = new SqlConnection(_ConnectionString);
command.CommandType = CommandType.Text;
return command as IDbCommand;
}
finally
{
_Log.TraceLeave();
}
}
Och sedan i mitt dal där jag exekverar mitt command så ser det ut så här för metoden som returnerar en datatable.
private DataTable ExecuteDataTableResult(params IDbCommand[] commands)
{
//-- declare variables
DataTable dataTable = null;
//-- Loop through all commands and execute, only return the last
for (int i = 0; i < commands.Length; i++)
dataTable = internalExecuteDataTableResult(commands[i]);
//-- Return datatable
return dataTable;
}
private DataTable internalExecuteDataTableResult(IDbCommand command)
{
//-- Declara variables
DataTable dataTable = null;
bool connectionAlreadyOpen = command.Connection.State == ConnectionState.Open;
try
{
//-- open the connection if needed.
if (!connectionAlreadyOpen)
command.Connection.Open();
//-- Fill DataTable with data
dataTable = FillDataTable(command);
}
finally
{
//-- Clean up after us
if (!connectionAlreadyOpen)
if (command.Connection != null)
command.Connection.Close();
}
//-- Return datatable
return dataTable;
}
private DataTable FillDataTable(IDbCommand command)
{
//-- Declara variables
DataTable dataTable = new DataTable();
if (command.GetType() == typeof(SqlCommand))
{
SqlDataAdapter dataAdapter = new SqlDataAdapter(command as SqlCommand);
try
{
dataAdapter.Fill(dataTable);
}
finally
{
if(dataAdapter != null)
dataAdapter.Dispose();
}
}
else if (command.GetType() == typeof(OleDbCommand))
{
OleDbDataAdapter dataAdapter = new OleDbDataAdapter(command as OleDbCommand);
try
{
dataAdapter.Fill(dataTable);
}
finally
{
if (dataAdapter != null)
dataAdapter.Dispose();
}
}
else if (command.GetType() == typeof(System.Data.Odbc.OdbcCommand))
{
OdbcDataAdapter dataAdapter = new OdbcDataAdapter(command as OdbcCommand);
try
{
dataAdapter.Fill(dataTable);
}
finally
{
if (dataAdapter != null)
dataAdapter.Dispose();
}
}
else
throw new CommandTypeNotFoundException(string.Format("Couldn't find the correct base type for the IDbCommand, it's type is {0}", command.GetType()));
//-- return datatable
return dataTable;
}
Men bry dig inte om det för det gör nog inte saken enklare....
- M
GGladhMedlem sedan maj 20012 812 inlägg
catz skrev:
Jag måste ju ange vilken db, tabell osv som vi ska hämta informationen eller delete:a info infrån.
Det enda så du behöver ange för EntityMappern är connectionStringen så den vet mot vilken databas den skall gå mot.
Tabell och kolumnnamn är ju information som du har satt på ditt Userobjekt så därför behöver du inte ange det någonstans, det är ju det som är det fina i kråksången...
- M
doggelito skrev:
Gladh, skulle det vara möjligt att använda din mapper mot Linq to sql?
Så att man kör alla select/inserts/updates/delets därigenom.
Jag tycker Linq är riktigt intressant men tycker att microsofts variant av att designa dbml-filerna inte är bra då alla klasser bakas ihop i en lång fil och man tappar kontrollen på klasserna.
Då vore det ultimata att kunna göra egna klasser som vanligt, mappa dem mot databasen genom din mapper, och sedan använda dem genom linq! :)
NHibernate kommer i nästa version att stödja LiNQ, det går redan nu att ladda ner det från deras svn :).
GGladhMedlem sedan maj 20012 812 inlägg
doggelito skrev:
Gladh, skulle det vara möjligt att använda din mapper mot Linq to sql?
Så att man kör alla select/inserts/updates/delets därigenom.
Nej inte om du inte bygger om en massa, men du skulle ju kunna använda dig av Linq, alltså hämta all information till en lista och använd sedan Linq för att filtrera den, dock ingen lösning som jag rekomenderar...
- M
GGladhMedlem sedan maj 20012 812 inlägg
cok skrev:
Nja. Jag skulle vilja ha det som i nhibernate, en mappfil per klass där mappfilen ligger i samma katalog som objektfilen. Mappfilen har jag koll på men inte hur man kan få tag på sökvägen till objektfilen.
Men hjälp av reflections och Assembly klassen, där någonstans så skall sökvägen för var Assemblyn finns placerad på servern vara lagrard.
typ något sånt här:
user.GetType().Assembly.FilePath()
Eller något, du hittar det säkert när du börjar rota i det...
- M
Gladh skrev:
cok skrev:
Nja. Jag skulle vilja ha det som i nhibernate, en mappfil per klass där mappfilen ligger i samma katalog som objektfilen. Mappfilen har jag koll på men inte hur man kan få tag på sökvägen till objektfilen.
Men hjälp av reflections och Assembly klassen, där någonstans så skall sökvägen för var Assemblyn finns placerad på servern vara lagrard.
typ något sånt här:
user.GetType().Assembly.FilePath()
Eller något, du hittar det säkert när du börjar rota i det...
- M
Det är nog ännu smartare att göra XML filen till embedded i dll:en så slipper man tänka på att en xml fil skall med :). Precis som NHibernate :D.
Man kan ha en och samma fil i NHibernate också, men det blir ju mer uppdelat och snyggare med en fil för varje klass :).
GGladhMedlem sedan maj 20012 812 inlägg
nickemannen skrev:
Det är nog ännu smartare att göra XML filen till embedded i dll:en så slipper man tänka på att en xml fil skall med . Precis som NHibernate .
Så länge det inte innebär att man skall kompilera om sin assembly om man byter något i XML-filen, för måste man det, så kan man lika gärna ha attributen...
- M