webForumDet fria alternativet

skapa en egen DAL med custom entities?

.NET

22 svar · 1 376 visningar · startad av CatZ

Medlem sedan jan. 20022 440 inlägg
Frågan#1

Jag har läst en massa nu men fattar inte mycket av det jag läser, jag hade tänkt för lärandets skull göra en enkel sida med BLL och DAL till en SQL 2005 databas. Bara något enkelt projekt till att börja med.

Nu kan det kanske bero på att jag har adhd att jag inte fattar det jag läser men får jag en färdig lösning att studera så lär jag mig lättare TRODDE JAG!

Det här är ju rena grekiskan för mig, det skrivs i ett antal olika ASP.NET C# böcker om varför man ska göra si och så men det står ta mej fan ingenting om vad koden i sig gör / betyder. Man får förklarat med för och nackdelarna att göra på vissa sätt sedan skriver författaren. -"Men jag gör såhär för enkelhetens skull" Sedan kommer en lösning utan annan text än: -"Snygg jobbat va?"

Nu kanske jag överdrev lite men jag skulle behöva lite konkreta korta kodexempel MED en förklaring, det behöver inte vara så himla avancerat. Nåt i stil med såhär kan man koda sin DAL i C# och såhär kommer man åt den med sin BLL :)

Någon som ids?

Medlem sedan jan. 20022 440 inlägg
#2

Äsch jag skiter i egna entiteter så länged. Jag kör med ett Typat dataset som DAL och har påbörjat arbetet med att skapa ett BLL till detta.

Såhär kan det se ut då

using wbDataTableAdapters;

[System.ComponentModel.DataObject]
public class AdminsBLL
{
    private AdminsTableAdapter _adminsAdapter = null;
    protected AdminsTableAdapter Adapter
    {
        get
        {
            if (_adminsAdapter == null)
                _adminsAdapter = new AdminsTableAdapter();

            return _adminsAdapter;
        }
    }

    [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, true)]
    public wbData.AdminsDataTable GetAdmins()
    {
        return Adapter.GetAdmins();
    }

    [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
    public wbData.AdminsDataTable GetAdminsByID(int ID)
    {
        return Adapter.GetAdminsByID(ID);
    }

    [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Insert, true)]
    public bool AddProduct(string fullName, string password, string email, int? writeaccess, int? adminaccess, int? resultaccess,
                          string phone)
    {
        // Create a new ProductRow instance
        wbData.AdminsDataTable admins = new wbData.AdminsDataTable();
        wbData.AdminsRow admin = admins.NewAdminsRow();

        admin.FullName = fullName;
        if (password == null) admin.SetPasswordNull(); else admin.Password = password;
        if (email == null) admin.SetEmailNull(); else admin.Email = email;
        if (writeaccess == null) admin.SetWriteAccessNull(); else admin.WriteAccess = writeaccess.Value;
        if (adminaccess == null) admin.SetAdminAccessNull(); else admin.AdminAccess = adminaccess.Value;
        if (resultaccess == null) admin.SetResultAccessNull(); else admin.ResultAccess = resultaccess.Value;
        if (phone == null) admin.SetPhoneNull(); else admin.Phone = phone;

        // Add the new product
        admins.AddAdminsRow(admin);
        int rowsAffected = Adapter.Update(admins);

        // Return true if precisely one row was inserted, otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Update, true)]
    public bool UpdateAdmin(string fullName, string password, string email, int? writeaccess, int? adminaccess, int? resultaccess,
                          string phone, int ID)
    {
        wbData.AdminsDataTable admins = Adapter.GetAdminsByID(ID);
        if (admins.Count == 0)
            // no matching record found, return false
            return false;

        wbData.AdminsRow admin = admins[0];

        admin.FullName = fullName;
        if (password == null) admin.SetPasswordNull(); else admin.Password = password;
        if (email == null) admin.SetEmailNull(); else admin.Email = email;
        if (writeaccess == null) admin.SetWriteAccessNull(); else admin.WriteAccess = writeaccess.Value;
        if (adminaccess == null) admin.SetAdminAccessNull(); else admin.AdminAccess = adminaccess.Value;
        if (resultaccess == null) admin.SetResultAccessNull(); else admin.ResultAccess = resultaccess.Value;
        if (phone == null) admin.SetPhoneNull(); else admin.Phone = phone;

        // Update the product record
        int rowsAffected = Adapter.Update(admin);

        // Return true if precisely one row was updated, otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteAdmin(int ID)
    {
        int rowsAffected = Adapter.Delete(ID);

        // Return true if precisely one row was deleted, otherwise false
        return rowsAffected == 1;
    }
}
Medlem sedan jan. 20022 440 inlägg
#3

Det blir allt lite krångligare att hantera tex en datagrid då det inte går binda direkt till en datasource utan allt måste verkligen kodas för hand eller?

protected void Page_Load(object sender, EventArgs e)
{
    AdminsTableAdapter adminsAdapter = new AdminsTableAdapter();
    gvAdmins.DataSource = adminsAdapter.GetAdmins();
    gvAdmins.DataBind();
}
Medlem sedan maj 20012 812 inlägg
#4

Det är inte alls speciellt svårt och krångligt, det många gör är att slänga sig med fina ord som låter bra och svårt, men som egentligen inte är något speciellt.

Om vi tar ett väldigt enkelt exempel på hur du kan göra så börjar vi med en solution och 4 olika projekt.

Projekt 1, innehåller alla dina entiteter, alltså dina objekt.

public class Person{
  public string Name;
  public int Age;

  public Person(){}
}

osv osv... Här har du nu en klass som har möjlighet att innehålla alla information som du vill om en person.

Projekt 2, det är ditt DAL. I vanliga fall så är mitt DAL ett generellt DAL som tar emot ett IDBCommand och returnerer ett DataTable med data, och sedan har jag en ORMapper som mappar DataTable till mitt object. Men i detta exempel så bygger vi in allt i DAL:et.

public class DAL{
 
 public List<Person> GetAllPersons(){
  
   //-- Här gör du en databas fråga mot din databas som hämtar all Person data från databasen till ett DataTable.
   ...
   ...

  //-- Här loppar vi igenom vårt DataTable och skapar våra Person objekt.
  List<Person> personList = new List<Person>();

  for(int i = 0; i < dataTable.Row.Count; i++)
  {
     Person person = new Person();
     person.Name = dataTable.Row[i].Column["Name"];
     person.Age = dataTable.Row[i].Column["Agr"];

     personList.Add(person);
  }

  return personList;
 }
}

I vårt BusinessLager så gör vi affärslogiska saker, och i fallet med att hämta en Lista med personer så finns inte så mycket logik, så vi kallar bara direkt på DAL:et.

Projekt 3, BusinessLayer

public class BLL{
 
 public List<Person> GetAllPerson(){
    
    DAL dal = new DAL();
    return dal.GetAllPerson();
 }
}

Och sedan i ditt presentationslager så uttnytjar du bara den listan så du får från ditt BusinessLager

Projekt 4, PresentationsLager

protected void Page_Load(object sender, EventArgs e)
{
   BLL bll = new BLL();
   List<Person> personList = bll.GetAllPerson();
   gvPerson.DataSource = personList;
   gvPerson.DataBind();
}

Här har du nu grunden för en flerskiktad lösning som har alla fördelar men även nackdelen att det tar längre tid att sätta upp och få igång än en "klicka-gissa-spring" lösning som MS förespråkar.

Fördelen här är att om du behöver ändra var ifrån som du får din data ifrån, så går du in i DALet och ändra connectionstring och så fungerar det utan att du behöver ändra i något annat lager, eller om det helt plötsligt blir så att man lägger ut Person data i flera olika datakäller, då bygger du ihop det i ditt BLL och inget i ditt prestationslager behöver ändras, det är inget som du hade löst med MS DataSet. Du kan även uttöka funktionalliteten i din entiteter så de innehåller valideringslogik och andra finesser.

Jag har exempelvis en BaseEntity som innehåller en del Interface som kan vara bra att ha när man vill använda sig av Binding-funktionerna, så som INotifyPropertyChange interfacet eller IEditableObject eller IDataErrorInfo även ICloneable som gör att jag enkelt kan klona ett objekt om jag behöver det...

- M

Medlem sedan jan. 20022 440 inlägg
#5

Jamen Gladh det var ju precis vad jag behövde veta. Snacka om utförligt! Du borde skriva en hel bok i samma anda.

Det var helt enkelt exakt vad jag försökte komma till utan resultat. Det där inlägget borde kvalificera dig till årets medlem 3 år på raken!

Medlem sedan jan. 20022 440 inlägg
#6

Eftersom Gladhs kod var mest pseudokod och jag inte har någon direkt färdig kod att visa hänvisar jag intresserade till http://msdn2.microsoft.com/en-us/library/aa479341.aspx där det står lite mer om just detta ämnet. Det var en toppenlänk som jag råkade hitta av en slump när jag surfade runt om annat.

Medlem sedan jan. 20023 327 inlägg
#7

Gladh skrev:

Jag har exempelvis en BaseEntity som innehåller en del Interface som kan vara bra att ha när man vill använda sig av Binding-funktionerna, så som INotifyPropertyChange interfacet eller IEditableObject eller IDataErrorInfo även ICloneable som gör att jag enkelt kan klona ett objekt om jag behöver det...

Gladh, det skulle vara intressant att få veta hur du implementerar Clone-metoden. Har du en generell Clone-metod som fixar "allt" eller överskuggar ("overridar") du Clone-metoden i din ärvda klass.

Min tanke var att klona bas-klassens attribut i basklassen och låta den ärvda klassen ta hand om kloningen av dess egna attribut.

Medlem sedan maj 20012 812 inlägg
#8

Jag har en generell clone method som clonar det mesta :e. Här är koden som du kan titta på, jag hittade den på nätet någonstans..

Det viktiga här är att de olika objekt som du har i ditt objekt som du skall klona också implementerar ICloneable, i mitt fall så ärver de från samma basklass så det är ju inget problem då.

        /// <summary>
        /// Create a deep copy of this object
        /// </summary>
        /// <returns></returns>
        protected virtual object Clone()
        {
            //-- First we create a instance of the type
            object cloneInstance = Activator.CreateInstance(this.GetType());
            //-- Get an array of all fields in this object
            FieldInfo[] fieldInfos = this.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            int i = 0;

            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                //-- Check if the fields support the ICloneble interface
                CheckForICloneableSupport(ref cloneInstance, fieldInfo, fieldInfos, i);
                //-- Check if this fields support the IEnumerable interface
                CheckForIEnumerableSupport(ref cloneInstance, fieldInfo, fieldInfos, i);
                i++;
            }
            //-- return the cloned instance
            return cloneInstance;
        }
        private void CheckForICloneableSupport(ref object cloneInstance, FieldInfo fieldInfo, FieldInfo[] fieldInfos, int i)
        {
            //-- Check if the fields support the ICloneble interface
            Type cloneableType = fieldInfo.FieldType.GetInterface("ICloneable", true);

            if (cloneableType != null)
            {
                //-- This field supports ICloneable use that to get the value.
                ICloneable cloneable = (ICloneable)fieldInfo.GetValue(this);
                fieldInfos[i].SetValue(cloneInstance, (cloneable != null ? cloneable.Clone() : null));
            }
            else
                //-- This field dosen't support ICloneable so only set the value
                fieldInfos[i].SetValue(cloneInstance, fieldInfo.GetValue(this));

        }
        private void CheckForIEnumerableSupport(ref object cloneInstance, FieldInfo fieldInfo, FieldInfo[] fieldInfos, int i)
        {
            //-- Check if this fields support the IEnumerable interface
            Type enumerableType = fieldInfo.FieldType.GetInterface("IEnumerable", true);
            if (enumerableType != null)
            {
                //-- Get the IEnumerable interface from the field.
                IEnumerable enumerable = (IEnumerable)fieldInfo.GetValue(this);

                //-- This version support the IList and the IDictionary interfaces to iterate on collections.
                Type listType = fieldInfos[i].FieldType.GetInterface("IList", true);
                Type dictionaryType = fieldInfos[i].FieldType.GetInterface("IDictionary", true);

                int j = 0;
                if (listType != null)
                {
                    //-- Getting the IList interface.
                    IList list = (IList)fieldInfos[i].GetValue(cloneInstance);

                    if (enumerable != null)
                    {
                        foreach (object obj in enumerable)
                        {
                            //-- Checking to see if the current item support the ICloneable interface.
                            Type cloneableType = obj.GetType().GetInterface("ICloneable", true);

                            if (cloneableType != null)
                            {
                                //-- If it does support the ICloneable interface, we use it to set the clone of the object in the list.
                                ICloneable cloneable = (ICloneable)obj;
                                list[j] = (cloneable != null ? cloneable.Clone() : null);
                            }

                            //-- NOTE: If the item in the list is not support the ICloneable interface then in the 
                            //-- cloned list this item will be the same item as in the original list (as long as this type is a reference type).
                            j++;
                        }
                    }
                }
                else if (dictionaryType != null)
                {
                    //-- Getting the dictionary interface.
                    IDictionary dic = (IDictionary)fieldInfos[i].GetValue(cloneInstance);
                    j = 0;

                    if (enumerable != null)
                    {
                        foreach (DictionaryEntry de in enumerable)
                        {
                            //-- Checking to see if the item support the ICloneable interface.
                            Type cloneableType = de.Value.GetType().GetInterface("ICloneable", true);

                            if (cloneableType != null)
                            {
                                ICloneable cloneable = (ICloneable)de.Value;
                                dic[de.Key] = (cloneable != null ? cloneable.Clone() : null);
                            }
                            j++;
                        }
                    }
                }
            }
        }
        #endregion

Det går säkert att förbättra, har inte tittat så mycket på den eftersom jag hittade koden på nätet och den klarade av det den behövde när jag behövde klona ett objekt.

- M

Medlem sedan dec. 19996 721 inlägg
#9

Ser helt OK, men ett par kommentarer, bara för den goda diskussionens skull.

Metoderna heter "Check..." men gör något helt annat. Illa :)

Vad är det för mening med att skicka med "fieldInfos" och "i" in i metoderna. "fieldInfo" räcker väl gott?

En sån här harang:

Type cloneableType = fieldInfo.FieldType.GetInterface("ICloneable", true);

if (cloneableType != null)
{
	//-- This field supports ICloneable use that to get the value.
	ICloneable cloneable = (ICloneable)fieldInfo.GetValue(this);
	fieldInfos[i].SetValue(cloneInstance, (cloneable != null ? cloneable.Clone() : null));
}
else
	//-- This field dosen't support ICloneable so only set the value
	fieldInfos[i].SetValue(cloneInstance, fieldInfo.GetValue(this));

borde kunna skrivas som

object candidate = fieldInfo.GetValue(this);

if (candidate is ICloneable && candidate!=null) //eller typomvandla till en ny instans med "as" direkt
{
	candidate=(candidate as ICloneable).Clone();
}

fieldInfos[i].SetValue(cloneInstance, candidate);

Jag ställer mig också aningen tvekande inför hur vettigt/nödvändigt det är med en generell Clone-metod.

Medlem sedan jan. 20023 327 inlägg
#10

Suveränt. Tror det var härdu hittade koden Gladh. :)

Såg den länken igår och hoppades på att det kanske fanns en "enklare" lösning. Läser man kommentarerna så finns det tydligen en del problem med den clone-metoden, fast du kanske har rättat dessa? Hmm kom bara åt en cachad version av sidan så jag kan bara läsa rubrikerna för kommentarerna.

Medlem sedan jan. 20022 440 inlägg
#11

Hmm, jag är fortfarande inte helt övertygad om vad det ska vara bra till?

Medlem sedan jan. 20023 327 inlägg
#12

Clone-metoden är bra när du vill göra kopior på objekt. Eftersom objekt "lever" på heapen och man kommer åt dem med referenser.

Tänk dig följande exempel.

Object obj = new Object();
Object objCopy = obj;

objCopy refererar inte en kopia av obj, däremot har du två objektreferenser som pekar på samma objektinstans.

Med value types är det dock helt ok att skriva:

int number = 10;
int copyNumber = number;

För att förstå varför Clone är nödvändigt bör man veta skillnande mellan value types (int, string, float etc) och referenser (objekt). Vad Clone metoden ska göra är att kopiera över alla värden från ett objekt till ett annat objekt på ett "riktigt sätt". När du kör myObject.Clone() är tanken att du ska få en kopia av objektet.

Läs här för en mer utförlig förklaring:
http://www.agiledeveloper.com/articles/cloning072002.htm

Denna är nog ännu bättre..
http://www.ondotnet.com/pub/a/dotnet/2002/11/25/copying.html

Medlem sedan maj 20012 812 inlägg
#13

Compusa skrev:

Läser man kommentarerna så finns det tydligen en del problem med den clone-metoden, fast du kanske har rättat dessa?

Nope! Jag har inte gjort några eller väldigt få ändringar i den, den löste mitt problem som jag hade och har hängt med sedan dess. Men visst skulle det säkert gå att lösa alla problem.

Det borde ju gå att dumpa all data till en minnes area och sedan läsa upp data igenom till ett annat objekt. Jag vet att jag satt och pullade med något liknande, men då ville jag serializera objektet till en sträng och sedan desiarlisera tillbaka det till ett annat objekt. Men det krävde ju att alla private members hade get.set properties så det föll på sin egen rimlighet.

Vet att WCF's DataMember/DataContract attribute har löst detta så den kan ta och serializera private members så det borde kanske vara vägen att gå, men vem har tid att grotta ner sig i något så roligt...

- M

Medlem sedan jan. 20022 440 inlägg
#14

För att spinna vidare lite på det här ämnet. Säg att jag i databasen har en html fil som jag vill skriva till browsern med följande kod

HttpContext.Current.Response.ContentType = "text/html"; 
HttpContext.Current.Response.AddHeader("Content-Disposition: ", "filename=" + resultsList.FileName); 
HttpContext.Current.Response.AddHeader("Content-Length", resultsList.FileSize); 
HttpContext.Current.Response.BinaryWrite(resultsList.File); 
HttpContext.Current.Response.Flush();

Vad är best practice när jag hämtar datan? Jag tänkte då att spara det i en lista och komma åt kanske inte är det lättaste. Nu är det ju bara en rad med 3 värden som ska skrivas till webläsaren.

Kan jag tillämpa tidigare lösning på detta på nåt vis? Det är ju inte så att jag vill binda till en objectdatasource...

Medlem sedan maj 20012 812 inlägg
#15

CatZ skrev:

Kan jag tillämpa tidigare lösning på detta på nåt vis? Det är ju inte så att jag vill binda till en objectdatasource...

Jag använder aldrig objectDataSource, tycker inte om att ha "affärslogik" i mitt presentationslager. Däremot så är det inga som helst problem att följa mitt exempel ovan och istället för att hämta en List av objekt så hämtar du bara ett objekt.

public class BLL{
 
 public HtmlInformation GetHtmlInformationByIdentifier(long identifier){
    
    DAL dal = new DAL();
    return dal.GetHtmlInformationByIdentifier(identifier);
 }
}

Och i din HttpHandler som du använder dig av istället för en ASPX-sida.


BLL bll = new BLL();
HtmlInformation htmlInformation = bll.GetHtmlInformationByIdentifier(identifier);

HttpContext.Current.Response.ContentType = "text/html"; 
HttpContext.Current.Response.AddHeader("Content-Disposition: ", "filename=" + htmlInformation.FileName); 
HttpContext.Current.Response.AddHeader("Content-Length", htmlInformation.FileSize); 
HttpContext.Current.Response.BinaryWrite(htmlInformation.File); 
HttpContext.Current.Response.Flush();

- M

Medlem sedan jan. 20022 440 inlägg
#16

Åh fan det är SÅ man gör!! Vad smidigt!

Ytterligare en följdfråga då, hur binder du datan i ditt presentationslager då? Jag måste ärligt säga att jag håller med om att det blir fult i presentationslagret under tiden man arbetar med det men finns det några för / nackdelar med just objdatasource? Om jag förstått det hela rätt så blir det svårt att göra några andra ändringar i presentationslagret än "för hand" (dvs programmatiskt) om du inte
1. har en OR Mapper
2. använder dig av någon av de medföljande datasourcen's

Sant/falskt?

Jag har egentligen inte så mycket emot microsofts klick-lösningar men jag har några otäckt stora projekt jag ska göra som kräver bättre prestanda och jag märker det redan. Allt hämtas, laddas, skrivs otroligt mycket snabbare i webläsaren och då är jag ensam användare. Antar att jag kommer bli tvungen att optimera cahce användning och liknande dessutom.

Medlem sedan maj 20012 812 inlägg
#17

catz skrev:

Ytterligare en följdfråga då, hur binder du datan i ditt presentationslager då?

gladh skrev:

protected void Page_Load(object sender, EventArgs e)
{
   BLL bll = new BLL();
   List<Person> personList = bll.GetAllPerson();
   gvPerson.DataSource = personList;
   gvPerson.DataBind();
}

catz skrev:

Om jag förstått det hela rätt så blir det svårt att göra några andra ändringar i presentationslagret än "för hand" (dvs programmatiskt)

Hur gör du förändringar i presentationslagret om du inte gör de för hand? Det enda som jag ser skulle kunna ge problem är om du binder en lista till en gridview så kanske du måste specificera vilka kolumner du vill ha med och till vilken property varje kolumn skall hämta sin data ifrån. Jag vet faktiskt inte om den klara av det automatiskt, då jag aldrig har testat det så, har aldrig stött på ett exempel där jag velat binda alla mina properties till en gridview.

catz skrev:

har en OR Mapper

OR Mappern har inget med presentationslagret att göra, den gör bara det du gör i ditt DAL idag, mappar en databastabell mot ett objekt.

- M

Medlem sedan jan. 20022 440 inlägg
#18

Gladh skrev:

Hur gör du förändringar i presentationslagret om du inte gör de för hand? Det enda som jag ser skulle kunna ge problem är om du binder en lista till en gridview så kanske du måste specificera vilka kolumner du vill ha med och till vilken property varje kolumn skall hämta sin data ifrån. Jag vet faktiskt inte om den klara av det automatiskt, då jag aldrig har testat det så, har aldrig stött på ett exempel där jag velat binda alla mina properties till en gridview.

Det är enklare att arbeta mot en ObjectDataSource under tiden man arbetar med GridView men så fort man är klar blir datasourcen överflödig och jag kan då binda direkt till mot entiteterna istället.

Nästa fråga då (vilken otroligt seglivad tråd det blev) Hur ska jag göra i det här fallet:
Jag har en sida med nyheter. Ska jag då
1. Behandla skribenter på nyhetssidan med entiteter från både NewsEntity och AdminEntity?

2. Göra en join mellan de två som jag tycker och binda till entiteter på NewsEntity. Databasen är ju fortfarande normaliserad men koden blir ju lite lätt denormaliserad...

Medlem sedan juni 20003 076 inlägg
#19

Jag är ingen allvetande skräphög men jag tycker nog att NewsEntity klassen bör innehålla fakta om skribenten. För att åstakomma det så kan du:
1. Som ditt förslag nr. 2, göra en join.
2. Göra en vy i databasen.

Nu får man väl knäna avslagna men du kan också kolla på Linq, då kan du relatera klasser med varann. :)
http://weblogs.asp.net/scottgu/archive/2007/05/29/linq-to-sql-part-2-defining-our-data-model-classes.aspx
Linq är lite Microsoft peka å klicka men det har helt klart sina fördelar!

Medlem sedan dec. 2005664 inlägg
#20

doggelito skrev:

Linq är lite Microsoft peka å klicka men det har helt klart sina fördelar!

Linq ska inte ha de skällsorden "peka och klicka". MS har säkerligen jättafina peka och klicka stöd, men nog är Linq så mycket mer ;)

282 ms totalt · 4 externa anrop · v20260731065814-full.a51de22e
128 ms — deklarationer (db)
0 ms — hämta statistik (cache)
151 ms — hämta tråd, inlägg och bilagor (db)
124 ms — ändringar (db)